adding configurations made in EL-lojas to correct release and ordering of remote tickets

updates-el-lojas
Adriano 2024-05-31 10:33:52 -03:00
commit b59cc61b6d
482 changed files with 46842 additions and 0 deletions

21
LICENSE 100644
View File

@ -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.

441
README.md 100644
View File

@ -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&currency_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&currency_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.

BIN
backend.7z 100644

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
/*.js
node_modules
dist

View File

@ -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": {}
}
}
}

16
backend/.gitignore vendored 100644
View File

@ -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/

View File

@ -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")
};

View File

@ -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,
};

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
singleQuote: false,
trailingComma: "none",
arrowParens: "avoid"
};

View File

@ -0,0 +1,5 @@
declare namespace Express {
export interface Request {
user: { id: string; profile: string };
}
}

View File

@ -0,0 +1 @@
declare module "qrcode-terminal";

View File

@ -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");
}
});
});

View File

@ -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);
}
});
});

View File

@ -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
);
});
});

View File

@ -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);
});
});

View File

@ -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
);
});
});

View File

@ -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
);
});
});

View File

@ -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 };

46
backend/src/app.ts 100644
View File

@ -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;

View File

@ -0,0 +1,5 @@
import dotenv from "dotenv";
dotenv.config({
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
});

View File

@ -0,0 +1,6 @@
export default {
secret: process.env.JWT_SECRET || "mysecret",
expiresIn: "15m",
refreshSecret: process.env.JWT_REFRESH_SECRET || "myanothersecret",
refreshExpiresIn: "7d"
};

View File

@ -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
};

View File

@ -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);
}
})
};

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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" });
};

View File

@ -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();
};

View File

@ -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" });
};

View File

@ -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();
};

View File

@ -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" });
};

View File

@ -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 });
};

View File

@ -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();
};

View File

@ -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();
};

View File

@ -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);
};

View File

@ -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" });
};

View File

@ -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" });
};

View File

@ -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" });
};

View File

@ -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" });
};

View File

@ -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!" };
}
};

View File

@ -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 };

View File

@ -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;

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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"
});
}
};

View File

@ -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");
}
};

View File

@ -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
});
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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"
});
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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
});
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
}

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -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