adding configurations made in EL-lojas to correct release and ordering of remote tickets
commit
b59cc61b6d
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 canove
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,441 @@
|
||||||
|
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?business=VWW3BHW4AWHUY&item_name=Desenvolvimento+de+Software¤cy_code=BRL)
|
||||||
|
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B21084%2Fgithub.com%2Fcanove%2Fwhaticket.svg?type=shield)](https://app.fossa.com/projects/custom%2B21084%2Fgithub.com%2Fcanove%2Fwhaticket?ref=badge_shield)
|
||||||
|
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=canove_whaticket&metric=alert_status)](https://sonarcloud.io/dashboard?id=canove_whaticket)
|
||||||
|
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=canove_whaticket&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=canove_whaticket)
|
||||||
|
[![Discord Chat](https://img.shields.io/discord/784109818247774249.svg?logo=discord)](https://discord.gg/Dp2tTZRYHg)
|
||||||
|
|
||||||
|
# WhaTicket
|
||||||
|
|
||||||
|
**NOTE**: The new version of whatsapp-web.js required Node 14. Upgrade your installations to keep using it.
|
||||||
|
|
||||||
|
A _very simple_ Ticket System based on WhatsApp messages.
|
||||||
|
|
||||||
|
Backend uses [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) to receive and send WhatsApp messages, create tickets from them and store all in a MySQL database.
|
||||||
|
|
||||||
|
Frontend is a full-featured multi-user _chat app_ bootstrapped with react-create-app and Material UI, that comunicates with backend using REST API and Websockets. It allows you to interact with contacts, tickets, send and receive WhatsApp messages.
|
||||||
|
|
||||||
|
**NOTE**: I can't guarantee you will not be blocked by using this method, although it has worked for me. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
I'm a SysAdmin, and in my daily work, I do a lot of support through WhatsApp. Since WhatsApp Web doesn't allow multiple users, and 90% of our tickets comes from this channel, we created this to share same whatsapp account cross our team.
|
||||||
|
|
||||||
|
## How it works?
|
||||||
|
|
||||||
|
On every new message received in an associated WhatsApp, a new Ticket is created. Then, this ticket can be reached in a _queue_ on _Tickets_ page, where you can assign ticket to your yourself by _aceppting_ it, respond ticket message and eventually _resolve_ it.
|
||||||
|
|
||||||
|
Subsequent messages from same contact will be related to first **open/pending** ticket found.
|
||||||
|
|
||||||
|
If a contact sent a new message in less than 2 hours interval, and there is no ticket from this contact with **pending/open** status, the newest **closed** ticket will be reopen, instead of creating a new one.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![](https://github.com/canove/whaticket/raw/master/images/whaticket-queues.gif)
|
||||||
|
<img src="https://raw.githubusercontent.com/canove/whaticket/master/images/chat2.png" width="350"> <img src="https://raw.githubusercontent.com/canove/whaticket/master/images/chat3.png" width="350"> <img src="https://raw.githubusercontent.com/canove/whaticket/master/images/multiple-whatsapps2.png" width="350"> <img src="https://raw.githubusercontent.com/canove/whaticket/master/images/contacts1.png" width="350">
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Have multiple users chating in same WhatsApp Number ✅
|
||||||
|
- Connect to multiple WhatsApp accounts and receive all messages in one place ✅ 🆕
|
||||||
|
- Create and chat with new contacts without touching cellphone ✅
|
||||||
|
- Send and receive message ✅
|
||||||
|
- Send media (images/audio/documents) ✅
|
||||||
|
- Receive media (images/audio/video/documents) ✅
|
||||||
|
|
||||||
|
## Installation and Usage (Linux Ubuntu - Development)
|
||||||
|
|
||||||
|
Create Mysql Database using docker:
|
||||||
|
_Note_: change MYSQL_DATABASE, MYSQL_PASSWORD, MYSQL_USER and MYSQL_ROOT_PASSWORD.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name whaticketdb -e MYSQL_ROOT_PASSWORD=strongpassword -e MYSQL_DATABASE=whaticket -e MYSQL_USER=whaticket -e MYSQL_PASSWORD=whaticket --restart always -p 3306:3306 -d mariadb:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Install puppeteer dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libxshmfence-dev libgbm-dev wget unzip fontconfig locales gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
Clone this repo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/canove/whaticket/ whaticket
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to backend folder and create .env file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill `.env` file with environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_ENV=DEVELOPMENT #it helps on debugging
|
||||||
|
BACKEND_URL=http://localhost
|
||||||
|
FRONTEND_URL=https://localhost:3000
|
||||||
|
PROXY_PORT=8080
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
DB_HOST= #DB host IP, usually localhost
|
||||||
|
DB_DIALECT=
|
||||||
|
DB_USER=
|
||||||
|
DB_PASS=
|
||||||
|
DB_NAME=
|
||||||
|
|
||||||
|
JWT_SECRET=3123123213123
|
||||||
|
JWT_REFRESH_SECRET=75756756756
|
||||||
|
```
|
||||||
|
|
||||||
|
Install backend dependencies, build app, run migrations and seeds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npx sequelize db:migrate
|
||||||
|
npx sequelize db:seed:all
|
||||||
|
```
|
||||||
|
|
||||||
|
Start backend:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a second terminal, go to frontend folder and create .env file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano .env
|
||||||
|
REACT_APP_BACKEND_URL = http://localhost:8080/ # Your previous configured backend app URL.
|
||||||
|
```
|
||||||
|
|
||||||
|
Start frontend app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
- Go to http://your_server_ip:3000/signup
|
||||||
|
- Create an user and login with it.
|
||||||
|
- On the sidebard, go to _Connections_ page and create your first WhatsApp connection.
|
||||||
|
- Wait for QR CODE button to appear, click it and read qr code.
|
||||||
|
- Done. Every message received by your synced WhatsApp number will appear in Tickets List.
|
||||||
|
|
||||||
|
## Basic production deployment (Ubuntu 18.04 VPS)
|
||||||
|
|
||||||
|
All instructions below assumes you are NOT running as root, since it will give an error in puppeteer. So let's start creating a new user and granting sudo privileges to it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adduser deploy
|
||||||
|
usermod -aG sudo deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can login with this new user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
su deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need two subdomains forwarding to yours VPS ip to follow these instructions. We'll use `myapp.mydomain.com` to frontend and `api.mydomain.com` to backend in the following example.
|
||||||
|
|
||||||
|
Update all system packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update && sudo apt upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Install node and confirm node command is available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Install docker and add you user to docker group:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install apt-transport-https ca-certificates curl software-properties-common
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||||
|
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install docker-ce
|
||||||
|
sudo systemctl status docker
|
||||||
|
sudo usermod -aG docker ${USER}
|
||||||
|
su - ${USER}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create Mysql Database using docker:
|
||||||
|
_Note_: change MYSQL_DATABASE, MYSQL_PASSWORD, MYSQL_USER and MYSQL_ROOT_PASSWORD.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name whaticketdb -e MYSQL_ROOT_PASSWORD=strongpassword -e MYSQL_DATABASE=whaticket -e MYSQL_USER=whaticket -e MYSQL_PASSWORD=whaticket --restart always -p 3306:3306 -d mariadb:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Clone this repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~
|
||||||
|
git clone https://github.com/canove/whaticket whaticket
|
||||||
|
```
|
||||||
|
|
||||||
|
Create backend .env file and fill with details:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp whaticket/backend/.env.example whaticket/backend/.env
|
||||||
|
nano whaticket/backend/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_ENV=
|
||||||
|
BACKEND_URL=https://api.mydomain.com #USE HTTPS HERE, WE WILL ADD SSL LATTER
|
||||||
|
FRONTEND_URL=https://myapp.mydomain.com #USE HTTPS HERE, WE WILL ADD SSL LATTER, CORS RELATED!
|
||||||
|
PROXY_PORT=443 #USE NGINX REVERSE PROXY PORT HERE, WE WILL CONFIGURE IT LATTER
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_DIALECT=
|
||||||
|
DB_USER=
|
||||||
|
DB_PASS=
|
||||||
|
DB_NAME=
|
||||||
|
|
||||||
|
JWT_SECRET=3123123213123
|
||||||
|
JWT_REFRESH_SECRET=75756756756
|
||||||
|
```
|
||||||
|
|
||||||
|
Install puppeteer dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libxshmfence-dev libgbm-dev wget unzip fontconfig locales gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
Install backend dependencies, build app, run migrations and seeds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd whaticket/backend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npx sequelize db:migrate
|
||||||
|
npx sequelize db:seed:all
|
||||||
|
```
|
||||||
|
|
||||||
|
Start it with `npm start`, you should see: `Server started on port...` on console. Hit `CTRL + C` to exit.
|
||||||
|
|
||||||
|
Install pm2 **with sudo**, and start backend with it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo npm install -g pm2
|
||||||
|
pm2 start dist/server.js --name whaticket-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Make pm2 auto start afeter reboot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pm2 startup ubuntu -u `YOUR_USERNAME`
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the last line outputed from previus command and run it, its something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo env PATH=\$PATH:/usr/bin pm2 startup ubuntu -u YOUR_USERNAME --hp /home/YOUR_USERNAM
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to frontend folder and install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../frontend
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit .env file and fill it with your backend address, it should look like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REACT_APP_BACKEND_URL = https://api.mydomain.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
Build frontend app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Start frontend with pm2, and save pm2 process list to start automatically after reboot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pm2 start server.js --name whaticket-frontend
|
||||||
|
pm2 save
|
||||||
|
```
|
||||||
|
|
||||||
|
To check if it's running, run `pm2 list`, it should look like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deploy@ubuntu-whats:~$ pm2 list
|
||||||
|
┌─────┬─────────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
|
||||||
|
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ . │ status │ cpu │ mem │ user │ watching │
|
||||||
|
├─────┼─────────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
|
||||||
|
│ 1 │ whaticket-frontend │ default │ 0.1.0 │ fork │ 179249 │ 12D │ 0 │ online │ 0.3% │ 50.2mb │ deploy │ disabled │
|
||||||
|
│ 6 │ whaticket-backend │ default │ 1.0.0 │ fork │ 179253 │ 12D │ 15 │ online │ 0.3% │ 118.5mb │ deploy │ disabled │
|
||||||
|
└─────┴─────────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Install nginx:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove nginx default site:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rm /etc/nginx/sites-enabled/default
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a new nginx site to frontend app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/sites-available/whaticket-frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit and fill it with this information, changing `server_name` to yours equivalent to `myapp.mydomain.com`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
server {
|
||||||
|
server_name myapp.mydomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3333;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create another one to backend api, changing `server_name` to yours equivalent to `api.mydomain.com`, and `proxy_pass` to your localhost backend node server URL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp /etc/nginx/sites-available/whaticket-frontend /etc/nginx/sites-available/whaticket-backend
|
||||||
|
sudo nano /etc/nginx/sites-available/whaticket-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
server {
|
||||||
|
server_name api.mydomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
......
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a symbolic links to enalbe nginx sites:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/whaticket-frontend /etc/nginx/sites-enabled
|
||||||
|
sudo ln -s /etc/nginx/sites-available/whaticket-backend /etc/nginx/sites-enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, nginx limit body size to 1MB, what isn't enough to some media uploads. Lets change it to 20MB adding a new line to config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/nginx.conf
|
||||||
|
...
|
||||||
|
http {
|
||||||
|
...
|
||||||
|
client_max_body_size 20M; # HANDLE BIGGER UPLOADS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test nginx configuration and restart server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t
|
||||||
|
sudo service nginx restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, enable SSL (https) on your sites to use all app features like notifications and sending audio messages. A easy way to this is using Certbot:
|
||||||
|
|
||||||
|
Install certbot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo add-apt-repository ppa:certbot/certbot
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python-certbot-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable SSL on nginx (Fill / Accept all information asked):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo certbot --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access Data
|
||||||
|
|
||||||
|
User: admin@whaticket.com
|
||||||
|
Password: admin
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
WhaTicket is a working in progress and we are adding new features frequently. To update your old installation and get all the new features, you can use a bash script like this:
|
||||||
|
|
||||||
|
**Note**: Always check the .env.example and adjust your .env file before upgrading, since some new variable may be added.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano updateWhaticket
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Updating Whaticket, please wait."
|
||||||
|
|
||||||
|
cd ~
|
||||||
|
cd whaticket
|
||||||
|
git pull
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
rm -rf dist
|
||||||
|
npm run build
|
||||||
|
npx sequelize db:migrate
|
||||||
|
npx sequelize db:seed
|
||||||
|
cd ../frontend
|
||||||
|
npm install
|
||||||
|
rm -rf build
|
||||||
|
npm run build
|
||||||
|
pm2 restart all
|
||||||
|
|
||||||
|
echo "Update finished. Enjoy!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Make it executable and run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x updateWhaticket
|
||||||
|
./updateWhaticket
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This project helps you and you want to help keep it going? Buy me a coffee:
|
||||||
|
|
||||||
|
<a href="https://www.buymeacoffee.com/canove" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 61px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||||
|
|
||||||
|
Para doações em BRL, utilize o Paypal:
|
||||||
|
|
||||||
|
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?business=VWW3BHW4AWHUY&item_name=Desenvolvimento+de+Software¤cy_code=BRL)
|
||||||
|
|
||||||
|
Any help and suggestions will be apreciated.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
I just started leaning Javascript a few months ago and this is my first project. It may have security issues and many bugs. I recommend using it only on local network.
|
||||||
|
|
||||||
|
This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners.
|
Binary file not shown.
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
|
@ -0,0 +1,32 @@
|
||||||
|
NODE_ENV=
|
||||||
|
BACKEND_URL=http://172.31.187.27
|
||||||
|
FRONTEND_URL=http://172.31.187.27:3331
|
||||||
|
PROXY_HOST=
|
||||||
|
PROXY_PORT=8081
|
||||||
|
PORT=8081
|
||||||
|
|
||||||
|
DB_DIALECT=mysql
|
||||||
|
DB_HOST=172.31.187.7
|
||||||
|
DB_USER=hit
|
||||||
|
DB_PASS=7901228899
|
||||||
|
DB_NAME=omnihit_db_test
|
||||||
|
|
||||||
|
VERIFY_TOKEN=OhsmitFDWNSdlXCC!Bw=sp-x3uTNlQBIFVTg?B-KtW221RqNTLPhUvNU6ZzqE?mc
|
||||||
|
TOKEN=EAAEPZBB2YqgwBOZBEAvPxYaO2nbPvuzU3ZBaZA6YF6tyWtjKom2yLxPxOm421njhbb1ZC2rOkyQyZCWpZBk9jXZCAaMLNY6SkNOrwPoRNaqO9Fbj31mZC8mxra08jIhBiziX7IZBFDWYbkcfw5cfLdTSys9ilfRlKsIZClOUlTiHnhSDkMvXY6rMFrvWswR2YVvJVH1qPvM7hGuuUqM
|
||||||
|
VERSION=v17.0
|
||||||
|
URL_WHATSAPP_MEDIA=https://hit-api.omnihit.app.br/whatsapp/official/media
|
||||||
|
URL_WHATSAPP_API=https://graph.facebook.com
|
||||||
|
|
||||||
|
WHATS_NUMBER_VALIDATOR_URL=http://172.31.187.24:8020
|
||||||
|
|
||||||
|
JWT_SECRET=3123123213123
|
||||||
|
JWT_REFRESH_SECRET=75756756756
|
||||||
|
|
||||||
|
CACHE=
|
||||||
|
|
||||||
|
REDIS_URI=redis://127.0.0.1:6379
|
||||||
|
|
||||||
|
APP_NAME=omnihit_test
|
||||||
|
BACKEND_URL_RAW=http://172.31.187.27
|
||||||
|
|
||||||
|
TOKEN_DASHBOARD_SESSIONS=8168dd72adb7bab7e8f54f9d022468ab
|
|
@ -0,0 +1,3 @@
|
||||||
|
/*.js
|
||||||
|
node_modules
|
||||||
|
dist
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es2021": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"airbnb-base",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier/@typescript-eslint",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 12,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ "argsIgnorePattern": "_" }
|
||||||
|
],
|
||||||
|
"import/prefer-default-export": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-param-reassign": "off",
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"ts": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
1,
|
||||||
|
"double",
|
||||||
|
{
|
||||||
|
"avoidEscape": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
node_modules
|
||||||
|
public/*
|
||||||
|
dist
|
||||||
|
!public/.gitkeep
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
/src/config/sentry.js
|
||||||
|
|
||||||
|
# Ignore test-related files
|
||||||
|
/coverage.data
|
||||||
|
/coverage/
|
|
@ -0,0 +1,8 @@
|
||||||
|
const { resolve } = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"config": resolve(__dirname, "dist", "config", "database.js"),
|
||||||
|
"modules-path": resolve(__dirname, "dist", "models"),
|
||||||
|
"migrations-path": resolve(__dirname, "dist", "database", "migrations"),
|
||||||
|
"seeders-path": resolve(__dirname, "dist", "database", "seeds")
|
||||||
|
};
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
* For a detailed explanation regarding each configuration property, visit:
|
||||||
|
* https://jestjs.io/docs/en/configuration.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// All imported modules in your tests should be mocked automatically
|
||||||
|
// automock: false,
|
||||||
|
|
||||||
|
// Stop running tests after `n` failures
|
||||||
|
bail: 1,
|
||||||
|
|
||||||
|
// The directory where Jest should store its cached dependency information
|
||||||
|
// cacheDirectory: "/tmp/jest_rs",
|
||||||
|
|
||||||
|
// Automatically clear mock calls and instances between every test
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
collectCoverage: true,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||||
|
collectCoverageFrom: ["<rootDir>/src/services/**/*.ts"],
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
coverageDirectory: "coverage",
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
// coveragePathIgnorePatterns: [
|
||||||
|
// "/node_modules/"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Indicates which provider should be used to instrument code for coverage
|
||||||
|
coverageProvider: "v8",
|
||||||
|
|
||||||
|
// A list of reporter names that Jest uses when writing coverage reports
|
||||||
|
coverageReporters: ["text", "lcov"],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
// coverageThreshold: undefined,
|
||||||
|
|
||||||
|
// A path to a custom dependency extractor
|
||||||
|
// dependencyExtractor: undefined,
|
||||||
|
|
||||||
|
// Make calling deprecated APIs throw helpful error messages
|
||||||
|
// errorOnDeprecated: false,
|
||||||
|
|
||||||
|
// Force coverage collection from ignored files using an array of glob patterns
|
||||||
|
// forceCoverageMatch: [],
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once before all test suites
|
||||||
|
// globalSetup: undefined,
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once after all test suites
|
||||||
|
// globalTeardown: undefined,
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
// globals: {},
|
||||||
|
|
||||||
|
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||||
|
// maxWorkers: "50%",
|
||||||
|
|
||||||
|
// An array of directory names to be searched recursively up from the requiring module's location
|
||||||
|
// moduleDirectories: [
|
||||||
|
// "node_modules"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
// moduleFileExtensions: [
|
||||||
|
// "js",
|
||||||
|
// "json",
|
||||||
|
// "jsx",
|
||||||
|
// "ts",
|
||||||
|
// "tsx",
|
||||||
|
// "node"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||||
|
// moduleNameMapper: {},
|
||||||
|
|
||||||
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
// modulePathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Activates notifications for test results
|
||||||
|
// notify: false,
|
||||||
|
|
||||||
|
// An enum that specifies notification mode. Requires { notify: true }
|
||||||
|
// notifyMode: "failure-change",
|
||||||
|
|
||||||
|
// A preset that is used as a base for Jest's configuration
|
||||||
|
preset: "ts-jest",
|
||||||
|
|
||||||
|
// Run tests from one or more projects
|
||||||
|
// projects: undefined,
|
||||||
|
|
||||||
|
// Use this configuration option to add custom reporters to Jest
|
||||||
|
// reporters: undefined,
|
||||||
|
|
||||||
|
// Automatically reset mock state between every test
|
||||||
|
// resetMocks: false,
|
||||||
|
|
||||||
|
// Reset the module registry before running each individual test
|
||||||
|
// resetModules: false,
|
||||||
|
|
||||||
|
// A path to a custom resolver
|
||||||
|
// resolver: undefined,
|
||||||
|
|
||||||
|
// Automatically restore mock state between every test
|
||||||
|
// restoreMocks: false,
|
||||||
|
|
||||||
|
// The root directory that Jest should scan for tests and modules within
|
||||||
|
// rootDir: undefined,
|
||||||
|
|
||||||
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
|
// roots: [
|
||||||
|
// "<rootDir>"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
|
// runner: "jest-runner",
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||||
|
// setupFiles: [],
|
||||||
|
|
||||||
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
|
// setupFilesAfterEnv: [],
|
||||||
|
|
||||||
|
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||||
|
// slowTestThreshold: 5,
|
||||||
|
|
||||||
|
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||||
|
// snapshotSerializers: [],
|
||||||
|
|
||||||
|
// The test environment that will be used for testing
|
||||||
|
testEnvironment: "node",
|
||||||
|
|
||||||
|
// Options that will be passed to the testEnvironment
|
||||||
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
// Adds a location field to test results
|
||||||
|
// testLocationInResults: false,
|
||||||
|
|
||||||
|
// The glob patterns Jest uses to detect test files
|
||||||
|
testMatch: ["**/__tests__/**/*.spec.ts"]
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
// testPathIgnorePatterns: [
|
||||||
|
// "/node_modules/"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||||
|
// testRegex: [],
|
||||||
|
|
||||||
|
// This option allows the use of a custom results processor
|
||||||
|
// testResultsProcessor: undefined,
|
||||||
|
|
||||||
|
// This option allows use of a custom test runner
|
||||||
|
// testRunner: "jasmine2",
|
||||||
|
|
||||||
|
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||||
|
// testURL: "http://localhost",
|
||||||
|
|
||||||
|
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||||
|
// timers: "real",
|
||||||
|
|
||||||
|
// A map from regular expressions to paths to transformers
|
||||||
|
// transform: undefined,
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
// transformIgnorePatterns: [
|
||||||
|
// "/node_modules/",
|
||||||
|
// "\\.pnp\\.[^\\/]+$"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
|
||||||
|
// Indicates whether each individual test should be reported during the run
|
||||||
|
// verbose: undefined,
|
||||||
|
|
||||||
|
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||||
|
// watchPathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Whether to use watchman for file crawling
|
||||||
|
// watchman: true,
|
||||||
|
};
|
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w",
|
||||||
|
"start": "nodemon --expose-gc dist/server.js",
|
||||||
|
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
|
||||||
|
"dev": "nodemon --exec npm run dev:server",
|
||||||
|
"pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
|
||||||
|
"test": "NODE_ENV=test jest",
|
||||||
|
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/node": "^5.29.2",
|
||||||
|
"@socket.io/redis-adapter": "^7.2.0",
|
||||||
|
"@types/fluent-ffmpeg": "^2.1.21",
|
||||||
|
"@types/pino": "^6.3.4",
|
||||||
|
"axios": "^1.2.3",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"cookie-parser": "^1.4.5",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"date-fns-tz": "^1.3.8",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"eiows": "^7.0.3",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"express-async-errors": "^3.1.1",
|
||||||
|
"fast-folder-size": "^1.7.0",
|
||||||
|
"flat": "^5.0.2",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"fs-extra": "^10.1.0",
|
||||||
|
"http-graceful-shutdown": "^2.3.2",
|
||||||
|
"ioredis": "^5.2.3",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"multer": "^1.4.2",
|
||||||
|
"mysql2": "^2.2.5",
|
||||||
|
"pg": "^8.4.1",
|
||||||
|
"pino": "^6.9.0",
|
||||||
|
"pino-pretty": "^9.1.1",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
|
"redis": "^4.6.13",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"sequelize": "^5.22.3",
|
||||||
|
"sequelize-cli": "^5.5.1",
|
||||||
|
"sequelize-typescript": "^1.1.0",
|
||||||
|
"sharp": "^0.32.5",
|
||||||
|
"socket.io": "^4.7.5",
|
||||||
|
"socket.io-client": "^4.5.4",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"whatsapp-web.js": "github:pedroslopez/whatsapp-web.js",
|
||||||
|
"yup": "^0.32.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bcryptjs": "^2.4.2",
|
||||||
|
"@types/bluebird": "^3.5.32",
|
||||||
|
"@types/cookie-parser": "^1.4.2",
|
||||||
|
"@types/cors": "^2.8.7",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/factory-girl": "^5.0.2",
|
||||||
|
"@types/faker": "^5.1.3",
|
||||||
|
"@types/jest": "^26.0.15",
|
||||||
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
|
"@types/multer": "^1.4.4",
|
||||||
|
"@types/node": "^14.18.63",
|
||||||
|
"@types/supertest": "^2.0.10",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
|
"@types/validator": "^13.1.0",
|
||||||
|
"@types/yup": "^0.29.8",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.4.0",
|
||||||
|
"@typescript-eslint/parser": "^4.4.0",
|
||||||
|
"eslint": "^7.10.0",
|
||||||
|
"eslint-config-airbnb-base": "^14.2.0",
|
||||||
|
"eslint-config-prettier": "^6.12.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.3.0",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
"factory-girl": "^5.0.4",
|
||||||
|
"faker": "^5.1.0",
|
||||||
|
"jest": "^26.6.0",
|
||||||
|
"nodemon": "^2.0.4",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"supertest": "^5.0.0",
|
||||||
|
"ts-jest": "^26.4.1",
|
||||||
|
"ts-node-dev": "^1.0.0-pre.63",
|
||||||
|
"typescript": "4.1.6"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "none",
|
||||||
|
arrowParens: "avoid"
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
declare namespace Express {
|
||||||
|
export interface Request {
|
||||||
|
user: { id: string; profile: string };
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
declare module "qrcode-terminal";
|
|
@ -0,0 +1,69 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import AppError from "../../../errors/AppError";
|
||||||
|
import AuthUserService from "../../../services/UserServices/AuthUserService";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("Auth", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to login with an existing user", async () => {
|
||||||
|
const password = faker.internet.password();
|
||||||
|
const email = faker.internet.email();
|
||||||
|
|
||||||
|
await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await AuthUserService({
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response).toHaveProperty("token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to login with not registered email", async () => {
|
||||||
|
try {
|
||||||
|
await AuthUserService({
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toBeInstanceOf(AppError);
|
||||||
|
expect(err.statusCode).toBe(401);
|
||||||
|
expect(err.message).toBe("ERR_INVALID_CREDENTIALS");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to login with incorret password", async () => {
|
||||||
|
await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: "mail@test.com",
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await AuthUserService({
|
||||||
|
email: "mail@test.com",
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toBeInstanceOf(AppError);
|
||||||
|
expect(err.statusCode).toBe(401);
|
||||||
|
expect(err.message).toBe("ERR_INVALID_CREDENTIALS");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import AppError from "../../../errors/AppError";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to create a new user", async () => {
|
||||||
|
const user = await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(user).toHaveProperty("id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to create a user with duplicated email", async () => {
|
||||||
|
await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: "teste@sameemail.com",
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: "teste@sameemail.com",
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toBeInstanceOf(AppError);
|
||||||
|
expect(err.statusCode).toBe(400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import AppError from "../../../errors/AppError";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import DeleteUserService from "../../../services/UserServices/DeleteUserService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be delete a existing user", async () => {
|
||||||
|
const { id } = await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(DeleteUserService(id)).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("to throw an error if tries to delete a non existing user", async () => {
|
||||||
|
expect(DeleteUserService(faker.random.number())).rejects.toBeInstanceOf(
|
||||||
|
AppError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import User from "../../../models/User";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import ListUsersService from "../../../services/UserServices/ListUsersService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to list users", async () => {
|
||||||
|
await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await ListUsersService({
|
||||||
|
pageNumber: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response).toHaveProperty("users");
|
||||||
|
expect(response.users[0]).toBeInstanceOf(User);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import AppError from "../../../errors/AppError";
|
||||||
|
import User from "../../../models/User";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import ShowUserService from "../../../services/UserServices/ShowUserService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to find a user", async () => {
|
||||||
|
const newUser = await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await ShowUserService(newUser.id);
|
||||||
|
|
||||||
|
expect(user).toHaveProperty("id");
|
||||||
|
expect(user).toBeInstanceOf(User);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to find a inexisting user", async () => {
|
||||||
|
expect(ShowUserService(faker.random.number())).rejects.toBeInstanceOf(
|
||||||
|
AppError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import faker from "faker";
|
||||||
|
import AppError from "../../../errors/AppError";
|
||||||
|
import CreateUserService from "../../../services/UserServices/CreateUserService";
|
||||||
|
import UpdateUserService from "../../../services/UserServices/UpdateUserService";
|
||||||
|
import { disconnect, truncate } from "../../utils/database";
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await truncate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to find a user", async () => {
|
||||||
|
const newUser = await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedUser = await UpdateUserService({
|
||||||
|
userId: newUser.id,
|
||||||
|
userData: {
|
||||||
|
name: "New name",
|
||||||
|
email: "newmail@email.com"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedUser).toHaveProperty("name", "New name");
|
||||||
|
expect(updatedUser).toHaveProperty("email", "newmail@email.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to updated a inexisting user", async () => {
|
||||||
|
const userId = faker.random.number();
|
||||||
|
const userData = {
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email()
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(UpdateUserService({ userId, userData })).rejects.toBeInstanceOf(
|
||||||
|
AppError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to updated an user with invalid data", async () => {
|
||||||
|
const newUser = await CreateUserService({
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
password: faker.internet.password()
|
||||||
|
});
|
||||||
|
|
||||||
|
const userId = newUser.id;
|
||||||
|
const userData = {
|
||||||
|
name: faker.name.findName(),
|
||||||
|
email: "test.worgn.email"
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(UpdateUserService({ userId, userData })).rejects.toBeInstanceOf(
|
||||||
|
AppError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
import database from "../../database";
|
||||||
|
|
||||||
|
const truncate = async (): Promise<void> => {
|
||||||
|
await database.truncate({ force: true, cascade: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = async (): Promise<void> => {
|
||||||
|
return database.connectionManager.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { truncate, disconnect };
|
|
@ -0,0 +1,46 @@
|
||||||
|
import "./bootstrap";
|
||||||
|
import "reflect-metadata";
|
||||||
|
import "express-async-errors";
|
||||||
|
import express, { Request, Response, NextFunction } from "express";
|
||||||
|
import cors from "cors";
|
||||||
|
import cookieParser from "cookie-parser";
|
||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
|
||||||
|
import "./database";
|
||||||
|
import uploadConfig from "./config/upload";
|
||||||
|
import AppError from "./errors/AppError";
|
||||||
|
import routes from "./routes";
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
|
Sentry.init({ dsn: process.env.SENTRY_DSN });
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
cors({
|
||||||
|
credentials: true,
|
||||||
|
origin: process.env.FRONTEND_URL
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(Sentry.Handlers.requestHandler());
|
||||||
|
app.use("/public", express.static(uploadConfig.directory));
|
||||||
|
// app.use('/api', routes);
|
||||||
|
app.use(routes);
|
||||||
|
|
||||||
|
app.use(Sentry.Handlers.errorHandler());
|
||||||
|
|
||||||
|
app.use(async (err: Error, req: Request, res: Response, _: NextFunction) => {
|
||||||
|
if (err instanceof AppError) {
|
||||||
|
logger.warn(err);
|
||||||
|
return res.status(err.statusCode).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(err);
|
||||||
|
return res.status(500).json({ error: "Internal server error" });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default app;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
secret: process.env.JWT_SECRET || "mysecret",
|
||||||
|
expiresIn: "15m",
|
||||||
|
refreshSecret: process.env.JWT_REFRESH_SECRET || "myanothersecret",
|
||||||
|
refreshExpiresIn: "7d"
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
require("../bootstrap");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
define: {
|
||||||
|
charset: "utf8mb4",
|
||||||
|
collate: "utf8mb4_bin"
|
||||||
|
},
|
||||||
|
dialect: process.env.DB_DIALECT || "mysql",
|
||||||
|
timezone: "-03:00",
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASS,
|
||||||
|
logging: false
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import path from "path";
|
||||||
|
import multer from "multer";
|
||||||
|
|
||||||
|
// const publicFolder = path.resolve(__dirname, "..", "..", "public");
|
||||||
|
const publicFolder = path.resolve(__dirname, "..", "..","..","..", "public");
|
||||||
|
|
||||||
|
console.log('publicFolder: ',publicFolder)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
directory: publicFolder,
|
||||||
|
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: publicFolder,
|
||||||
|
filename(req, file, cb) {
|
||||||
|
const fileName = new Date().getTime() + path.extname(file.originalname);
|
||||||
|
|
||||||
|
console.log('THE FILE NAME FROM MULTER: ',fileName)
|
||||||
|
|
||||||
|
return cb(null, fileName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
|
@ -0,0 +1,267 @@
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import ListContactsService from "../services/ContactServices/ListContactsService";
|
||||||
|
import CreateContactService from "../services/ContactServices/CreateContactService";
|
||||||
|
import ShowContactService from "../services/ContactServices/ShowContactService";
|
||||||
|
import UpdateContactService from "../services/ContactServices/UpdateContactService";
|
||||||
|
import DeleteContactService from "../services/ContactServices/DeleteContactService";
|
||||||
|
|
||||||
|
import CheckContactNumber from "../services/WbotServices/CheckNumber";
|
||||||
|
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
|
||||||
|
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
import {
|
||||||
|
searchContactCache,
|
||||||
|
insertContactsCache,
|
||||||
|
escapeCharCache
|
||||||
|
} from "../helpers/ContactsCache";
|
||||||
|
|
||||||
|
import { off } from "process";
|
||||||
|
import GetContactService from "../services/ContactServices/GetContactService";
|
||||||
|
import ContactQueue from "../models/ContactQueues";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
userId?:string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IndexGetContactQuery = {
|
||||||
|
name: string;
|
||||||
|
number: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ExtraInfo {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
interface ContactData {
|
||||||
|
name: string;
|
||||||
|
number: string;
|
||||||
|
email?: string;
|
||||||
|
extraInfo?: ExtraInfo[];
|
||||||
|
queueIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
let { searchParam, pageNumber, userId } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
console.log("PAGE NUMBER CONTACT: ", pageNumber);
|
||||||
|
|
||||||
|
if (pageNumber === undefined || pageNumber.trim().length == 0) {
|
||||||
|
pageNumber = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST DEL
|
||||||
|
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
|
||||||
|
try {
|
||||||
|
const offset = 20 * (+pageNumber - 1);
|
||||||
|
|
||||||
|
searchParam = searchParam.replace(/\s+/g, " ").trim().toLowerCase();
|
||||||
|
|
||||||
|
const data = await searchContactCache(searchParam, offset, 20);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
console.log("QUERY CONTACTS FROM CACHE SEARCH PARAM: ", searchParam);
|
||||||
|
|
||||||
|
console.log("QUERY CONTACTS FROM CACHE QUERY LENGTH: ", data.length);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
contacts: data,
|
||||||
|
count: data.length,
|
||||||
|
hasMore: data.length > 0 ? true : false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"There was an error on search ContactController.ts search cache: ",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contacts, count, hasMore } = await ListContactsService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ contacts, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getContact = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { name, number } = req.body as IndexGetContactQuery;
|
||||||
|
|
||||||
|
const contact = await GetContactService({
|
||||||
|
name,
|
||||||
|
number
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(contact);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const newContact: ContactData = req.body;
|
||||||
|
newContact.number = newContact.number.replace("-", "").replace(" ", "");
|
||||||
|
|
||||||
|
const schema = Yup.object().shape({
|
||||||
|
name: Yup.string().required(),
|
||||||
|
number: Yup.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
|
||||||
|
// .matches(/^55\d+$/, "The number must start with 55.")
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await schema.validate(newContact);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
newContact.number = addStartPhoneNumber(newContact.number);
|
||||||
|
|
||||||
|
const validNumber = await CheckIsValidContact(newContact.number);
|
||||||
|
|
||||||
|
if (!validNumber) {
|
||||||
|
throw new AppError("ERR_WAPP_CHECK_CONTACT");
|
||||||
|
}
|
||||||
|
|
||||||
|
const profilePicUrl = await GetProfilePicUrl(validNumber);
|
||||||
|
|
||||||
|
let name = newContact.name;
|
||||||
|
let number = validNumber;
|
||||||
|
let email = newContact.email;
|
||||||
|
let extraInfo = newContact.extraInfo;
|
||||||
|
|
||||||
|
const contact = await CreateContactService({
|
||||||
|
name,
|
||||||
|
number,
|
||||||
|
email,
|
||||||
|
profilePicUrl: profilePicUrl,
|
||||||
|
extraInfo,
|
||||||
|
queueIds: newContact?.queueIds
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contact", {
|
||||||
|
action: "create",
|
||||||
|
contact
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(contact);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { contactId } = req.params;
|
||||||
|
|
||||||
|
const contact = await ShowContactService(contactId);
|
||||||
|
|
||||||
|
return res.status(200).json(contact);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const contactData: ContactData = req.body;
|
||||||
|
|
||||||
|
const schema = Yup.object().shape({
|
||||||
|
name: Yup.string(),
|
||||||
|
number: Yup.string().matches(
|
||||||
|
/^\d+$/,
|
||||||
|
"Invalid number format. Only numbers is allowed."
|
||||||
|
)
|
||||||
|
// .matches(/^55\d+$/, "The number must start with 55.")
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await schema.validate(contactData);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
contactData.number = addStartPhoneNumber(contactData.number);
|
||||||
|
|
||||||
|
await CheckIsValidContact(contactData.number);
|
||||||
|
|
||||||
|
const { contactId } = req.params;
|
||||||
|
|
||||||
|
const contact = await UpdateContactService({ contactData, contactId });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contact", {
|
||||||
|
action: "update",
|
||||||
|
contact
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(contact);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { contactId } = req.params;
|
||||||
|
|
||||||
|
await DeleteContactService(contactId);
|
||||||
|
|
||||||
|
await ContactQueue.destroy({ where: { contactId } });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contact", {
|
||||||
|
action: "delete",
|
||||||
|
contactId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Contact deleted" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const contacsBulkInsertOnQueue = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
// console.log('THE BODY: ', req.body)
|
||||||
|
|
||||||
|
const {
|
||||||
|
adminId,
|
||||||
|
identifier,
|
||||||
|
queueStatus,
|
||||||
|
file,
|
||||||
|
contacts_inserted,
|
||||||
|
campaign
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contactsBulkInsertOnQueueStatus", {
|
||||||
|
action: "update",
|
||||||
|
insertOnQueue: {
|
||||||
|
adminId: adminId,
|
||||||
|
identifier: identifier,
|
||||||
|
queueStatus: queueStatus,
|
||||||
|
file: file,
|
||||||
|
campaign
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.CACHE && contacts_inserted) {
|
||||||
|
await insertContactsCache(contacts_inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "ok" });
|
||||||
|
};
|
||||||
|
|
||||||
|
function addStartPhoneNumber(phoneNumber: string) {
|
||||||
|
const regex = /^55/;
|
||||||
|
|
||||||
|
if (!regex.test(phoneNumber)) {
|
||||||
|
phoneNumber = "55" + phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
|
@ -0,0 +1,492 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import CreateUserService from "../services/UserServices/CreateUserService";
|
||||||
|
import UpdateUserService from "../services/UserServices/UpdateUserService";
|
||||||
|
import DeleteUserService from "../services/UserServices/DeleteUserService";
|
||||||
|
import { del, get, set } from "../helpers/RedisClient";
|
||||||
|
|
||||||
|
import {
|
||||||
|
startWhoIsOnlineMonitor,
|
||||||
|
stopWhoIsOnlineMonitor
|
||||||
|
} from "../helpers/WhoIsOnlineMonitor";
|
||||||
|
|
||||||
|
import User from "../models/User";
|
||||||
|
|
||||||
|
export const createUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id, user_first_name, user_tax_id, user_email, user_title }: any =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, [
|
||||||
|
"user_id",
|
||||||
|
"user_tax_id",
|
||||||
|
"user_first_name"
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(response("1", `${invalid}`, "0", "createUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auxUser = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (auxUser) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(
|
||||||
|
response("1", `The user ${user_id} already exist`, "0", "createUser")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await CreateUserService({
|
||||||
|
email: user_tax_id || user_email,
|
||||||
|
password: "12345",
|
||||||
|
name: user_first_name,
|
||||||
|
profile: "user",
|
||||||
|
ignoreThrow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user?.error) {
|
||||||
|
return res
|
||||||
|
.status(user?.status)
|
||||||
|
.json(response("0", `${user?.msg}`, "0", "createUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user?.error) {
|
||||||
|
const _user = await User.findByPk(user.id);
|
||||||
|
_user?.update({ secondaryId: user_id });
|
||||||
|
|
||||||
|
const { id, name } = user;
|
||||||
|
await set(`user:${id}`, { id, name });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "create",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(response("1", `User ${user_id} created`, "1", "createUser"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id }: any = req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(response("1", `${invalid}`, "0", "deleteUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (_user) {
|
||||||
|
const user = await DeleteUserService(_user.id, true);
|
||||||
|
|
||||||
|
if (user?.error) {
|
||||||
|
return res
|
||||||
|
.status(user?.status)
|
||||||
|
.json(response("0", `${user?.msg}`, "0", "deleteUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user?.error) {
|
||||||
|
del(`user:${_user.id}`);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "delete",
|
||||||
|
userId: _user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await stopWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
io.emit("onlineStatus", {
|
||||||
|
action: "delete",
|
||||||
|
userOnlineTime: _user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(response("1", `User ${user_id} deleted`, "1", "deleteUser"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json(response("0", "Internal server error", "0", "deleteUser"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listAllUsers = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const _users: any = await User.findAll({
|
||||||
|
where: {
|
||||||
|
secondaryId: {
|
||||||
|
[Op.ne]: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attributes: ["secondaryId", "name"]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_users) {
|
||||||
|
const user_list = _users.map((user: any) => {
|
||||||
|
const { secondaryId, name } = user;
|
||||||
|
return { user_id: secondaryId, full_name: name };
|
||||||
|
});
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(response("1", "Success", user_list, "listAllUsers"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json(response("0", "Internal server error", [], "listAllUsers"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id }: any = req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(response("1", `${invalid}`, "0", "checkUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (_user) {
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(response("1", `User ${user_id} exist`, "1", "checkUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json(response("1", `User ${user_id} not exist`, "0", "checkUser"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id, user_first_name, user_tax_id, user_email, user_title }: any =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(response("1", `${invalid}`, "0", "checkUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user: any = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (!_user)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json(response("1", `User ${user_id} not exist`, "0", "updateUser"));
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
email: user_tax_id || user_email,
|
||||||
|
name: user_first_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
let user: any = await UpdateUserService({
|
||||||
|
userData,
|
||||||
|
userId: _user.id,
|
||||||
|
ignoreThrow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user?.error) {
|
||||||
|
return res
|
||||||
|
.status(user?.status)
|
||||||
|
.json(response("0", `${user?.msg}`, "0", "updateUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const { id, name } = user;
|
||||||
|
await set(`user:${id}`, { id, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "update",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(response("1", `User ${user_id} updated`, "1", "updateUser"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id, user_password }: any = req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id", "user_password"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(response("1", `${invalid}`, "0", "resetPassword"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (!_user) {
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json(response("1", `User ${user_id} not exist`, "0", "resetPassword"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
password: user_password,
|
||||||
|
email: _user.email
|
||||||
|
};
|
||||||
|
|
||||||
|
let user: any = await UpdateUserService({
|
||||||
|
userData,
|
||||||
|
userId: _user.id,
|
||||||
|
ignoreThrow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user?.error) {
|
||||||
|
return res
|
||||||
|
.status(user?.status)
|
||||||
|
.json(response("0", `${user?.msg}`, "0", "resetPassword"));
|
||||||
|
}
|
||||||
|
|
||||||
|
await logoutUser(_user.id);
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(
|
||||||
|
response("1", `User ${user_id} password updated`, "1", "resetPassword")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const linkUserAndUserRight = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id, user_right_id, user_right_title }: any = req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(response("1", `${invalid}`, "0", "linkUserAndUserRight"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(user_right_id &&
|
||||||
|
!["admin", "user", "supervisor"].includes(
|
||||||
|
user_right_id?.trim().toLocaleLowerCase()
|
||||||
|
)) ||
|
||||||
|
(user_right_title &&
|
||||||
|
!["admin", "user", "supervisor"].includes(
|
||||||
|
user_right_title?.trim().toLocaleLowerCase()
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(
|
||||||
|
response(
|
||||||
|
"1",
|
||||||
|
`The user profile ${
|
||||||
|
user_right_title || user_right_id
|
||||||
|
} provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`,
|
||||||
|
"0",
|
||||||
|
"linkUserAndUserRight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user: any = await User.findOne({ where: { secondaryId: user_id } });
|
||||||
|
|
||||||
|
if (!_user)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json(
|
||||||
|
response("1", `User ${user_id} not exist`, "0", "linkUserAndUserRight")
|
||||||
|
);
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
profile: user_right_title || user_right_id,
|
||||||
|
email: _user.email
|
||||||
|
};
|
||||||
|
|
||||||
|
let user: any = await UpdateUserService({
|
||||||
|
userData,
|
||||||
|
userId: _user.id,
|
||||||
|
ignoreThrow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user?.error) {
|
||||||
|
return res
|
||||||
|
.status(user?.status)
|
||||||
|
.json(response("0", `${user?.msg}`, "0", "linkUserAndUserRight"));
|
||||||
|
}
|
||||||
|
|
||||||
|
await logoutUser(_user.id);
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(
|
||||||
|
response(
|
||||||
|
"1",
|
||||||
|
`User ${user_id} associated with ${
|
||||||
|
user_right_title || user_right_id
|
||||||
|
} profile`,
|
||||||
|
"1",
|
||||||
|
"linkUserAndUserRight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkUserRight = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { user_id, user_right_id, user_right_title }: any = req.body;
|
||||||
|
|
||||||
|
const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(response("1", `${invalid}`, "0", "checkUserRight"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(user_right_id &&
|
||||||
|
!["admin", "user", "supervisor"].includes(
|
||||||
|
user_right_id?.trim().toLocaleLowerCase()
|
||||||
|
)) ||
|
||||||
|
(user_right_title &&
|
||||||
|
!["admin", "user", "supervisor"].includes(
|
||||||
|
user_right_title?.trim().toLocaleLowerCase()
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(
|
||||||
|
response(
|
||||||
|
"1",
|
||||||
|
`The user profile ${
|
||||||
|
user_right_title || user_right_id
|
||||||
|
} provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`,
|
||||||
|
"0",
|
||||||
|
"checkUserRight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _user: any = await User.findOne({
|
||||||
|
where: {
|
||||||
|
secondaryId: user_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_user)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json(response("1", `User ${user_id} not exist`, "0", "checkUserRight"));
|
||||||
|
|
||||||
|
if (
|
||||||
|
(user_right_id && _user.profile != user_right_id) ||
|
||||||
|
(user_right_title && _user.profile != user_right_title)
|
||||||
|
) {
|
||||||
|
return res
|
||||||
|
.status(403)
|
||||||
|
.json(
|
||||||
|
response(
|
||||||
|
"1",
|
||||||
|
`User ${user_id} does not have this profile`,
|
||||||
|
"0",
|
||||||
|
"checkUserRight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json(
|
||||||
|
response(
|
||||||
|
"1",
|
||||||
|
`User ${user_id} has ${user_right_title || user_right_id} profile`,
|
||||||
|
"1",
|
||||||
|
"checkUserRight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function logoutUser(userId: any) {
|
||||||
|
await stopWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
let onlineTime = {
|
||||||
|
userId: `${userId}`,
|
||||||
|
status: "logout..."
|
||||||
|
};
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("onlineStatus", {
|
||||||
|
action: "logout",
|
||||||
|
userOnlineTime: onlineTime
|
||||||
|
});
|
||||||
|
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function response(code: string, msg: string, obj: any, type: string) {
|
||||||
|
let payload = { return_code: code, return_msg: msg };
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "createUser":
|
||||||
|
return { ...payload, user_created: obj };
|
||||||
|
case "deleteUser":
|
||||||
|
return { ...payload, user_removed: obj };
|
||||||
|
case "listAllUsers":
|
||||||
|
return { ...payload, user_list: obj };
|
||||||
|
case "checkUser":
|
||||||
|
return { ...payload, user_exists: obj };
|
||||||
|
case "updateUser":
|
||||||
|
return { ...payload, user_updated: obj };
|
||||||
|
case "resetPassword":
|
||||||
|
return { ...payload, password_set: obj };
|
||||||
|
case "linkUserAndUserRight":
|
||||||
|
return { ...payload, user_right_linked: obj };
|
||||||
|
case "checkUserRight":
|
||||||
|
return { ...payload, user_right_exists: obj };
|
||||||
|
default:
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidProperties(body: any, pros: any[]) {
|
||||||
|
for (const field of pros) {
|
||||||
|
console.log("body[field]: ", body[field], " field: ", field);
|
||||||
|
if (!body[field]) {
|
||||||
|
return `${field} is required`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import ImportContactsService from "../services/WbotServices/ImportContactsService";
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
await ImportContactsService();
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "contacts imported" });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
|
||||||
|
|
||||||
|
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import Message from "../models/Message";
|
||||||
|
|
||||||
|
import ListMessagesService from "../services/MessageServices/ListMessagesService";
|
||||||
|
import ShowTicketService from "../services/TicketServices/ShowTicketService";
|
||||||
|
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
|
||||||
|
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
|
||||||
|
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
|
||||||
|
import axios from "axios";
|
||||||
|
import Contact from "../models/Contact";
|
||||||
|
import {
|
||||||
|
isValidMsg,
|
||||||
|
verifyMessage
|
||||||
|
} from "../services/WbotServices/wbotMessageListener";
|
||||||
|
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
|
||||||
|
import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import { get } from "../helpers/RedisClient";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MessageData = {
|
||||||
|
body: string;
|
||||||
|
fromMe: boolean;
|
||||||
|
read: boolean;
|
||||||
|
quotedMsg?: Message;
|
||||||
|
mic_audio?: boolean;
|
||||||
|
params: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
const { pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { count, messages, ticket, hasMore } = await ListMessagesService({
|
||||||
|
pageNumber,
|
||||||
|
ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ count, messages, ticket, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
const { body, quotedMsg, mic_audio, params }: MessageData = req.body;
|
||||||
|
const medias = req.files as Express.Multer.File[];
|
||||||
|
const ticket = await ShowTicketService(ticketId);
|
||||||
|
|
||||||
|
const { queueId } = ticket;
|
||||||
|
console.log(
|
||||||
|
"-----------> queueId: ",
|
||||||
|
queueId,
|
||||||
|
" | quotedMsg: ",
|
||||||
|
quotedMsg,
|
||||||
|
" | params: ",
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const { phoneNumberId, whatsappId } = ticket;
|
||||||
|
|
||||||
|
if (phoneNumberId) {
|
||||||
|
const into24hs = await checkLastClientMsg24hs(ticket);
|
||||||
|
|
||||||
|
if (into24hs && into24hs.length == 0) {
|
||||||
|
if (params) {
|
||||||
|
console.log("SEND TEMPLATE PARAMS: ", params);
|
||||||
|
|
||||||
|
// return res.send()
|
||||||
|
|
||||||
|
let payloadComponents = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i in params) {
|
||||||
|
const { parameters, language, type } = params[i];
|
||||||
|
if (type == "BODY") {
|
||||||
|
if (parameters && parameters.length > 0) {
|
||||||
|
let components: any = [{ type: "body", parameters: [] }];
|
||||||
|
for (let x in parameters) {
|
||||||
|
const { type, text, index } = parameters[x];
|
||||||
|
console.log(text);
|
||||||
|
components[0].parameters.splice(index - 1, 0, {
|
||||||
|
type,
|
||||||
|
text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
payloadComponents.push(components[0]);
|
||||||
|
}
|
||||||
|
} else if (type == "BUTTONS") {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = params.find((p: any) => p?.template_name);
|
||||||
|
const { language }: any =
|
||||||
|
params?.find((p: any) => p?.language) || "pt_BR";
|
||||||
|
|
||||||
|
const { template_name } = name;
|
||||||
|
|
||||||
|
if (template_name && language) {
|
||||||
|
const template: any = {
|
||||||
|
template: {
|
||||||
|
name: template_name,
|
||||||
|
language: { code: language },
|
||||||
|
components: payloadComponents
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("TEMPLATE: ", template);
|
||||||
|
|
||||||
|
sendWhatsAppMessageOfficialAPI(ticket, body, null, template);
|
||||||
|
|
||||||
|
return res.send();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new AppError(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const { wabaId }: any = await Whatsapp.findByPk(whatsappId);
|
||||||
|
|
||||||
|
const { data } = await whatsappOfficialAPI.get(
|
||||||
|
`/${process.env.VERSION}/${wabaId}/message_templates?language=pt_BR`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json(data);
|
||||||
|
} catch (error) {
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ message: "Não foi possível baixar os templates!" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (medias) {
|
||||||
|
await Promise.all(
|
||||||
|
medias.map(async (media: Express.Multer.File) => {
|
||||||
|
console.log(
|
||||||
|
`\n >>>>>>>>>> SENDING MESSAGE MEDIA
|
||||||
|
Parcial ticket info and media:
|
||||||
|
ticket.id: ${ticket.id}
|
||||||
|
ticket.status: ${ticket.status}
|
||||||
|
ticket.whatsapp.id: ${ticket.whatsappId}
|
||||||
|
ticket.contact.number: ${ticket.contact.number}
|
||||||
|
ticket.contact.name: ${ticket.contact.name}
|
||||||
|
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
||||||
|
ticket.user.id: ${ticket.user.id}
|
||||||
|
ticket.user.name: ${ticket.user.name}
|
||||||
|
media:`,
|
||||||
|
media,
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
await SendWhatsAppMedia({ media, ticket, mic_audio });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`\n >>>>>>>>>> SENDING MESSAGE
|
||||||
|
Parcial ticket info:
|
||||||
|
ticket.id: ${ticket.id}
|
||||||
|
ticket.status: ${ticket.status}
|
||||||
|
ticket.whatsapp.id: ${ticket.whatsappId}
|
||||||
|
ticket.contact.number: ${ticket.contact.number}
|
||||||
|
message: ${body}
|
||||||
|
ticket.contact.name: ${ticket.contact.name}
|
||||||
|
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
||||||
|
ticket.user.id: ${ticket.user.id}
|
||||||
|
ticket.user.name: ${ticket.user.name}\n`);
|
||||||
|
|
||||||
|
await SendWhatsAppMessage({ body, ticket, quotedMsg });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { messageId } = req.params;
|
||||||
|
|
||||||
|
const message = await DeleteWhatsAppMessage(messageId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to(message.ticketId.toString()).emit("appMessage", {
|
||||||
|
action: "update",
|
||||||
|
message
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.send();
|
||||||
|
};
|
|
@ -0,0 +1,113 @@
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import ListPositionService from "../services/PositionService/ListPositionService";
|
||||||
|
import ShowPositionService from "../services/PositionService/ShowPositionService";
|
||||||
|
import CreatePositionService from "../services/PositionService/CreatePositionService";
|
||||||
|
import UpdatePositionService from "../services/PositionService/UpdatePositionService";
|
||||||
|
import DeletePositionService from "../services/PositionService/DeletePositionService";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PositionData {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { searchParam, pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { positions, count, hasMore } = await ListPositionService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ positions, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const newPosition: PositionData = req.body;
|
||||||
|
|
||||||
|
const PositionSchema = Yup.object().shape({
|
||||||
|
name: Yup.string().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await PositionSchema.validate(newPosition);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = await CreatePositionService({
|
||||||
|
...newPosition
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("position", {
|
||||||
|
action: "create",
|
||||||
|
position
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { positionId } = req.params;
|
||||||
|
|
||||||
|
const position = await ShowPositionService(positionId);
|
||||||
|
|
||||||
|
return res.status(200).json(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const positionData: PositionData = req.body;
|
||||||
|
|
||||||
|
const schema = Yup.object().shape({
|
||||||
|
name: Yup.string().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await schema.validate(positionData);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { positionId } = req.params;
|
||||||
|
|
||||||
|
const position = await UpdatePositionService({
|
||||||
|
positionData,
|
||||||
|
positionId
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("position", {
|
||||||
|
action: "update",
|
||||||
|
position
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { positionId } = req.params;
|
||||||
|
|
||||||
|
await DeletePositionService(positionId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("position", {
|
||||||
|
action: "delete",
|
||||||
|
positionId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Position deleted" });
|
||||||
|
};
|
|
@ -0,0 +1,240 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import CreateQueueService from "../services/QueueService/CreateQueueService";
|
||||||
|
import DeleteQueueService from "../services/QueueService/DeleteQueueService";
|
||||||
|
import ListQueuesService from "../services/QueueService/ListQueuesService";
|
||||||
|
import ShowQueueService from "../services/QueueService/ShowQueueService";
|
||||||
|
import UpdateQueueService from "../services/QueueService/UpdateQueueService";
|
||||||
|
import Queue from "../models/Queue";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import { del, get, set } from "../helpers/RedisClient";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import QuickAnswerQueue from "../models/QuickAnswerQueue";
|
||||||
|
import ContactQueue from "../models/ContactQueues";
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const queues = await ListQueuesService();
|
||||||
|
|
||||||
|
return res.status(200).json(queues);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listQueues = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const whatsapps = await Whatsapp.findAll({
|
||||||
|
where: {
|
||||||
|
name: { [Op.ne]: "botqueue" },
|
||||||
|
number: { [Op.ne]: "" },
|
||||||
|
phoneNumberId: false
|
||||||
|
},
|
||||||
|
attributes: ["number"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Queue,
|
||||||
|
as: "queues",
|
||||||
|
attributes: ["id", "name"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const whats = whatsapps
|
||||||
|
?.filter((w: any) => w?.queues?.length > 0)
|
||||||
|
?.map((w: any) => {
|
||||||
|
const { number, queues } = w;
|
||||||
|
return {
|
||||||
|
number,
|
||||||
|
queues: queues?.map((q: any) => {
|
||||||
|
const { id, name } = q;
|
||||||
|
return { id, name };
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let _queues: any = [];
|
||||||
|
|
||||||
|
for (const w of whats) {
|
||||||
|
const { queues } = w;
|
||||||
|
|
||||||
|
for (const q of queues) {
|
||||||
|
const { id: queueId, name } = q;
|
||||||
|
|
||||||
|
const auxQueue = _queues.findIndex((q: any) => q.queueId == queueId);
|
||||||
|
|
||||||
|
if (auxQueue == -1) {
|
||||||
|
_queues.push({ queueId, name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(_queues);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { name, color, greetingMessage, farewellMessage, cc } = req.body;
|
||||||
|
|
||||||
|
const queue = await CreateQueueService({
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
greetingMessage,
|
||||||
|
cc,
|
||||||
|
farewellMessage
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("queue", {
|
||||||
|
action: "update",
|
||||||
|
queue
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(queue);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customization = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { ura } = req.body;
|
||||||
|
|
||||||
|
if (!ura) throw new AppError("BAD REQUEST", 400);
|
||||||
|
|
||||||
|
let new_queues: any;
|
||||||
|
|
||||||
|
if (ura.length > 0) {
|
||||||
|
new_queues = ura
|
||||||
|
.filter(
|
||||||
|
(u: any) =>
|
||||||
|
u.idmaster === ura[1].id &&
|
||||||
|
u?.queueName &&
|
||||||
|
u?.color &&
|
||||||
|
u?.greetingMessage
|
||||||
|
)
|
||||||
|
.map((u: any) => {
|
||||||
|
const { queueName, color, greetingMessage } = u;
|
||||||
|
return {
|
||||||
|
queueName: queueName?.trim()?.replace(/\s+/g, " "),
|
||||||
|
color,
|
||||||
|
greetingMessage
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (new_queues && new_queues.length > 0) {
|
||||||
|
const db_queues: any = await Queue.findAll();
|
||||||
|
|
||||||
|
// for (const i in new_queues) {
|
||||||
|
// let { queueName: name, color, greetingMessage } = new_queues[i];
|
||||||
|
|
||||||
|
// name = name?.trim()?.replace(/\s+/g, " ");
|
||||||
|
|
||||||
|
// const update = db_queues.find(
|
||||||
|
// (q: any) => q.name?.trim()?.replace(/\s+/g, " ") == name
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (update) {
|
||||||
|
// const { id } = update;
|
||||||
|
// // UPDATE
|
||||||
|
// // const queue = await UpdateQueueService(id, {
|
||||||
|
// // name,
|
||||||
|
// // color,
|
||||||
|
// // greetingMessage
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// // const io = getIO();
|
||||||
|
// // io.emit("queue", {
|
||||||
|
// // action: "update",
|
||||||
|
// // queue
|
||||||
|
// // });
|
||||||
|
// } else {
|
||||||
|
// // CREATE
|
||||||
|
// // const queue = await CreateQueueService({
|
||||||
|
// // name,
|
||||||
|
// // color,
|
||||||
|
// // greetingMessage
|
||||||
|
// // });
|
||||||
|
// // const io = getIO();
|
||||||
|
// // io.emit("queue", {
|
||||||
|
// // action: "update",
|
||||||
|
// // queue
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
let remove_queues = db_queues.filter(
|
||||||
|
(q: any) =>
|
||||||
|
!new_queues
|
||||||
|
.map((nq: any) => nq.queueName)
|
||||||
|
.includes(q.name?.trim()?.replace(/\s+/g, " "))
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const i in remove_queues) {
|
||||||
|
const { id, name } = remove_queues[i];
|
||||||
|
|
||||||
|
// await DeleteQueueService(id);
|
||||||
|
|
||||||
|
// const io = getIO();
|
||||||
|
// io.emit("queue", {
|
||||||
|
// action: "delete",
|
||||||
|
// queueId: +id
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
// await set("ura", ura);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await set("ura", ura);
|
||||||
|
|
||||||
|
const _ura = await get({ key: "ura", parse: true });
|
||||||
|
console.log("_URA: ", _ura);
|
||||||
|
|
||||||
|
return res.status(200).json({ new_queues });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { queueId } = req.params;
|
||||||
|
|
||||||
|
const queue = await ShowQueueService(queueId);
|
||||||
|
|
||||||
|
return res.status(200).json(queue);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { queueId } = req.params;
|
||||||
|
|
||||||
|
const queue = await UpdateQueueService(queueId, req.body);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("queue", {
|
||||||
|
action: "update",
|
||||||
|
queue
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(201).json(queue);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { queueId } = req.params;
|
||||||
|
|
||||||
|
await DeleteQueueService(queueId);
|
||||||
|
|
||||||
|
await QuickAnswerQueue.destroy({ where: { queueId } });
|
||||||
|
await ContactQueue.destroy({ where: { queueId } });
|
||||||
|
|
||||||
|
await del(`queue:${queueId}`);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("queue", {
|
||||||
|
action: "delete",
|
||||||
|
queueId: +queueId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send();
|
||||||
|
};
|
|
@ -0,0 +1,123 @@
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import ListQuickAnswerService from "../services/QuickAnswerService/ListQuickAnswerService";
|
||||||
|
import CreateQuickAnswerService from "../services/QuickAnswerService/CreateQuickAnswerService";
|
||||||
|
import ShowQuickAnswerService from "../services/QuickAnswerService/ShowQuickAnswerService";
|
||||||
|
import UpdateQuickAnswerService from "../services/QuickAnswerService/UpdateQuickAnswerService";
|
||||||
|
import DeleteQuickAnswerService from "../services/QuickAnswerService/DeleteQuickAnswerService";
|
||||||
|
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import QuickAnswerQueue from "../models/QuickAnswerQueue";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface QuickAnswerData {
|
||||||
|
shortcut: string;
|
||||||
|
message: string;
|
||||||
|
queueIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { searchParam, pageNumber, userId } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { quickAnswers, count, hasMore } = await ListQuickAnswerService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ quickAnswers, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const newQuickAnswer: QuickAnswerData = req.body;
|
||||||
|
|
||||||
|
const QuickAnswerSchema = Yup.object().shape({
|
||||||
|
shortcut: Yup.string().required(),
|
||||||
|
message: Yup.string().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await QuickAnswerSchema.validate(newQuickAnswer);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const quickAnswer = await CreateQuickAnswerService({
|
||||||
|
...newQuickAnswer
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("quickAnswer", {
|
||||||
|
action: "create",
|
||||||
|
quickAnswer
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(quickAnswer);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { quickAnswerId, userId } = req.params;
|
||||||
|
|
||||||
|
const quickAnswer = await ShowQuickAnswerService(quickAnswerId, userId);
|
||||||
|
|
||||||
|
return res.status(200).json(quickAnswer);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const quickAnswerData: QuickAnswerData = req.body;
|
||||||
|
|
||||||
|
const schema = Yup.object().shape({
|
||||||
|
shortcut: Yup.string(),
|
||||||
|
message: Yup.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await schema.validate(quickAnswerData);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { quickAnswerId } = req.params;
|
||||||
|
|
||||||
|
const quickAnswer = await UpdateQuickAnswerService({
|
||||||
|
quickAnswerData,
|
||||||
|
quickAnswerId
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("quickAnswer", {
|
||||||
|
action: "update",
|
||||||
|
quickAnswer
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(quickAnswer);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { quickAnswerId, queueId } = req.params;
|
||||||
|
|
||||||
|
await DeleteQuickAnswerService(quickAnswerId);
|
||||||
|
|
||||||
|
await QuickAnswerQueue.destroy({ where: { quickAnswerId } });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("quickAnswer", {
|
||||||
|
action: "delete",
|
||||||
|
quickAnswerId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Quick Answer deleted" });
|
||||||
|
};
|
|
@ -0,0 +1,376 @@
|
||||||
|
//relatorio
|
||||||
|
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import ShowTicketReport from "../services/TicketServices/ShowTicketReport";
|
||||||
|
import ShowMessageReport from "../services/MessageServices/ShowMessageReport";
|
||||||
|
import onlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
|
||||||
|
import User from "../models/User";
|
||||||
|
import Queue from "../models/Queue";
|
||||||
|
import UserOnlineTime from "../models/UserOnlineTime";
|
||||||
|
import { Op, Sequelize, literal } from "sequelize";
|
||||||
|
import format from "date-fns/format";
|
||||||
|
import ptBR from "date-fns/locale/pt-BR";
|
||||||
|
import { splitDateTime } from "../helpers/SplitDateTime";
|
||||||
|
import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService";
|
||||||
|
import ListUserParamiterService from "../services/UserServices/ListUserParamiterService";
|
||||||
|
import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport";
|
||||||
|
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
|
||||||
|
import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import { Json } from "sequelize/types/lib/utils";
|
||||||
|
import ReportByNumberQueueService from "../services/ReportServices/ReportByNumberQueueService";
|
||||||
|
import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
userId: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
createdOrUpdated: string;
|
||||||
|
queueId: string;
|
||||||
|
pageNumber: string;
|
||||||
|
userQueues: [];
|
||||||
|
isRemote: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReportOnQueue = {
|
||||||
|
userId: string;
|
||||||
|
identifier: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportUserByDateStartDateEnd = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
userId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
pageNumber,
|
||||||
|
userQueues,
|
||||||
|
createdOrUpdated,
|
||||||
|
queueId
|
||||||
|
} = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { tickets, count, hasMore } = await ShowTicketReport({
|
||||||
|
userId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
pageNumber,
|
||||||
|
createdOrUpdated,
|
||||||
|
queueId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const queues = await Queue.findAll({ attributes: ["id", "name"] });
|
||||||
|
|
||||||
|
return res.status(200).json({ tickets, count, hasMore, queues });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportUserService = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
const { userId, startDate, endDate, userQueues} = req.query as IndexQuery;
|
||||||
|
|
||||||
|
// let usersProfile = await ListUserParamiterService({ profile: 'user' })
|
||||||
|
let usersProfile = await ListUserParamiterService({
|
||||||
|
profiles: ["user", "supervisor"],
|
||||||
|
userQueues: userQueues ? userQueues : undefined,
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const sumUserOlineTime = await ShowUserServiceReport({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
const closedByUser = await ShowUserServiceReport({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
ticketStatus: "closed",
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
const openByUser = await ShowUserServiceReport({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
ticketStatus: "open",
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
let dateTime = splitDateTime(
|
||||||
|
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
|
||||||
|
);
|
||||||
|
const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate });
|
||||||
|
|
||||||
|
const openByUserOnQueue = await CountTicketsByUserQueue({
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
status: "open",
|
||||||
|
clientChatStart: true
|
||||||
|
});
|
||||||
|
const openByUserOutQueue = await CountTicketsByUserQueue({
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
status: "open",
|
||||||
|
clientChatStart: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const closedByUserOnQueue = await CountTicketsByUserQueue({
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
status: "closed",
|
||||||
|
clientChatStart: true
|
||||||
|
});
|
||||||
|
const closedUserOutQueue = await CountTicketsByUserQueue({
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
status: "closed",
|
||||||
|
clientChatStart: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// let openQueueInOut = openByUserOnQueue.concat(openByUserOutQueue)
|
||||||
|
// let closedQueueInOut = closedByUserOnQueue.concat(closedUserOutQueue)
|
||||||
|
|
||||||
|
const queuesByUser = await ShowQueuesByUser({ profile: "user" });
|
||||||
|
|
||||||
|
let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue);
|
||||||
|
let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue);
|
||||||
|
|
||||||
|
// console.log('onlineUsers: ',JSON.parse(JSON.stringify(onlineUsers)))
|
||||||
|
// console.log('sumUserOlineTime: ', JSON.parse(JSON.stringify(sumUserOlineTime)))
|
||||||
|
|
||||||
|
for (let i = 0; i < queuesByUser.length; i++) {
|
||||||
|
queuesByUser[i].countOpen = 0;
|
||||||
|
queuesByUser[i].countClosed = 0;
|
||||||
|
|
||||||
|
for (let x = 0; x < openCloseOnQueue.length; x++) {
|
||||||
|
if (
|
||||||
|
queuesByUser[i].userId == openCloseOnQueue[x].userId &&
|
||||||
|
queuesByUser[i].queueId == openCloseOnQueue[x].queueId &&
|
||||||
|
openCloseOnQueue[x].status == "open"
|
||||||
|
) {
|
||||||
|
queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance;
|
||||||
|
} else if (
|
||||||
|
queuesByUser[i].userId == openCloseOnQueue[x].userId &&
|
||||||
|
queuesByUser[i].queueId == openCloseOnQueue[x].queueId &&
|
||||||
|
openCloseOnQueue[x].status == "closed"
|
||||||
|
) {
|
||||||
|
queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usersProfile.map((user: any) => {
|
||||||
|
let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
user.sumOnlineTime = sumUserOlineTime[index];
|
||||||
|
|
||||||
|
// console.log('user.sumOlineTime: 'user.sumOnlineTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
index = closedByUser.findIndex((e: any) => e.userId == user.id);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
user.sumClosed = closedByUser[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
index = openByUser.findIndex((e: any) => e.userId == user.id);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
user.sumOpen = openByUser[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPEN, CLOSED TICKETS STARTED BY USERS
|
||||||
|
let openClosedOutQueue = {};
|
||||||
|
let open = openCloseOutQueue.filter(
|
||||||
|
e => e.userId == user.id && e.status == "open"
|
||||||
|
);
|
||||||
|
let closed = openCloseOutQueue.filter(
|
||||||
|
e => e.userId == user.id && e.status == "closed"
|
||||||
|
);
|
||||||
|
|
||||||
|
openClosedOutQueue = {
|
||||||
|
...openClosedOutQueue,
|
||||||
|
userId: user.id,
|
||||||
|
countOpen: open && open.length > 0 ? open[0].totAttendance : 0,
|
||||||
|
countClosed: closed && closed.length > 0 ? closed[0].totAttendance : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
user.openClosedOutQueue = openClosedOutQueue;
|
||||||
|
|
||||||
|
// OPEN, CLOSED TICKETS STARTED BY CLIENTS
|
||||||
|
let openClosedInQueue = queuesByUser.filter(e => e.userId == user.id);
|
||||||
|
|
||||||
|
if (openClosedInQueue && openClosedInQueue.length > 0) {
|
||||||
|
user.openClosedInQueue = openClosedInQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = onlineUsers.findIndex((e: any) => e.userId == user.id);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
user.statusOnline = onlineUsers[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate.length > 0 && startDate.split("-").length == 3) {
|
||||||
|
let date = startDate.split("-");
|
||||||
|
user.startDate = `${date[2]}/${date[1]}/${date[0]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate.length > 0 && endDate.split("-").length == 3) {
|
||||||
|
let date = endDate.split("-");
|
||||||
|
user.endDate = `${date[2]}/${date[1]}/${date[0]}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ usersProfile: usersProfile });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportMessagesUserByDateStartDateEnd = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId, startDate, endDate } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
let data_query_messages = await ShowMessageReport(userId, startDate, endDate);
|
||||||
|
|
||||||
|
for (var i = 0; i < data_query_messages.length; i++) {
|
||||||
|
if (data_query_messages[i].fromMe) {
|
||||||
|
data_query_messages[i].fromMe = "Atendente";
|
||||||
|
} else {
|
||||||
|
data_query_messages[i].fromMe = "Cliente";
|
||||||
|
}
|
||||||
|
|
||||||
|
data_query_messages[i].id = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(data_query_messages);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportOnQueue = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
// console.log(req.body)
|
||||||
|
|
||||||
|
const { adminId, identifier, queueStatus, file } = req.body;
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("queryOnQueueStatus", {
|
||||||
|
action: "update",
|
||||||
|
queryOnQueue: {
|
||||||
|
adminId: adminId,
|
||||||
|
identifier: identifier,
|
||||||
|
queueStatus: queueStatus,
|
||||||
|
file: file
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "ok" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportService = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("IS REMOTE: ", isRemote);
|
||||||
|
console.log("isRemote: ", isRemote && isRemote == "true" ? true : false);
|
||||||
|
|
||||||
|
const reportService = await ReportByNumberQueueService({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
isRemote: isRemote && isRemote == "true" ? true : false
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ reportService });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportServiceByQueue = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const reportService = await ReportByNumberQueueService({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
queue: true,
|
||||||
|
isRemote: isRemote && isRemote == "true" ? true : false
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ reportService });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportTicksCountByStatusChatEnds = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startDate, endDate, userQueues } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const dateToday = splitDateTime(
|
||||||
|
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const queueIds = userQueues ? userQueues.map(queue => parseInt(queue)) : [];
|
||||||
|
|
||||||
|
const reportStatusChatEnd = await CountStatusChatEndService(
|
||||||
|
startDate || dateToday.fullDate,
|
||||||
|
endDate || dateToday.fullDate,
|
||||||
|
queueIds
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({ reportStatusChatEnd });
|
||||||
|
};
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import DeleteSchedulingNotifyService from "../services/SchedulingNotifyServices/DeleteSchedulingNotifyService";
|
||||||
|
import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService";
|
||||||
|
import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService";
|
||||||
|
import ShowSchedulingNotifyService from "../services/SchedulingNotifyServices/ShowSchedulingNotifyService";
|
||||||
|
import { deleteScheduleByTicketIdCache } from "../helpers/SchedulingNotifyCache";
|
||||||
|
import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
contactNumber: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportScheduleNotifyByDateStartDateEnd = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { contactNumber, startDate, endDate } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const data_query = await ListSchedulingNotifyContactService(
|
||||||
|
contactNumber,
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json(data_query);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createOrUpdateScheduleNotify = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const scheduleData = req.body;
|
||||||
|
|
||||||
|
const statusChatEnd = await ShowStatusChatEndService({
|
||||||
|
name: scheduleData.statusChatEndName
|
||||||
|
});
|
||||||
|
|
||||||
|
const schedulingNotifyCreate = await CreateSchedulingNotifyService({
|
||||||
|
schedulingNotifyId: scheduleData.schedulingNotifyId,
|
||||||
|
ticketId: scheduleData.ticketId,
|
||||||
|
statusChatEndId: statusChatEnd.id,
|
||||||
|
schedulingDate: scheduleData.schedulingDate,
|
||||||
|
schedulingTime: scheduleData.schedulingTime,
|
||||||
|
message: scheduleData.message
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.group(':::::::::::::::::: DATA schedulingNotifyCreate:\n',schedulingNotifyCreate)
|
||||||
|
// const io = getIO();
|
||||||
|
// io.emit("schedulingNotify", {action: "update", schedulingNotifyCreate });
|
||||||
|
|
||||||
|
return res.status(200).json(schedulingNotifyCreate);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { scheduleId } = req.params;
|
||||||
|
|
||||||
|
let schedule: any = await ShowSchedulingNotifyService(scheduleId);
|
||||||
|
|
||||||
|
await deleteScheduleByTicketIdCache(schedule.ticketId);
|
||||||
|
|
||||||
|
await DeleteSchedulingNotifyService(scheduleId);
|
||||||
|
|
||||||
|
return res.status(200).send();
|
||||||
|
};
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
import AuthUserService from "../services/UserServices/AuthUserService";
|
||||||
|
import { SendRefreshToken } from "../helpers/SendRefreshToken";
|
||||||
|
import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService";
|
||||||
|
|
||||||
|
import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
|
||||||
|
import { removeUserFromOlineList } from "../helpers/removeUserFromOnlineList";
|
||||||
|
|
||||||
|
// const usersSocket = require("./../libs/socket");
|
||||||
|
const usersSocket = require("../libs/socket");
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
const { token, serializedUser, refreshToken } = await AuthUserService({
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
});
|
||||||
|
|
||||||
|
SendRefreshToken(res, refreshToken);
|
||||||
|
|
||||||
|
const userOnline = await createOrUpdateOnlineUserService({
|
||||||
|
userId: serializedUser.id,
|
||||||
|
status: "online"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
token,
|
||||||
|
user: serializedUser
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const token: string = req.cookies.jrt;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new AppError("ERR_SESSION_EXPIRED", 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user, newToken, refreshToken } = await RefreshTokenService(
|
||||||
|
res,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
|
SendRefreshToken(res, refreshToken);
|
||||||
|
|
||||||
|
return res.json({ token: newToken, user });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
res.clearCookie("jrt");
|
||||||
|
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
removeUserFromOlineList(userId);
|
||||||
|
|
||||||
|
const userOnline = await createOrUpdateOnlineUserService({
|
||||||
|
userId,
|
||||||
|
status: "offline"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.send();
|
||||||
|
};
|
|
@ -0,0 +1,177 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
import UpdateSettingService from "../services/SettingServices/UpdateSettingService";
|
||||||
|
import ListSettingsService from "../services/SettingServices/ListSettingsService";
|
||||||
|
import loadSettings from "../helpers/LoadSettings";
|
||||||
|
import updateSettingTicket from "../services/SettingServices/UpdateSettingTicket";
|
||||||
|
import SettingTicket from "../models/SettingTicket";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
// if (req.user.profile !== "master") {
|
||||||
|
// throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const settings = await ListSettingsService();
|
||||||
|
|
||||||
|
return res.status(200).json({ settings });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ticketSettings = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { number } = req.params;
|
||||||
|
|
||||||
|
const config = await SettingTicket.findAll({ where: { number } });
|
||||||
|
|
||||||
|
return res.status(200).json({ config });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTicketSettings = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const {
|
||||||
|
number,
|
||||||
|
saturdayBusinessTime,
|
||||||
|
outBusinessHours,
|
||||||
|
ticketExpiration,
|
||||||
|
weekend,
|
||||||
|
saturday,
|
||||||
|
sunday,
|
||||||
|
holiday
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (!number) throw new AppError("No number selected", 400);
|
||||||
|
|
||||||
|
if (outBusinessHours && Object.keys(outBusinessHours).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...outBusinessHours,
|
||||||
|
key: "outBusinessHours",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saturdayBusinessTime && Object.keys(saturdayBusinessTime).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...saturdayBusinessTime,
|
||||||
|
key: "saturdayBusinessTime",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticketExpiration && Object.keys(ticketExpiration).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...ticketExpiration,
|
||||||
|
key: "ticketExpiration",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weekend && Object.keys(weekend).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...weekend,
|
||||||
|
key: "weekend",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saturday && Object.keys(saturday).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...saturday,
|
||||||
|
key: "saturday",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sunday && Object.keys(sunday).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...sunday,
|
||||||
|
key: "sunday",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holiday && Object.keys(holiday).length > 0) {
|
||||||
|
await updateSettingTicket({
|
||||||
|
...holiday,
|
||||||
|
key: "holiday",
|
||||||
|
number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
outBusinessHours,
|
||||||
|
ticketExpiration,
|
||||||
|
weekend,
|
||||||
|
saturday,
|
||||||
|
sunday,
|
||||||
|
holiday
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "master") {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
const { settingKey: key } = req.params;
|
||||||
|
const { value, obj } = req.body;
|
||||||
|
|
||||||
|
const setting = await UpdateSettingService({
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
obj
|
||||||
|
});
|
||||||
|
|
||||||
|
if (key && key == "whatsaAppCloudApi") {
|
||||||
|
let whatsapps: any = await Whatsapp.findAll();
|
||||||
|
|
||||||
|
if (whatsapps) {
|
||||||
|
for (let i in whatsapps) {
|
||||||
|
const { id, wabaId, isOfficial } = whatsapps[i];
|
||||||
|
|
||||||
|
if (isOfficial && wabaId) {
|
||||||
|
try {
|
||||||
|
const whatsapp = await Whatsapp.findByPk(id);
|
||||||
|
if (whatsapp) {
|
||||||
|
if (value == "disabled") {
|
||||||
|
whatsapp.update({ status: "OPENING" });
|
||||||
|
} else if (value == "enabled") {
|
||||||
|
const info = await whatsappOfficialNumberInfo(wabaId);
|
||||||
|
if (info) {
|
||||||
|
whatsapp.update({
|
||||||
|
classification: info.quality_rating,
|
||||||
|
status: "CONNECTED"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"error on try update classification number from oficial whatsapp in SettingControllers.ts: ",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("settings", {
|
||||||
|
action: "update",
|
||||||
|
setting
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(setting);
|
||||||
|
};
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusChatEndData {
|
||||||
|
name: string;
|
||||||
|
farewellMessage: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService";
|
||||||
|
import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService";
|
||||||
|
import UpdateStatusChatEndService from "../services/StatusChatEndService/UpdateStatusChatEndService";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import StatusChatEnd from "../models/StatusChatEnd";
|
||||||
|
import CreateStatusChatEndService from "../services/StatusChatEndService/CreateStatusChatEndService";
|
||||||
|
import { del } from "../helpers/RedisClient";
|
||||||
|
|
||||||
|
// export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
|
// const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({ searchParam: "", pageNumber: "1" });
|
||||||
|
|
||||||
|
// return res.status(200).json(statusChatEnd);
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { searchParam, pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ statusChatEnd, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { statusChatEndId } = req.params;
|
||||||
|
|
||||||
|
let statushCatEnd;
|
||||||
|
|
||||||
|
const isNumber = (str: any) => !isNaN(str);
|
||||||
|
|
||||||
|
if (isNumber(statusChatEndId))
|
||||||
|
statushCatEnd = await ShowStatusChatEndService({ id: statusChatEndId });
|
||||||
|
else statushCatEnd = await ShowStatusChatEndService({ isDefault: true });
|
||||||
|
|
||||||
|
return res.status(200).json(statushCatEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const newStatusChatEnd: StatusChatEndData = req.body;
|
||||||
|
|
||||||
|
const StatusChatEndSchema = Yup.object().shape({
|
||||||
|
name: Yup.string().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await StatusChatEndSchema.validate(newStatusChatEnd);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusChatEnd = await CreateStatusChatEndService({
|
||||||
|
...newStatusChatEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("statusChatEnd", {
|
||||||
|
action: "create",
|
||||||
|
statusChatEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(statusChatEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const statusChatEndData: StatusChatEndData = req.body;
|
||||||
|
|
||||||
|
const schema = Yup.object().shape({
|
||||||
|
name: Yup.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await schema.validate(statusChatEndData);
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new AppError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { statusChatEndId } = req.params;
|
||||||
|
|
||||||
|
const statusChatEnd = await UpdateStatusChatEndService({
|
||||||
|
statusChatEndData,
|
||||||
|
statusChatEndId
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("statusChatEnd", {
|
||||||
|
action: "update",
|
||||||
|
statusChatEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(statusChatEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { statusChatEndId } = req.params;
|
||||||
|
|
||||||
|
await StatusChatEnd.destroy({ where: { id: statusChatEndId } });
|
||||||
|
|
||||||
|
await del(`statusChatEnd:${statusChatEndId}`);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("statusChatEnd", {
|
||||||
|
action: "delete",
|
||||||
|
statusChatEndId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Status chat deleted" });
|
||||||
|
};
|
|
@ -0,0 +1,728 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import CreateTicketService from "../services/TicketServices/CreateTicketService";
|
||||||
|
import DeleteTicketService from "../services/TicketServices/DeleteTicketService";
|
||||||
|
import ListTicketsService from "../services/TicketServices/ListTicketsService";
|
||||||
|
import ShowTicketService from "../services/TicketServices/ShowTicketService";
|
||||||
|
import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
|
||||||
|
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
|
||||||
|
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
|
||||||
|
import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService";
|
||||||
|
|
||||||
|
import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService";
|
||||||
|
import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService";
|
||||||
|
|
||||||
|
import { isScheduling } from "../helpers/CheckSchedulingReminderNotify";
|
||||||
|
|
||||||
|
import ptBR from "date-fns/locale/pt-BR";
|
||||||
|
import { splitDateTime } from "../helpers/SplitDateTime";
|
||||||
|
import format from "date-fns/format";
|
||||||
|
|
||||||
|
import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache";
|
||||||
|
|
||||||
|
import { searchTicketCache, loadTicketsCache } from "../helpers/TicketCache";
|
||||||
|
import { Op, where } from "sequelize";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
status: string;
|
||||||
|
date: string;
|
||||||
|
showAll: string;
|
||||||
|
withUnreadMessages: string;
|
||||||
|
queueIds: string;
|
||||||
|
unlimited?: string;
|
||||||
|
searchParamContent?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TicketData {
|
||||||
|
contactId: number;
|
||||||
|
status: string;
|
||||||
|
queueId: number;
|
||||||
|
userId: number;
|
||||||
|
whatsappId?: string | number;
|
||||||
|
msg?: string;
|
||||||
|
transfer?: boolean | undefined;
|
||||||
|
fromMe?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService";
|
||||||
|
import Ticket from "../models/Ticket";
|
||||||
|
import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport";
|
||||||
|
import TicketEmiterSumOpenClosedByUser from "../helpers/OnlineReporEmiterInfoByUser";
|
||||||
|
import CountTicketService from "../services/TicketServices/CountTicketService";
|
||||||
|
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
|
||||||
|
import ShowUserService from "../services/UserServices/ShowUserService";
|
||||||
|
import axios from "axios";
|
||||||
|
import User from "../models/User";
|
||||||
|
import CheckContactOpenTickets from "../helpers/CheckContactOpenTickets";
|
||||||
|
import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp";
|
||||||
|
import { getWbot } from "../libs/wbot";
|
||||||
|
import endPointQuery from "../helpers/old_EndPointQuery";
|
||||||
|
import Contact from "../models/Contact";
|
||||||
|
import BotIsOnQueue from "../helpers/BotIsOnQueue";
|
||||||
|
import { setMessageAsRead } from "../helpers/SetMessageAsRead";
|
||||||
|
import { getSettingValue } from "../helpers/WhaticketSettings";
|
||||||
|
import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsAppsForQueueService";
|
||||||
|
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
|
||||||
|
import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
|
||||||
|
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
|
||||||
|
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
|
||||||
|
import CreateContactService from "../services/ContactServices/CreateContactService";
|
||||||
|
import { botSendMessage } from "../services/WbotServices/wbotMessageListener";
|
||||||
|
import WhatsappQueue from "../models/WhatsappQueue";
|
||||||
|
import { del, get, set } from "../helpers/RedisClient";
|
||||||
|
import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService";
|
||||||
|
import Queue from "../models/Queue";
|
||||||
|
import StatusChatEnd from "../models/StatusChatEnd";
|
||||||
|
import controllByNumber from "../helpers/controllByNumber";
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const {
|
||||||
|
pageNumber,
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
searchParam,
|
||||||
|
showAll,
|
||||||
|
queueIds: queueIdsStringified,
|
||||||
|
withUnreadMessages,
|
||||||
|
unlimited,
|
||||||
|
searchParamContent
|
||||||
|
} = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
let queueIds: number[] = [];
|
||||||
|
|
||||||
|
if (queueIdsStringified && queueIdsStringified.trim().length > 0) {
|
||||||
|
queueIds = JSON.parse(queueIdsStringified);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tickets, count, hasMore, remoteTicketsControll } =
|
||||||
|
await ListTicketsService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
showAll,
|
||||||
|
userId,
|
||||||
|
queueIds,
|
||||||
|
withUnreadMessages,
|
||||||
|
unlimited,
|
||||||
|
searchParamContent
|
||||||
|
});
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json({ tickets, count, hasMore, remoteTicketsControll });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remoteTicketCreation = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
let { queueId, contact_from, cc, contact_to, msg, contact_name }: any =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
let whatsappId: any;
|
||||||
|
|
||||||
|
if (!queueId && !contact_from && !cc) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `Property 'queueId' or 'contact_from' or 'cc' is required.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = ["contact_to", "msg"];
|
||||||
|
const validateOnlyNumber = ["queueId", "contact_to", "contact_from"];
|
||||||
|
|
||||||
|
for (let prop of validate) {
|
||||||
|
if (!req.body[prop])
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: `Property '${prop}' is undefined.` });
|
||||||
|
|
||||||
|
if (validateOnlyNumber.includes(prop)) {
|
||||||
|
if (!/^\d+$/.test(req.body[prop])) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: `The property '${prop}' must be a number` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queueId) {
|
||||||
|
const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED");
|
||||||
|
|
||||||
|
if (!whatsapps || whatsapps?.length == 0) {
|
||||||
|
return res.status(500).json({
|
||||||
|
msg: `queueId ${queueId} does not have a WhatsApp number associated with it or the number's session is disconnected.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = whatsapps[0];
|
||||||
|
|
||||||
|
whatsappId = id;
|
||||||
|
} else if (contact_from) {
|
||||||
|
const whatsapp = await Whatsapp.findOne({
|
||||||
|
where: { number: contact_from, status: "CONNECTED" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!whatsapp) {
|
||||||
|
return res.status(404).json({
|
||||||
|
msg: `Whatsapp number ${contact_from} not found or disconnected!`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = whatsapp;
|
||||||
|
|
||||||
|
const { queues } = await ShowWhatsAppService(id);
|
||||||
|
|
||||||
|
if (!queues || queues.length == 0) {
|
||||||
|
return res.status(500).json({
|
||||||
|
msg: `The WhatsApp number ${contact_from} is not associated with any queue! `
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
queueId = queues[0].id;
|
||||||
|
whatsappId = id;
|
||||||
|
} else if (cc) {
|
||||||
|
const queue = await Queue.findOne({ where: { cc } });
|
||||||
|
if (!queue) {
|
||||||
|
return res.status(404).json({
|
||||||
|
msg: `Queue with cc ${cc} not found! `
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
queueId = queue.id;
|
||||||
|
|
||||||
|
const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED");
|
||||||
|
|
||||||
|
if (whatsapps.length === 0) {
|
||||||
|
return res.status(500).json({
|
||||||
|
msg: `No WhatsApp found for this cc ${cc} or the WhatsApp number is disconnected! `
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
whatsappId = whatsapps[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const validNumber = await CheckIsValidContact(contact_to, true);
|
||||||
|
const validNumber = contact_to;
|
||||||
|
|
||||||
|
if (validNumber) {
|
||||||
|
let contact = await Contact.findOne({ where: { number: validNumber } });
|
||||||
|
|
||||||
|
if (!contact) {
|
||||||
|
// const profilePicUrl = await GetProfilePicUrl(validNumber);
|
||||||
|
|
||||||
|
contact = await CreateContactService({
|
||||||
|
name: contact_name ? contact_name : contact_to,
|
||||||
|
number: validNumber
|
||||||
|
// profilePicUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contact", {
|
||||||
|
action: "create",
|
||||||
|
contact
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: contactId } = contact;
|
||||||
|
|
||||||
|
const botInfo = await BotIsOnQueue("botqueue");
|
||||||
|
|
||||||
|
// ticket from queueChoice or bot
|
||||||
|
let ticket: any = await Ticket.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ contactId, status: "queueChoice" },
|
||||||
|
{ contactId, status: "open", userId: botInfo.userIdBot }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
|
||||||
|
if (ticket) {
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { status: "closed" },
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
ticket = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ticket) {
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { status: "closed" },
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket = await Ticket.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ contactId, status: "pending" },
|
||||||
|
{ contactId, status: "open" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticket) {
|
||||||
|
console.log(
|
||||||
|
`THE CAMPAIGN TICKET WAS NOT CREATED BECAUSE THE TICKET IS PENDING OR OPEN`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(422).json({
|
||||||
|
msg: `The campaign ticket was not created because the number ${contact_to} already has a ticket open or pending`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket = await FindOrCreateTicketService(
|
||||||
|
contact,
|
||||||
|
whatsappId,
|
||||||
|
0,
|
||||||
|
undefined,
|
||||||
|
queueId,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// botSendMessage(ticket, `${msg}`);
|
||||||
|
|
||||||
|
await ticket.update({
|
||||||
|
lastMessage: msg
|
||||||
|
});
|
||||||
|
|
||||||
|
await set(
|
||||||
|
`remote:ticketId:${ticket.id}`,
|
||||||
|
JSON.stringify({
|
||||||
|
id: ticket.id,
|
||||||
|
createdAt: ticket.createdAt,
|
||||||
|
updatedAt: ticket.updatedAt,
|
||||||
|
whatsappId: ticket.whatsappId,
|
||||||
|
status: ticket.status
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to(ticket.status).emit("ticket", {
|
||||||
|
action: "update",
|
||||||
|
ticket
|
||||||
|
});
|
||||||
|
|
||||||
|
const obj = await controllByNumber();
|
||||||
|
|
||||||
|
if (obj?.tickets) {
|
||||||
|
io.emit("remoteTickesControll", {
|
||||||
|
action: "update",
|
||||||
|
tickets: obj.ticketIds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({ msg: "success" });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: The number ${contact_to} does not exist on WhatsApp`
|
||||||
|
);
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ msg: `The number ${contact_to} does not exist on WhatsApp` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { contactId, status, userId, msg, queueId, whatsappId }: TicketData =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
const botInfo = await BotIsOnQueue("botqueue");
|
||||||
|
|
||||||
|
let ticket = await Ticket.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ contactId, status: "queueChoice" },
|
||||||
|
{ contactId, status: "open", userId: botInfo.userIdBot }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
|
||||||
|
if (ticket) {
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { status: "closed" },
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
ticket = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ticket) {
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { status: "open", userId: userId, queueId },
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ticket) {
|
||||||
|
ticket = await CreateTicketService({
|
||||||
|
contactId,
|
||||||
|
status,
|
||||||
|
userId,
|
||||||
|
queueId,
|
||||||
|
whatsappId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to(ticket.status).emit("ticket", {
|
||||||
|
action: "update",
|
||||||
|
ticket
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(ticket);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
|
const contact = await ShowTicketService(ticketId);
|
||||||
|
|
||||||
|
const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({
|
||||||
|
searchParam: "",
|
||||||
|
pageNumber: "1"
|
||||||
|
});
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
const schedulesContact = await ListSchedulingNotifyContactService(
|
||||||
|
contact.contact.number
|
||||||
|
);
|
||||||
|
/////////////////
|
||||||
|
|
||||||
|
return res.status(200).json({ contact, statusChatEnd, schedulesContact });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const count = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
// type indexQ = { status: string; date?: string; };
|
||||||
|
const { status, date, queueIds } = req.query as IndexQuery;
|
||||||
|
const ticketCount = await CountTicketService(status, date, queueIds);
|
||||||
|
|
||||||
|
return res.status(200).json(ticketCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
console.log("ENTROU NO UPDATE TICKET CONTROLLER");
|
||||||
|
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
|
const userOldInfo = await Ticket.findByPk(ticketId);
|
||||||
|
|
||||||
|
let ticket2 = {};
|
||||||
|
|
||||||
|
if (req.body["status"] === "closed") {
|
||||||
|
const { status, userId, schedulingNotifyData } = req.body;
|
||||||
|
|
||||||
|
// lembrete
|
||||||
|
const scheduleData = JSON.parse(schedulingNotifyData);
|
||||||
|
|
||||||
|
console.log("scheduleData: ", scheduleData);
|
||||||
|
|
||||||
|
const statusChatEnd = await ShowStatusChatEndService({
|
||||||
|
name: scheduleData.statusChatEndName
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ticket } = await UpdateTicketService({
|
||||||
|
ticketData: {
|
||||||
|
status: status,
|
||||||
|
userId: userId,
|
||||||
|
statusChatEnd: statusChatEnd.name,
|
||||||
|
statusChatEndId: statusChatEnd.id
|
||||||
|
},
|
||||||
|
ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
let _farewellMessage;
|
||||||
|
|
||||||
|
if (getSettingValue("farewellMessageByStatusChatEnd")?.value == "enabled") {
|
||||||
|
const statusChatEndData = await get({
|
||||||
|
key: `statusChatEnd:${statusChatEnd.id}`,
|
||||||
|
parse: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
statusChatEndData &&
|
||||||
|
statusChatEndData?.farewellMessage &&
|
||||||
|
statusChatEndData?.farewellMessage?.trim()?.length > 0
|
||||||
|
) {
|
||||||
|
const { farewellMessage } = statusChatEndData;
|
||||||
|
|
||||||
|
_farewellMessage = farewellMessage;
|
||||||
|
}
|
||||||
|
} else if (getSettingValue("farewellMessageByQueue")?.value == "enabled") {
|
||||||
|
const queueData = await get({
|
||||||
|
key: `queue:${ticket.queueId}`,
|
||||||
|
parse: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
queueData &&
|
||||||
|
queueData?.farewellMessage &&
|
||||||
|
queueData?.farewellMessage?.trim()?.length > 0
|
||||||
|
) {
|
||||||
|
const { farewellMessage } = queueData;
|
||||||
|
|
||||||
|
_farewellMessage = farewellMessage;
|
||||||
|
}
|
||||||
|
} else if (scheduleData.farewellMessage) {
|
||||||
|
const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
|
||||||
|
|
||||||
|
const { farewellMessage } = whatsapp;
|
||||||
|
|
||||||
|
if (farewellMessage) {
|
||||||
|
_farewellMessage = farewellMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_farewellMessage) {
|
||||||
|
await SendWhatsAppMessage({ body: `\u200e${_farewellMessage}`, ticket });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("tatusChatEnd.name: ", statusChatEnd.name);
|
||||||
|
|
||||||
|
if (
|
||||||
|
statusChatEnd.name === "LEMBRETE" ||
|
||||||
|
statusChatEnd.name === "AGENDAMENTO À CONFIRMAR"
|
||||||
|
) {
|
||||||
|
// lembrete // agendamento
|
||||||
|
if (
|
||||||
|
isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)
|
||||||
|
) {
|
||||||
|
console.log("*** É AGENDAMENTO!");
|
||||||
|
} else {
|
||||||
|
console.log("*** É LEMBRETE!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const schedulingNotifyCreate = await CreateSchedulingNotifyService({
|
||||||
|
ticketId: scheduleData.ticketId,
|
||||||
|
statusChatEndId: `${statusChatEnd.id}`,
|
||||||
|
schedulingDate: scheduleData.schedulingDate,
|
||||||
|
schedulingTime: scheduleData.schedulingTime,
|
||||||
|
message: scheduleData.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket2 = ticket;
|
||||||
|
} else {
|
||||||
|
// Para aparecer pendente para todos usuarios que estao na fila
|
||||||
|
if (req.body.transfer) {
|
||||||
|
req.body.userId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ticketData: TicketData = req.body;
|
||||||
|
|
||||||
|
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
|
||||||
|
if (ticketData.transfer) {
|
||||||
|
const whatsappsByqueue = await ListWhatsAppsForQueueService(
|
||||||
|
ticketData.queueId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userOldInfo) {
|
||||||
|
let listTicketOpenPending: any = [];
|
||||||
|
|
||||||
|
for (const w of whatsappsByqueue) {
|
||||||
|
let whats = await ListWhatsAppsNumber(w.id);
|
||||||
|
|
||||||
|
const ticket = await Ticket.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.and]: [
|
||||||
|
{ contactId: userOldInfo.contactId },
|
||||||
|
{
|
||||||
|
whatsappId: {
|
||||||
|
[Op.in]: whats.whatsapps.map((w: any) => w.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ status: { [Op.or]: ["open", "pending"] } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticket) {
|
||||||
|
listTicketOpenPending.push({
|
||||||
|
ticketId: ticket.id,
|
||||||
|
status: ticket.status,
|
||||||
|
userId: ticket.userId,
|
||||||
|
contactId: ticket.contactId,
|
||||||
|
whatsappId: ticket.whatsappId,
|
||||||
|
queueId: ticket.queueId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("userOldInfo: ", JSON.stringify(userOldInfo, null, 6));
|
||||||
|
// console.log("##########")
|
||||||
|
// console.log(
|
||||||
|
// "listTicketOpenPending: ",
|
||||||
|
// JSON.stringify(listTicketOpenPending)
|
||||||
|
// );
|
||||||
|
|
||||||
|
if (
|
||||||
|
listTicketOpenPending.filter(
|
||||||
|
(ob: any) => userOldInfo.whatsappId != ob.whatsappId
|
||||||
|
)?.length > 0
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_OTHER_OPEN_TICKET");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
// const defaultWhatsapp: any = await GetDefaultWhatsApp({
|
||||||
|
// userId: ticketData.userId
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// "ticketData.userId: ",
|
||||||
|
// ticketData.userId,
|
||||||
|
// " | defaultWhatsapp: ",
|
||||||
|
// JSON.stringify(defaultWhatsapp, null, 6)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const _ticket: any = await Ticket.findByPk(ticketId);
|
||||||
|
|
||||||
|
// if (defaultWhatsapp && ticketData.status != "open") {
|
||||||
|
// await CheckContactOpenTickets(
|
||||||
|
// _ticket.dataValues.contactId,
|
||||||
|
// defaultWhatsapp.dataValues.id
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ticketData.whatsappId = defaultWhatsapp.dataValues.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"--------> ticketData.status: ",
|
||||||
|
ticketData.status,
|
||||||
|
" | ticketData.fromMe: ",
|
||||||
|
ticketData.fromMe
|
||||||
|
);
|
||||||
|
|
||||||
|
const { ticket } = await UpdateTicketService({
|
||||||
|
ticketData,
|
||||||
|
ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticketData.status == "open" && !ticketData.fromMe) {
|
||||||
|
await setMessageAsRead(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticketData.status == "open" || ticketData.status == "pending") {
|
||||||
|
let ticketRemote = await get({
|
||||||
|
key: `remote:ticketId:${ticketId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticketRemote) {
|
||||||
|
ticketRemote = JSON.parse(ticketRemote);
|
||||||
|
|
||||||
|
ticketRemote = {
|
||||||
|
...ticketRemote,
|
||||||
|
...{
|
||||||
|
status: ticketData.status == "open" ? "open" : "pending",
|
||||||
|
updatedAt: ticket.updatedAt
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
set(`remote:ticketId:${ticketId}`, JSON.stringify(ticketRemote));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticketData.userId) {
|
||||||
|
const dateToday = splitDateTime(
|
||||||
|
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
|
||||||
|
);
|
||||||
|
TicketEmiterSumOpenClosedByUser(
|
||||||
|
ticketData.userId.toString(),
|
||||||
|
dateToday.fullDate,
|
||||||
|
dateToday.fullDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket2 = ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOldInfo) {
|
||||||
|
const dateToday = splitDateTime(
|
||||||
|
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userOldInfo.userId) {
|
||||||
|
TicketEmiterSumOpenClosedByUser(
|
||||||
|
userOldInfo.userId.toString(),
|
||||||
|
dateToday.fullDate,
|
||||||
|
dateToday.fullDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(ticket2);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const update = async (
|
||||||
|
// req: Request,
|
||||||
|
// res: Response
|
||||||
|
// ): Promise<Response> => {
|
||||||
|
// const { ticketId } = req.params;
|
||||||
|
// const ticketData: TicketData = req.body;
|
||||||
|
|
||||||
|
// const { ticket } = await UpdateTicketService({
|
||||||
|
// ticketData,
|
||||||
|
// ticketId
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (ticket.status === "closed") {
|
||||||
|
// const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
|
||||||
|
|
||||||
|
// const { farewellMessage } = whatsapp;
|
||||||
|
|
||||||
|
// if (farewellMessage) {
|
||||||
|
// await SendWhatsAppMessage({ body: farewellMessage, ticket });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res.status(200).json(ticket);
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
|
console.log('deletado kkkkkkkkkkkkkkkkkkkk 1')
|
||||||
|
|
||||||
|
const ticket = await DeleteTicketService(ticketId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to(ticket.status).to(ticketId).to("notification").emit("ticket", {
|
||||||
|
action: "delete",
|
||||||
|
ticketId: +ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
io.emit("ticket", {
|
||||||
|
action: "deleteForever",
|
||||||
|
ticketId: +ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
await del(`remote:ticketId:${ticketId}`);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "ticket deleted" });
|
||||||
|
};
|
|
@ -0,0 +1,336 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import CheckSettingsHelper from "../helpers/CheckSettings";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
import CreateUserService from "../services/UserServices/CreateUserService";
|
||||||
|
import ListUsersService from "../services/UserServices/ListUsersService";
|
||||||
|
import UpdateUserService from "../services/UserServices/UpdateUserService";
|
||||||
|
import ShowUserService from "../services/UserServices/ShowUserService";
|
||||||
|
import DeleteUserService from "../services/UserServices/DeleteUserService";
|
||||||
|
|
||||||
|
import ListUser from "../services/UserServices/ListUserParamiterService";
|
||||||
|
import User from "../models/User";
|
||||||
|
import { del, get, set } from "../helpers/RedisClient";
|
||||||
|
|
||||||
|
import {
|
||||||
|
startWhoIsOnlineMonitor,
|
||||||
|
stopWhoIsOnlineMonitor
|
||||||
|
} from "../helpers/WhoIsOnlineMonitor";
|
||||||
|
import UserOnlineTIme from "../models/UserOnlineTime";
|
||||||
|
|
||||||
|
import { format, subMonths } from "date-fns";
|
||||||
|
import { ptBR } from "date-fns/locale";
|
||||||
|
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
|
||||||
|
import { splitDateTime } from "../helpers/SplitDateTime";
|
||||||
|
import ListUserByWhatsappQueuesService from "../services/UserServices/ListUserByWhatsappQueuesService";
|
||||||
|
import { json } from "sequelize";
|
||||||
|
import { getSettingValue } from "../helpers/WhaticketSettings";
|
||||||
|
import { setBotInfo } from "../helpers/SetBotInfo";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
profile?: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { searchParam, pageNumber, profile } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { users, count, hasMore } = await ListUsersService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
profile
|
||||||
|
});
|
||||||
|
|
||||||
|
if (req.user.profile !== "master") {
|
||||||
|
let auxUsers: Array<object> = [];
|
||||||
|
|
||||||
|
// for (var user of users) {
|
||||||
|
// if (user.profile !== 'master') {
|
||||||
|
// auxUsers.push(user)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (var user of users) {
|
||||||
|
if (user.profile !== "master") {
|
||||||
|
if (req.user.profile == "supervisor" && user.profile == "admin")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auxUsers.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ users: auxUsers, count, hasMore });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ users, count, hasMore });
|
||||||
|
|
||||||
|
// const { users, count, hasMore } = await ListUsersService({
|
||||||
|
// searchParam,
|
||||||
|
// pageNumber
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if(req.user.profile!=='master'){
|
||||||
|
|
||||||
|
// let auxUsers: Array<object> = [];
|
||||||
|
|
||||||
|
// for (var user of users) {
|
||||||
|
// if(user.profile!=='master'){
|
||||||
|
// auxUsers.push(user)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res.json({ users: auxUsers, count, hasMore });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res.json({ users, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const usersByWhatsappQueue = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
// const { profile } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
// const users = await ListUser({
|
||||||
|
// profile
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return res.json({ users });
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const all = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
let { userId, profile, transferToOtherQueues }: any = req.query as IndexQuery;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"userId: ",
|
||||||
|
userId,
|
||||||
|
" | profile: ",
|
||||||
|
profile,
|
||||||
|
' | getSettingValue("queueTransferByWhatsappScope")?.value: ',
|
||||||
|
getSettingValue("queueTransferByWhatsappScope")?.value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled" && !transferToOtherQueues) {
|
||||||
|
if (!userId) return res.json({ users: [], queues: [] });
|
||||||
|
|
||||||
|
const obj = await ListUserByWhatsappQueuesService(
|
||||||
|
userId,
|
||||||
|
'"admin", "user", "supervisor"'
|
||||||
|
);
|
||||||
|
|
||||||
|
const usersByWhatsqueue = obj.users;
|
||||||
|
const queues = obj.queues;
|
||||||
|
|
||||||
|
let userIds = usersByWhatsqueue.map((w: any) => w.userId);
|
||||||
|
|
||||||
|
const users = await ListUser({
|
||||||
|
userIds
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ users, queues });
|
||||||
|
} else {
|
||||||
|
const users = await ListUser({
|
||||||
|
profile
|
||||||
|
});
|
||||||
|
return res.json({ users });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
name,
|
||||||
|
profile,
|
||||||
|
positionId,
|
||||||
|
queueIds,
|
||||||
|
transferToOtherQueues
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
console.log("===========> req.url: ", req.url);
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.url === "/user" &&
|
||||||
|
getSettingValue("userCreation")?.value == "disabled" &&
|
||||||
|
req.user.profile == "admin"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
} else if (
|
||||||
|
req.url === "/signup" &&
|
||||||
|
getSettingValue("userCreation")?.value == "disabled"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_USER_CREATION_DISABLED", 403);
|
||||||
|
} else if (req.user.profile !== "master") {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await CreateUserService({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
name,
|
||||||
|
positionId,
|
||||||
|
profile,
|
||||||
|
queueIds,
|
||||||
|
transferToOtherQueues
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const { id, name } = user;
|
||||||
|
await set(`user:${id}`, { id, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "create",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
// await stopWhoIsOnlineMonitor()
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
return res.status(200).json(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
const user = await ShowUserService(userId);
|
||||||
|
|
||||||
|
return res.status(200).json(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logoutUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
await stopWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
let onlineTime = {
|
||||||
|
userId: userId,
|
||||||
|
status: "logout..."
|
||||||
|
};
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("onlineStatus", {
|
||||||
|
action: "logout",
|
||||||
|
userOnlineTime: onlineTime
|
||||||
|
});
|
||||||
|
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
//
|
||||||
|
|
||||||
|
return res.status(200).json({});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (
|
||||||
|
req.user.profile !== "admin" &&
|
||||||
|
req.user.profile !== "master" &&
|
||||||
|
req.user.profile !== "supervisor"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId } = req.params;
|
||||||
|
const userData = req.body;
|
||||||
|
|
||||||
|
const dateToday = splitDateTime(
|
||||||
|
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
const tenMonthsAgo = subMonths(currentDate, 10);
|
||||||
|
const formattedDate = format(tenMonthsAgo, "yyyy-MM-dd");
|
||||||
|
console.log("dateToday.fullDate: ", dateToday.fullDate);
|
||||||
|
console.log("formattedDate 10 months ago: ", formattedDate);
|
||||||
|
|
||||||
|
const openByUserOnQueue: any[] = await CountTicketsByUserQueue({
|
||||||
|
startDate: formattedDate,
|
||||||
|
endDate: dateToday.fullDate,
|
||||||
|
status: "open",
|
||||||
|
clientChatStart: true,
|
||||||
|
userId: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log('------> openByUserOnQueue: ', openByUserOnQueue)
|
||||||
|
// console.log()
|
||||||
|
// console.log('------> 1 userData.queueIds: ', userData.queueIds)
|
||||||
|
|
||||||
|
let userQueuesAttendance = [];
|
||||||
|
|
||||||
|
if ((openByUserOnQueue && openByUserOnQueue.length) > 0) {
|
||||||
|
userQueuesAttendance = openByUserOnQueue.filter(
|
||||||
|
(e: any) => !userData.queueIds.includes(e.queueId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userQueuesAttendance && userQueuesAttendance.length > 0) {
|
||||||
|
const queueInAttendance = userQueuesAttendance.map(e => e.queueId);
|
||||||
|
|
||||||
|
const mergedSet = new Set([...userData.queueIds, ...queueInAttendance]);
|
||||||
|
|
||||||
|
// Convert the Set back to an array
|
||||||
|
userData.queueIds = Array.from(mergedSet);
|
||||||
|
|
||||||
|
// console.log('------> 2 userData.queueIds: ', userData.queueIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let user: any = await UpdateUserService({ userData, userId });
|
||||||
|
|
||||||
|
await setBotInfo(user);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const { id, name } = user;
|
||||||
|
await set(`user:${id}`, { id, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "update",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
user.userQueuesAttendance = userQueuesAttendance;
|
||||||
|
|
||||||
|
return res.status(200).json(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
if (req.user.profile !== "master") {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DeleteUserService(userId);
|
||||||
|
|
||||||
|
del(`user:${userId}`);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "delete",
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
//test del
|
||||||
|
await stopWhoIsOnlineMonitor();
|
||||||
|
|
||||||
|
io.emit("onlineStatus", {
|
||||||
|
action: "delete",
|
||||||
|
userOnlineTime: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
await startWhoIsOnlineMonitor();
|
||||||
|
//
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "User deleted" });
|
||||||
|
};
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import omnihitDashboardSession from "../helpers/OmnhitDashboardSession";
|
||||||
|
|
||||||
|
// type IndexQuery = {
|
||||||
|
// centro_custo: string;
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const wbotMonitorRemote = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { action, whatsappId, reason } = req.body;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"action: ",
|
||||||
|
action,
|
||||||
|
" | whatsappId: ",
|
||||||
|
whatsappId,
|
||||||
|
" | reason: ",
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("-----------> ACTION: ", req.body["action"]);
|
||||||
|
|
||||||
|
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
|
||||||
|
|
||||||
|
if (whatsapp) {
|
||||||
|
if (action === "disconnected") {
|
||||||
|
logger.info(`Disconnected session: ${whatsapp.name}, reason: ${reason}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: any = {};
|
||||||
|
data.whatsapp = { ...whatsapp, DB: process.env.DB_NAME };
|
||||||
|
data.action = "update";
|
||||||
|
await omnihitDashboardSession(data);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Ok" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wbotMonitorQrcodeRemote = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.body;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"-----------> QRCODE MONITOR whatsappId: ",
|
||||||
|
req.body["whatsappId"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
|
||||||
|
// let whatsapp = await ShowWhatsAppService(whatsappId)
|
||||||
|
|
||||||
|
if (whatsapp) {
|
||||||
|
let data: any = {};
|
||||||
|
data.whatsapp = { ...whatsapp, DB: process.env.DB_NAME };
|
||||||
|
data.action = "update";
|
||||||
|
await omnihitDashboardSession(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whatsapp) {
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Ok" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wbotMonitorQrcodeNumberRead = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { number } = req.body;
|
||||||
|
|
||||||
|
console.log("-----------> number read: ", number);
|
||||||
|
|
||||||
|
const msg = `Numero lido: ${number} \nEssa sessão de whatsapp foi desconectada porque o numero que esta descrito no nome dessa sessão não corresponde ao numero lido!`;
|
||||||
|
let data: any = {};
|
||||||
|
data.msg = msg;
|
||||||
|
data.action = "error";
|
||||||
|
await omnihitDashboardSession(data);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "error",
|
||||||
|
msg: `Numero lido: ${number} \nEssa sessão de whatsapp foi desconectada porque o numero que esta descrito no nome dessa sessão não corresponde ao numero lido!`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Ok" });
|
||||||
|
};
|
|
@ -0,0 +1,628 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import { removeWbot } from "../libs/wbot";
|
||||||
|
import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
|
||||||
|
|
||||||
|
import { removeDir } from "../helpers/DeleteDirectory";
|
||||||
|
|
||||||
|
import CreateWhatsAppService from "../services/WhatsappService/CreateWhatsAppService";
|
||||||
|
import DeleteWhatsAppService from "../services/WhatsappService/DeleteWhatsAppService";
|
||||||
|
import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService";
|
||||||
|
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
|
||||||
|
import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
|
||||||
|
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
import getNumberFromName from "../helpers/GetNumberSequence";
|
||||||
|
import phoneNumberStart from "../helpers/PhoneNumberStatusCode";
|
||||||
|
|
||||||
|
import path, { join } from "path";
|
||||||
|
import validatePhoneName from "../helpers/ValidatePhoneName";
|
||||||
|
import postData from "../helpers/AxiosPost";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import Message from "../models/Message";
|
||||||
|
import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
|
||||||
|
import {
|
||||||
|
handleMessage,
|
||||||
|
handleMsgAck,
|
||||||
|
verifyContact,
|
||||||
|
verifyMessage
|
||||||
|
} from "../services/WbotServices/wbotMessageListener";
|
||||||
|
import Contact from "../models/Contact";
|
||||||
|
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
|
||||||
|
import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp";
|
||||||
|
import ShowUserService from "../services/UserServices/ShowUserService";
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import receiveWhatsAppMediaOfficialAPI from "../helpers/ReceiveWhatsAppMediaOfficialAPI";
|
||||||
|
|
||||||
|
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
|
||||||
|
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
|
||||||
|
import { getSettingValue } from "../helpers/WhaticketSettings";
|
||||||
|
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
|
||||||
|
import SettingTicket from "../models/SettingTicket";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import { del, get, set } from "../helpers/RedisClient";
|
||||||
|
|
||||||
|
interface WhatsappData {
|
||||||
|
name: string;
|
||||||
|
queueIds: number[];
|
||||||
|
url: string;
|
||||||
|
urlApi: string;
|
||||||
|
greetingMessage?: string;
|
||||||
|
farewellMessage?: string;
|
||||||
|
status?: string;
|
||||||
|
isDefault?: boolean;
|
||||||
|
isOfficial?: boolean;
|
||||||
|
phoneNumberId?: string;
|
||||||
|
number?: string;
|
||||||
|
wabaId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count: number = 0;
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
let whatsapps = await ListWhatsAppsService();
|
||||||
|
|
||||||
|
if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
|
||||||
|
// Atualizar isso quando tiver tempo
|
||||||
|
if (count > 12) count = 0;
|
||||||
|
if (count == 0) {
|
||||||
|
for (let i in whatsapps) {
|
||||||
|
const { id, wabaId, isOfficial } = whatsapps[i];
|
||||||
|
|
||||||
|
if (isOfficial && wabaId) {
|
||||||
|
try {
|
||||||
|
const info = await whatsappOfficialNumberInfo(wabaId);
|
||||||
|
if (info) {
|
||||||
|
const whatsapp = await Whatsapp.findByPk(id);
|
||||||
|
|
||||||
|
if (whatsapp) {
|
||||||
|
whatsapp.update({
|
||||||
|
classification: info.quality_rating
|
||||||
|
});
|
||||||
|
whatsapps[i].classification = info.quality_rating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"error on try update classification number from oficial whatsapp in WhatsappController.ts: ",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("count: ", count);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const whatsAppOfficialMatchQueue = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { userId, queueId }: any = req.query;
|
||||||
|
|
||||||
|
let whatsapps = await GetDefaultWhatsApp({ userId, queueId });
|
||||||
|
|
||||||
|
if (whatsapps && Array.isArray(whatsapps)) {
|
||||||
|
const uniqueWhatsApps = whatsapps.filter(
|
||||||
|
(whatsapp, index, self) =>
|
||||||
|
index === self.findIndex(w => w.number === whatsapp.number)
|
||||||
|
);
|
||||||
|
whatsapps = uniqueWhatsApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const whatsAppOfficialMatchQueueUser = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { userId, queueId }: any = req.query;
|
||||||
|
|
||||||
|
let whatsApps: any = await ListWhatsAppsService();
|
||||||
|
let user: any = await ShowUserService(userId);
|
||||||
|
|
||||||
|
// console.log(JSON.stringify(user, null, 2));
|
||||||
|
|
||||||
|
let queuesConnected = whatsApps
|
||||||
|
.filter((w: any) => w.status === "CONNECTED")
|
||||||
|
.map((item: any) => {
|
||||||
|
const { queues } = item;
|
||||||
|
return {
|
||||||
|
queues: queues.map((q: any) => {
|
||||||
|
return { id: q.id };
|
||||||
|
})
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.flatMap((item: any) => item.queues.map((queue: any) => queue.id));
|
||||||
|
|
||||||
|
queuesConnected = [...new Set(queuesConnected)].map(q => {
|
||||||
|
return { id: q };
|
||||||
|
});
|
||||||
|
|
||||||
|
const userQueues = user.queues.map((item: any) => {
|
||||||
|
const { id, name, color } = item;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
disable: queuesConnected.find((queue: any) => queue.id === id)
|
||||||
|
? false
|
||||||
|
: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(userQueues);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const media = async (req: Request, res: Response) => {
|
||||||
|
const { filename } = req.params;
|
||||||
|
|
||||||
|
const filePath = join(__dirname, "..", "..", "..", "..", "public", filename);
|
||||||
|
|
||||||
|
console.log("filePath: ", filePath);
|
||||||
|
|
||||||
|
console.log(filename);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return res.status(404).json({ message: "File not folund!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set appropriate headers for the download.
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename=${filename}`);
|
||||||
|
res.sendFile(filePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const weebhook = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
// console.log(JSON.stringify(req.body, null, 2));
|
||||||
|
|
||||||
|
console.log("req.method: ", req.method);
|
||||||
|
|
||||||
|
if (req.method == "GET") {
|
||||||
|
/**
|
||||||
|
* UPDATE YOUR VERIFY TOKEN
|
||||||
|
*This will be the Verify Token value when you set up webhook
|
||||||
|
**/
|
||||||
|
const verify_token = process.env.VERIFY_TOKEN;
|
||||||
|
|
||||||
|
// Parse params from the webhook verification request
|
||||||
|
let mode = req.query["hub.mode"];
|
||||||
|
let token = req.query["hub.verify_token"];
|
||||||
|
let challenge = req.query["hub.challenge"];
|
||||||
|
|
||||||
|
// Check if a token and mode were sent
|
||||||
|
if (mode && token) {
|
||||||
|
// Check the mode and token sent are correct
|
||||||
|
if (mode === "subscribe" && token === verify_token) {
|
||||||
|
// Respond with 200 OK and challenge token from the request
|
||||||
|
console.log("WEBHOOK_VERIFIED");
|
||||||
|
return res.status(200).send(challenge);
|
||||||
|
} else {
|
||||||
|
// Responds with '403 Forbidden' if verify tokens do not match
|
||||||
|
return res.sendStatus(403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendStatus(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MESSAGE
|
||||||
|
if (req.body.object) {
|
||||||
|
if (
|
||||||
|
req.body.entry &&
|
||||||
|
req.body.entry[0].changes &&
|
||||||
|
req.body.entry[0].changes[0] &&
|
||||||
|
req.body.entry[0].changes[0].value.messages &&
|
||||||
|
req.body.entry[0].changes[0].value.messages[0]
|
||||||
|
) {
|
||||||
|
const message = req.body.entry[0].changes[0].value.messages[0];
|
||||||
|
const contact_from = message.from; // extract the phone number from the webhook payload
|
||||||
|
const contact_to =
|
||||||
|
req.body.entry[0].changes[0].value.metadata.display_phone_number;
|
||||||
|
let type = message.type;
|
||||||
|
|
||||||
|
const contact_to_exist = await get({
|
||||||
|
key: "whatsapp:*",
|
||||||
|
value: `${contact_to}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contact_to_exist == null) {
|
||||||
|
console.log(
|
||||||
|
"WHATSAPP OFFICIAL",
|
||||||
|
" | CONCTACT_FROM: ",
|
||||||
|
contact_from,
|
||||||
|
" | CONTACT_TO: ",
|
||||||
|
contact_to
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"NUMBER IGNORED. WHATSAPP NUMBER FROM ANOTHER OMNIHIT APPLICATION!"
|
||||||
|
);
|
||||||
|
return res.status(403).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let wbot = {};
|
||||||
|
let msg = {};
|
||||||
|
let contacts = req.body.entry[0].changes[0].value.contacts[0];
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
...msg,
|
||||||
|
id: { id: message.id },
|
||||||
|
fromMe: false,
|
||||||
|
type: type,
|
||||||
|
read: false,
|
||||||
|
hasMedia: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// NEW
|
||||||
|
const whatsapp = await ShowWhatsAppService(null, {
|
||||||
|
number: contact_to
|
||||||
|
});
|
||||||
|
|
||||||
|
if (type == "text") {
|
||||||
|
if (!message?.text?.body) {
|
||||||
|
return res.status(400).json({ error: "body not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
type = "chat";
|
||||||
|
msg = {
|
||||||
|
...msg,
|
||||||
|
body: message.text.body, // extract the message text from the webhook payload,
|
||||||
|
type
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (!message[message?.type]?.id) {
|
||||||
|
return res.status(400).json({ error: "id not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaId = message[message.type].id;
|
||||||
|
const mimetype = message[message.type].mime_type;
|
||||||
|
|
||||||
|
let filename = await receiveWhatsAppMediaOfficialAPI(
|
||||||
|
mediaId,
|
||||||
|
whatsapp.phoneNumberId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!filename) throw new AppError("There was an error");
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
...msg,
|
||||||
|
hasMedia: true
|
||||||
|
};
|
||||||
|
|
||||||
|
wbot = { ...wbot, media: { filename, mimetype } };
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = { ...msg, phoneNumberId: whatsapp.phoneNumberId };
|
||||||
|
|
||||||
|
console.log("from: ", contact_from);
|
||||||
|
console.log("to: ", contact_to);
|
||||||
|
console.log("msg type: ", type);
|
||||||
|
|
||||||
|
wbot = {
|
||||||
|
...wbot,
|
||||||
|
id: whatsapp.id,
|
||||||
|
msgContact: {
|
||||||
|
number: contact_from,
|
||||||
|
name: contacts?.profile?.name
|
||||||
|
},
|
||||||
|
chat: { isGroup: false, unreadCount: 1 },
|
||||||
|
quotedMsg: message && message?.context ? message.context.id : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMessage(msg, wbot, true);
|
||||||
|
|
||||||
|
return res.sendStatus(200);
|
||||||
|
}
|
||||||
|
// STATUS MESSAGE SENT
|
||||||
|
else if (
|
||||||
|
req.body.entry &&
|
||||||
|
req.body.entry[0].changes &&
|
||||||
|
req.body.entry[0].changes[0] &&
|
||||||
|
req.body.entry[0].changes[0].value.statuses &&
|
||||||
|
req.body.entry[0].changes[0].value.statuses[0]
|
||||||
|
) {
|
||||||
|
const id = req.body.entry[0].changes[0].value.statuses[0].id;
|
||||||
|
const ack = req.body.entry[0].changes[0].value.statuses[0].status;
|
||||||
|
handleMsgAck(id, ack, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendStatus(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
let {
|
||||||
|
name,
|
||||||
|
status,
|
||||||
|
isDefault,
|
||||||
|
greetingMessage,
|
||||||
|
farewellMessage,
|
||||||
|
queueIds,
|
||||||
|
url,
|
||||||
|
urlApi,
|
||||||
|
phoneNumberId,
|
||||||
|
wabaId,
|
||||||
|
isOfficial,
|
||||||
|
number
|
||||||
|
}: WhatsappData = req.body;
|
||||||
|
|
||||||
|
if (req.user.profile !== "master") {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalid = checkWhatsAppData({
|
||||||
|
urlApi,
|
||||||
|
isOfficial,
|
||||||
|
phoneNumberId,
|
||||||
|
wabaId,
|
||||||
|
number
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOfficial) {
|
||||||
|
urlApi = "";
|
||||||
|
url = "";
|
||||||
|
} else if (!isOfficial) {
|
||||||
|
phoneNumberId = "";
|
||||||
|
wabaId = "";
|
||||||
|
number = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let invalidPhoneName = validatePhoneName(name);
|
||||||
|
|
||||||
|
if (invalidPhoneName) {
|
||||||
|
return res.status(200).json({ message: invalidPhoneName });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
urlApi,
|
||||||
|
status,
|
||||||
|
isDefault,
|
||||||
|
greetingMessage,
|
||||||
|
farewellMessage,
|
||||||
|
queueIds,
|
||||||
|
phoneNumberId,
|
||||||
|
wabaId,
|
||||||
|
isOfficial,
|
||||||
|
number
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("whatsapp.id: ", whatsapp.id);
|
||||||
|
|
||||||
|
if (!isOfficial) {
|
||||||
|
postData(`${whatsapp.urlApi}/api/session`, {
|
||||||
|
app_name: process.env.APP_NAME,
|
||||||
|
whatsappId: whatsapp.id,
|
||||||
|
number: getNumberFromName(name),
|
||||||
|
client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await set(
|
||||||
|
`whatsapp:${whatsapp.id}`,
|
||||||
|
JSON.stringify({
|
||||||
|
number: whatsapp?.number,
|
||||||
|
id: whatsapp?.id,
|
||||||
|
greetingMessage: whatsapp?.greetingMessage,
|
||||||
|
phoneNumberId: whatsapp?.phoneNumberId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oldDefaultWhatsapp) {
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp: oldDefaultWhatsapp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapp);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
const whatsapp = await ShowWhatsAppService(whatsappId);
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapp);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
const whatsappData = req.body;
|
||||||
|
|
||||||
|
let invalidPhoneName = validatePhoneName(whatsappData.name);
|
||||||
|
|
||||||
|
if (invalidPhoneName) {
|
||||||
|
return res.status(200).json({ message: invalidPhoneName });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { urlApi, isOfficial, phoneNumberId, number, wabaId } = whatsappData;
|
||||||
|
|
||||||
|
const invalid = checkWhatsAppData({
|
||||||
|
urlApi,
|
||||||
|
isOfficial,
|
||||||
|
phoneNumberId,
|
||||||
|
wabaId,
|
||||||
|
number
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return res.status(400).json(invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOfficial) {
|
||||||
|
whatsappData.urlApi = "";
|
||||||
|
whatsappData.url = "";
|
||||||
|
} else if (!isOfficial) {
|
||||||
|
whatsappData.phoneNumberId = "";
|
||||||
|
whatsappData.wabaId = "";
|
||||||
|
whatsappData.number = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({
|
||||||
|
whatsappData,
|
||||||
|
whatsappId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!whatsappData?.isOfficial) {
|
||||||
|
postData(`${whatsapp.urlApi}/api/session`, {
|
||||||
|
app_name: process.env.APP_NAME,
|
||||||
|
whatsappId: whatsapp.id,
|
||||||
|
number: getNumberFromName(whatsapp.name),
|
||||||
|
client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await set(
|
||||||
|
`whatsapp:${whatsapp.id}`,
|
||||||
|
JSON.stringify({
|
||||||
|
number: whatsapp?.number,
|
||||||
|
id: whatsapp?.id,
|
||||||
|
greetingMessage: whatsapp?.greetingMessage,
|
||||||
|
phoneNumberId: whatsapp?.phoneNumberId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oldDefaultWhatsapp) {
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp: oldDefaultWhatsapp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapp);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "master") {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
|
||||||
|
|
||||||
|
if (!whatsapp?.isOfficial) {
|
||||||
|
postData(`${whatsapp.urlApi}/api/session/del`, {
|
||||||
|
app_name: process.env.APP_NAME,
|
||||||
|
whatsappId: whatsappId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await del(`whatsapp:${whatsappId}`);
|
||||||
|
|
||||||
|
let whats = await ListWhatsAppsNumber(whatsappId);
|
||||||
|
|
||||||
|
// Remove tickets business hours config
|
||||||
|
if (whats?.whatsapps?.length == 1) {
|
||||||
|
const configIds = await SettingTicket.findAll({
|
||||||
|
where: { number: whats?.whatsapps[0]?.number },
|
||||||
|
raw: true,
|
||||||
|
attributes: ["id"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const whatsappTicketConfig = await SettingTicket.findOne({
|
||||||
|
where: { number: whats.whatsapps[0].number }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (whatsappTicketConfig) {
|
||||||
|
try {
|
||||||
|
await SettingTicket.destroy({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
[Op.in]: configIds.map(config => config.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"Error on delete SettingTicket by number: ",
|
||||||
|
whats?.whatsapps[0]?.number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await DeleteWhatsAppService(whatsappId);
|
||||||
|
|
||||||
|
removeDir(
|
||||||
|
path.join(process.cwd(), ".wwebjs_auth", `session-bd_${whatsappId}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
removeDir(
|
||||||
|
path.join(
|
||||||
|
process.cwd(),
|
||||||
|
".wwebjs_auth",
|
||||||
|
"sessions",
|
||||||
|
`session-bd_${whatsappId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
removeWbot(+whatsappId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "delete",
|
||||||
|
whatsappId: +whatsappId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Whatsapp deleted." });
|
||||||
|
};
|
||||||
|
|
||||||
|
interface WhatsappDataValidate {
|
||||||
|
urlApi?: string;
|
||||||
|
isOfficial?: boolean;
|
||||||
|
phoneNumberId?: string;
|
||||||
|
wabaId?: string;
|
||||||
|
number?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkWhatsAppData = ({
|
||||||
|
urlApi,
|
||||||
|
isOfficial,
|
||||||
|
phoneNumberId,
|
||||||
|
wabaId,
|
||||||
|
number
|
||||||
|
}: WhatsappDataValidate) => {
|
||||||
|
if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) {
|
||||||
|
return { message: "Phone number Id is required!" };
|
||||||
|
} else if (isOfficial && (!wabaId || wabaId.trim() == "")) {
|
||||||
|
return { message: "WABA ID is required!" };
|
||||||
|
} else if (isOfficial && (!number || number.trim() == "")) {
|
||||||
|
return { message: "Phone number is required!" };
|
||||||
|
} else if (!isOfficial && (!urlApi || urlApi.trim() == "")) {
|
||||||
|
return { message: "urlApi is required!" };
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getWbot, removeWbot } from "../libs/wbot";
|
||||||
|
import { removeDir } from "../helpers/DeleteDirectory";
|
||||||
|
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
|
||||||
|
import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
|
||||||
|
import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
|
||||||
|
|
||||||
|
import { restartWhatsSession } from "../helpers/RestartWhatsSession";
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import { stat } from "fs";
|
||||||
|
|
||||||
|
import { setRestoreControll, getRestoreControll, shifRestoreControll } from "../helpers/RestoreControll";
|
||||||
|
|
||||||
|
import autoRestore from "../helpers/AutoRestore";
|
||||||
|
import axios from "axios";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import endPointQuery from "../helpers/EndPointQuery";
|
||||||
|
|
||||||
|
|
||||||
|
// let lstRestore: any = []
|
||||||
|
|
||||||
|
const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
const whatsapp = await ShowWhatsAppService(whatsappId);
|
||||||
|
|
||||||
|
StartWhatsAppSession(whatsapp);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Starting session." });
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
const { whatsapp } = await UpdateWhatsAppService({
|
||||||
|
whatsappId,
|
||||||
|
whatsappData: { session: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
StartWhatsAppSession(whatsapp);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Starting session." });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const restart = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
|
||||||
|
if (Object.keys(req.body).length > 0) {
|
||||||
|
|
||||||
|
let lstRestore: any = getRestoreControll()
|
||||||
|
|
||||||
|
for (let i = 0; i < lstRestore.length; i++) {
|
||||||
|
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: { 'id': +lstRestore[i].id, 'disabled': true }
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let whatsapp: any = await Whatsapp.findByPk(whatsappId)
|
||||||
|
|
||||||
|
if (whatsapp) {
|
||||||
|
|
||||||
|
await whatsapp.update({ status: 'OPENING' });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
whatsapp = JSON.parse(JSON.stringify(whatsapp))
|
||||||
|
|
||||||
|
console.log('whatsapp url: ', whatsapp.url)
|
||||||
|
|
||||||
|
await endPointQuery(`${whatsapp.url}/api/restore`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
autoRestore(whatsappId)
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Starting session." });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const remove = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
// const whatsapp = await ShowWhatsAppService(whatsappId);
|
||||||
|
|
||||||
|
// const wbot = getWbot(whatsapp.id);
|
||||||
|
|
||||||
|
// await wbot.logout();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const wbot_url = await getWbot(whatsappId);
|
||||||
|
|
||||||
|
let response = await axios.post(`${wbot_url}/api/disconnect`);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
console.log('There was an error on try disconnect the whatsapp id: ', whatsappId)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Session disconnected." });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { store, remove, update, restart };
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Sequelize } from "sequelize-typescript";
|
||||||
|
import User from "../models/User";
|
||||||
|
import Setting from "../models/Setting";
|
||||||
|
import Contact from "../models/Contact";
|
||||||
|
import Ticket from "../models/Ticket";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import ContactCustomField from "../models/ContactCustomField";
|
||||||
|
import Message from "../models/Message";
|
||||||
|
import Queue from "../models/Queue";
|
||||||
|
import WhatsappQueue from "../models/WhatsappQueue";
|
||||||
|
import UserQueue from "../models/UserQueue";
|
||||||
|
import QuickAnswer from "../models/QuickAnswer";
|
||||||
|
|
||||||
|
import SchedulingNotify from "../models/SchedulingNotify";
|
||||||
|
import StatusChatEnd from "../models/StatusChatEnd";
|
||||||
|
import UserOnlineTime from "../models/UserOnlineTime";
|
||||||
|
import SettingTicket from "../models/SettingTicket";
|
||||||
|
import QuickAnswerQueue from "../models/QuickAnswerQueue";
|
||||||
|
import Position from "../models/Position"
|
||||||
|
import ContactQueue from "../models/ContactQueues"
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const dbConfig = require("../config/database");
|
||||||
|
// import dbConfig from "../config/database";
|
||||||
|
|
||||||
|
const sequelize = new Sequelize(dbConfig);
|
||||||
|
|
||||||
|
const models = [
|
||||||
|
User,
|
||||||
|
Contact,
|
||||||
|
Ticket,
|
||||||
|
Message,
|
||||||
|
Whatsapp,
|
||||||
|
ContactCustomField,
|
||||||
|
Setting,
|
||||||
|
Queue,
|
||||||
|
WhatsappQueue,
|
||||||
|
UserQueue,
|
||||||
|
QuickAnswer,
|
||||||
|
QuickAnswerQueue,
|
||||||
|
SchedulingNotify,
|
||||||
|
StatusChatEnd,
|
||||||
|
UserOnlineTime,
|
||||||
|
SettingTicket,
|
||||||
|
Position,
|
||||||
|
ContactQueue
|
||||||
|
];
|
||||||
|
|
||||||
|
sequelize.addModels(models);
|
||||||
|
|
||||||
|
export default sequelize;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Users", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
passwordHash: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Users");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Contacts", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
profilePicUrl: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Contacts");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Tickets", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
defaultValue: "pending",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
lastMessage: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
contactId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Contacts", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE"
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Users", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE(6),
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE(6),
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Tickets");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Messages", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
ack: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
mediaType: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
mediaUrl: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Users", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
},
|
||||||
|
ticketId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Tickets", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE(6),
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE(6),
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Messages");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Whatsapps", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
},
|
||||||
|
qrcode: {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
battery: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
plugged: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Whatsapps");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("ContactCustomFields", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
contactId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Contacts", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("ContactCustomFields");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Contacts", "email", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: ""
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Contacts", "email");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "userId");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "userId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Users", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "fromMe", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "fromMe");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.changeColumn("Tickets", "lastMessage", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.changeColumn("Tickets", "lastMessage", {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Users", "profile", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "admin"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Users", "profile");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Settings", {
|
||||||
|
key: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Settings");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "name", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "name");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "default", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "default");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "whatsappId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Whatsapps", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "whatsappId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("Whatsapps", "default", "isDefault");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("Whatsapps", "isDefault", "default");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "isDeleted", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "isDeleted");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Users", "tokenVersion", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Users", "tokenVersion");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "isGroup", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "isGroup");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Contacts", "isGroup", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Contacts", "isGroup");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "contactId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Contacts", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "contactId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "vcardContactId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Contacts", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "vcardContactId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "vcardContactId");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "vcardContactId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Contacts", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "retries", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
defaultValue: 0,
|
||||||
|
allowNull: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "retries");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "quotedMsgId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: { model: "Messages", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "quotedMsgId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "unreadMessages", {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "unreadMessages");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Queues", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
greetingMessage: {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Queues");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "queueId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Queues", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "queueId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("WhatsappQueues", {
|
||||||
|
whatsappId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
queueId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("WhatsappQueues");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("UserQueues", {
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
queueId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("UserQueues");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "greetingMessage", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "greetingMessage");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("QuickAnswers", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("QuickAnswers");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "farewellMessage", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "farewellMessage");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("Schedules", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("Schedules");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("SchedulingNotifies", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
scheduleId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Schedules", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
ticketId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Tickets", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
schedulingDate: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
schedulingTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("SchedulingNotifies");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameTable("Schedules", "StatusChatEnd");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameTable("StatusChatEnd", "Schedules");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("SchedulingNotifies", "scheduleId", "statusChatEndId");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("SchedulingNotifies", "statusChatEndId", "scheduleId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameTable("StatusChatEnd", "StatusChatEnds");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameTable("StatusChatEnds", "StatusChatEnd");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.changeColumn("SchedulingNotifies", "statusChatEndId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "StatusChatEnds", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.changeColumn("SchedulingNotifies", "statusChatEndId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Schedules", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("UserOnlineTimes", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Users", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
onlineTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("UserOnlineTimes");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "statusChatEnd", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "statusChatEnd");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "number", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "number");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "url", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "url");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "urlApi", {
|
||||||
|
type: DataTypes.TEXT
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "urlApi");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize"
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.createTable("SettingTickets", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
startTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
endTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("SettingTickets");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "phoneNumberId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "phoneNumberId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "phoneNumberId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "phoneNumberId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "phoneNumberId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "phoneNumberId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "isOfficial", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "isOfficial");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "wabaId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "wabaId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Whatsapps", "classification", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Whatsapps", "classification");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("SettingTickets", "number", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("SettingTickets", "number");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Users", "positionCompany", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Users", "positionCompany");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Messages", "fromAgent", {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Messages", "fromAgent");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Users", "secondaryId", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Users", "secondaryId");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "statusChatEndId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "StatusChatEnds", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL",
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "statusChatEndId");
|
||||||
|
}
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue