mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-20 20:59:16 +00:00
Merge pull request #24 from canove/typescript
Typescript adoption on backend code.
This commit is contained in:
73
README.md
73
README.md
@@ -64,25 +64,71 @@ Install puppeteer dependencies:
|
|||||||
sudo apt-get install -y 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
|
sudo apt-get install -y 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
|
Clone this repo
|
||||||
- On backend folder:
|
|
||||||
- Copy .env.example to .env and fill it with database details.
|
```bash
|
||||||
- Install dependecies: `npm install` (Only in the first time)
|
git clone https://github.com/canove/whaticket/ whaticket
|
||||||
- Create database tables: `npx sequelize db:migrate` (Only in the first time)
|
```
|
||||||
- Fill database with initial values: `npx sequelize db:seed:all`
|
|
||||||
- Start backend: `npm start`
|
Go to backend folder and create .env file:
|
||||||
- In another terminal, on frontend folder:
|
|
||||||
- Copy .env.example to .env and fill it with backend URL (normally localhost:port)
|
```bash
|
||||||
- Install dependecies: `npm install` (Only in the first time)
|
cp .env.example .env
|
||||||
- Start frontend: `npm start`
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill `.env` file with environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_ENV=DEVELOPMENT #it helps on debugging
|
||||||
|
BACKEND_URL=http://localhost
|
||||||
|
PROXY_PORT=8080
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
DB_HOST= #DB host IP, usually localhost
|
||||||
|
DB_USER=
|
||||||
|
DB_PASS=
|
||||||
|
DB_NAME=
|
||||||
|
```
|
||||||
|
|
||||||
|
Install backend dependencies, build app, run migrations and seeds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm 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
|
- Go to http://your_server_ip:3000/signup
|
||||||
- Create an user and login with it.
|
- Create an user and login with it.
|
||||||
- On the sidebard, go to _Connections_ and create your first WhatsApp connection.
|
- 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.
|
- 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.
|
- Done. Every message received by your synced WhatsApp number will appear in Tickets List.
|
||||||
|
|
||||||
## Basic production deployment (Ubuntu 18.04 VPS)
|
## Basic production deployment (Ubuntu 18.04 VPS)
|
||||||
|
|
||||||
|
All instructions below assumes you are NOT running as root, since it will give error in puppeteer. See
|
||||||
|
|
||||||
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. We'll also use an dedicated user with sudo privileges no deploy it (not root).
|
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. We'll also use an dedicated user with sudo privileges no deploy it (not root).
|
||||||
|
|
||||||
Update all system packages:
|
Update all system packages:
|
||||||
@@ -152,11 +198,12 @@ Install puppeteer dependencies:
|
|||||||
sudo apt-get install -y 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
|
sudo apt-get install -y 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, run migrations and seeds:
|
Install backend dependencies, build app, run migrations and seeds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd whaticket/backend
|
cd whaticket/backend
|
||||||
npm install
|
npm install
|
||||||
|
npm build
|
||||||
npx sequelize db:migrate
|
npx sequelize db:migrate
|
||||||
npx sequelize db:seed:all
|
npx sequelize db:seed:all
|
||||||
```
|
```
|
||||||
|
|||||||
9
backend/.editorconfig
Normal file
9
backend/.editorconfig
Normal 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
|
||||||
3
backend/.eslintignore
Normal file
3
backend/.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/*.js
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
47
backend/.eslintrc.json
Normal file
47
backend/.eslintrc.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es2021": true,
|
||||||
|
"node": 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-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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
public/*
|
public/*
|
||||||
|
dist
|
||||||
!public/.gitkeep
|
!public/.gitkeep
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const { resolve } = require("path");
|
const { resolve } = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: resolve(__dirname, "src", "config", "database.js"),
|
"config": resolve(__dirname, "dist", "config", "database.js"),
|
||||||
"modules-path": resolve(__dirname, "src", "models"),
|
"modules-path": resolve(__dirname, "dist", "models"),
|
||||||
"migrations-path": resolve(__dirname, "src", "database", "migrations"),
|
"migrations-path": resolve(__dirname, "dist", "database", "migrations"),
|
||||||
"seeders-path": resolve(__dirname, "src", "database", "seeds"),
|
"seeders-path": resolve(__dirname, "dist", "database", "seeds")
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,39 +1,58 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "nodemon src/app.js",
|
"build": "tsc",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"watch": "tsc -w",
|
||||||
},
|
"start": "nodemon dist/server.js",
|
||||||
"nodemonConfig": {
|
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts"
|
||||||
"ignore": [
|
},
|
||||||
"controllers/session.json"
|
"author": "",
|
||||||
]
|
"license": "MIT",
|
||||||
},
|
"dependencies": {
|
||||||
"author": "",
|
"@sentry/node": "5.22.3",
|
||||||
"license": "ISC",
|
"bcryptjs": "^2.4.3",
|
||||||
"dependencies": {
|
"cors": "^2.8.5",
|
||||||
"@sentry/node": "5.22.3",
|
"date-fns": "^2.16.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"dotenv": "^8.2.0",
|
||||||
"cors": "^2.8.5",
|
"express": "^4.17.1",
|
||||||
"date-fns": "^2.16.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"express": "^4.17.1",
|
"multer": "^1.4.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"mysql2": "^2.1.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"multer": "^1.4.2",
|
"reflect-metadata": "^0.1.13",
|
||||||
"mysql2": "^2.1.0",
|
"sequelize": "5",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"sequelize-cli": "5",
|
||||||
"sequelize": "^6.3.5",
|
"sequelize-typescript": "^1.1.0",
|
||||||
"socket.io": "^2.3.0",
|
"socket.io": "^2.3.0",
|
||||||
"whatsapp-web.js": "^1.8.2",
|
"whatsapp-web.js": "^1.8.2",
|
||||||
"youch": "^2.0.10",
|
"yup": "^0.29.3"
|
||||||
"yup": "^0.29.3"
|
},
|
||||||
},
|
"devDependencies": {
|
||||||
"devDependencies": {
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"nodemon": "^2.0.4",
|
"@types/bluebird": "^3.5.32",
|
||||||
"sequelize-cli": "^6.2.0"
|
"@types/cors": "^2.8.7",
|
||||||
}
|
"@types/express": "^4.17.8",
|
||||||
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
|
"@types/multer": "^1.4.4",
|
||||||
|
"@types/node": "^14.10.1",
|
||||||
|
"@types/socket.io": "^2.1.11",
|
||||||
|
"@types/validator": "^13.1.0",
|
||||||
|
"@types/yup": "^0.29.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||||
|
"@typescript-eslint/parser": "^4.1.0",
|
||||||
|
"eslint": "^7.9.0",
|
||||||
|
"eslint-config-airbnb-base": "^14.2.0",
|
||||||
|
"eslint-config-prettier": "^6.11.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.3.0",
|
||||||
|
"eslint-plugin-import": "^2.21.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
"nodemon": "^2.0.4",
|
||||||
|
"prettier": "^2.1.1",
|
||||||
|
"ts-node-dev": "^1.0.0-pre.62",
|
||||||
|
"typescript": "^4.0.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
backend/prettier.config.js
Normal file
5
backend/prettier.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "none",
|
||||||
|
arrowParens: "avoid",
|
||||||
|
};
|
||||||
5
backend/src/@types/express.d.ts
vendored
Normal file
5
backend/src/@types/express.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare namespace Express {
|
||||||
|
export interface Request {
|
||||||
|
user: { id: string; profile: string };
|
||||||
|
}
|
||||||
|
}
|
||||||
1
backend/src/@types/qrcode-terminal.d.ts
vendored
Normal file
1
backend/src/@types/qrcode-terminal.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module "qrcode-terminal";
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
require("express-async-errors");
|
|
||||||
require("./database");
|
|
||||||
const express = require("express");
|
|
||||||
const path = require("path");
|
|
||||||
const Youch = require("youch");
|
|
||||||
const cors = require("cors");
|
|
||||||
const multer = require("multer");
|
|
||||||
const Sentry = require("@sentry/node");
|
|
||||||
|
|
||||||
const { initWbot } = require("./libs/wbot");
|
|
||||||
const wbotMessageListener = require("./services/wbotMessageListener");
|
|
||||||
const wbotMonitor = require("./services/wbotMonitor");
|
|
||||||
const Whatsapp = require("./models/Whatsapp");
|
|
||||||
|
|
||||||
const Router = require("./router");
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
const server = app.listen(process.env.PORT, () => {
|
|
||||||
console.log(`Server started on port: ${process.env.PORT}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
Sentry.init({ dsn: process.env.SENTRY_DSN });
|
|
||||||
|
|
||||||
const fileStorage = multer.diskStorage({
|
|
||||||
destination: (req, file, cb) => {
|
|
||||||
cb(null, path.resolve(__dirname, "..", "public"));
|
|
||||||
},
|
|
||||||
filename: (req, file, cb) => {
|
|
||||||
cb(null, new Date().getTime() + path.extname(file.originalname));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(Sentry.Handlers.requestHandler());
|
|
||||||
app.use(cors());
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(multer({ storage: fileStorage }).single("media"));
|
|
||||||
app.use("/public", express.static(path.join(__dirname, "..", "public")));
|
|
||||||
app.use(Router);
|
|
||||||
|
|
||||||
const io = require("./libs/socket").init(server);
|
|
||||||
io.on("connection", socket => {
|
|
||||||
console.log("Client Connected");
|
|
||||||
socket.on("joinChatBox", ticketId => {
|
|
||||||
console.log("A client joined a ticket channel");
|
|
||||||
socket.join(ticketId);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("joinNotification", () => {
|
|
||||||
console.log("A client joined notification channel");
|
|
||||||
socket.join("notification");
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
|
||||||
console.log("Client disconnected");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const startWhatsAppSessions = async () => {
|
|
||||||
const whatsapps = await Whatsapp.findAll();
|
|
||||||
|
|
||||||
if (whatsapps.length > 0) {
|
|
||||||
whatsapps.forEach(whatsapp => {
|
|
||||||
initWbot(whatsapp)
|
|
||||||
.then(() => {
|
|
||||||
wbotMessageListener(whatsapp);
|
|
||||||
wbotMonitor(whatsapp);
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
startWhatsAppSessions();
|
|
||||||
|
|
||||||
app.use(Sentry.Handlers.errorHandler());
|
|
||||||
|
|
||||||
app.use(async (err, req, res, next) => {
|
|
||||||
if (process.env.NODE_ENV === "DEVELOPMENT") {
|
|
||||||
const errors = await new Youch(err, req).toJSON();
|
|
||||||
console.log(err);
|
|
||||||
return res.status(500).json(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(500).json({ error: "Internal server error" });
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
secret: "mysecret",
|
|
||||||
expiresIn: "7d",
|
|
||||||
};
|
|
||||||
4
backend/src/config/auth.ts
Normal file
4
backend/src/config/auth.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
secret: "mysecret",
|
||||||
|
expiresIn: "7d"
|
||||||
|
};
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
require("dotenv/config");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
define: {
|
|
||||||
charset: "utf8mb4",
|
|
||||||
collate: "utf8mb4_bin",
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
seederStorage: "sequelize",
|
|
||||||
};
|
|
||||||
15
backend/src/config/database.ts
Normal file
15
backend/src/config/database.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require("dotenv/config");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
define: {
|
||||||
|
charset: "utf8mb4",
|
||||||
|
collate: "utf8mb4_bin"
|
||||||
|
},
|
||||||
|
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
|
||||||
|
};
|
||||||
16
backend/src/config/upload.ts
Normal file
16
backend/src/config/upload.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import path from "path";
|
||||||
|
import multer from "multer";
|
||||||
|
|
||||||
|
const publicFolder = path.resolve(__dirname, "..", "..", "public");
|
||||||
|
export default {
|
||||||
|
directory: publicFolder,
|
||||||
|
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: publicFolder,
|
||||||
|
filename(req, file, cb) {
|
||||||
|
const fileName = new Date().getTime() + path.extname(file.originalname);
|
||||||
|
|
||||||
|
return cb(null, fileName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const { Op } = require("sequelize");
|
|
||||||
|
|
||||||
const Contact = require("../models/Contact");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
const ContactCustomField = require("../models/ContactCustomField");
|
|
||||||
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
const { getWbot } = require("../libs/wbot");
|
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
|
||||||
const { searchParam = "", pageNumber = 1 } = req.query;
|
|
||||||
|
|
||||||
const whereCondition = {
|
|
||||||
[Op.or]: [
|
|
||||||
{
|
|
||||||
name: Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("name")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ number: { [Op.like]: `%${searchParam}%` } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let limit = 20;
|
|
||||||
let offset = limit * (pageNumber - 1);
|
|
||||||
|
|
||||||
const { count, rows: contacts } = await Contact.findAndCountAll({
|
|
||||||
where: whereCondition,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
order: [["createdAt", "DESC"]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasMore = count > offset + contacts.length;
|
|
||||||
|
|
||||||
return res.json({ contacts, count, hasMore });
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.store = async (req, res) => {
|
|
||||||
const defaultWhatsapp = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!defaultWhatsapp) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.json({ error: "No default WhatsApp found. Check Connection page." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const wbot = getWbot(defaultWhatsapp);
|
|
||||||
const io = getIO();
|
|
||||||
const newContact = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isValidNumber = await wbot.isRegisteredUser(
|
|
||||||
`${newContact.number}@c.us`
|
|
||||||
);
|
|
||||||
if (!isValidNumber) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ error: "The suplied number is not a valid Whatsapp number" });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return res.status(500).json({
|
|
||||||
error: "Could not check whatsapp contact. Check connection page.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const profilePicUrl = await wbot.getProfilePicUrl(
|
|
||||||
`${newContact.number}@c.us`
|
|
||||||
);
|
|
||||||
|
|
||||||
const contact = await Contact.create(
|
|
||||||
{ ...newContact, profilePicUrl },
|
|
||||||
{
|
|
||||||
include: "extraInfo",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
io.emit("contact", {
|
|
||||||
action: "create",
|
|
||||||
contact: contact,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(contact);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.show = async (req, res) => {
|
|
||||||
const { contactId } = req.params;
|
|
||||||
|
|
||||||
const contact = await Contact.findByPk(contactId, {
|
|
||||||
include: "extraInfo",
|
|
||||||
attributes: ["id", "name", "number", "email"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contact) {
|
|
||||||
return res.status(404).json({ error: "No contact found with this id." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(contact);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const updatedContact = req.body;
|
|
||||||
|
|
||||||
const { contactId } = req.params;
|
|
||||||
|
|
||||||
const contact = await Contact.findByPk(contactId, {
|
|
||||||
include: "extraInfo",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contact) {
|
|
||||||
return res.status(404).json({ error: "No contact found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedContact.extraInfo) {
|
|
||||||
await Promise.all(
|
|
||||||
updatedContact.extraInfo.map(async info => {
|
|
||||||
await ContactCustomField.upsert({ ...info, contactId: contact.id });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
contact.extraInfo.map(async oldInfo => {
|
|
||||||
let stillExists = updatedContact.extraInfo.findIndex(
|
|
||||||
info => info.id === oldInfo.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stillExists === -1) {
|
|
||||||
await ContactCustomField.destroy({ where: { id: oldInfo.id } });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await contact.update(updatedContact);
|
|
||||||
|
|
||||||
io.emit("contact", {
|
|
||||||
action: "update",
|
|
||||||
contact: contact,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(contact);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.delete = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
const { contactId } = req.params;
|
|
||||||
|
|
||||||
const contact = await Contact.findByPk(contactId);
|
|
||||||
|
|
||||||
if (!contact) {
|
|
||||||
return res.status(404).json({ error: "No contact found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await contact.destroy();
|
|
||||||
|
|
||||||
io.emit("contact", {
|
|
||||||
action: "delete",
|
|
||||||
contactId: contactId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "Contact deleted" });
|
|
||||||
};
|
|
||||||
103
backend/src/controllers/ContactController.ts
Normal file
103
backend/src/controllers/ContactController.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
|
||||||
|
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { searchParam, pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { contacts, count, hasMore } = await ListContactsService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ contacts, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ExtraInfo {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
interface ContactData {
|
||||||
|
name: string;
|
||||||
|
number: string;
|
||||||
|
email?: string;
|
||||||
|
extraInfo?: ExtraInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const newContact: ContactData = req.body;
|
||||||
|
|
||||||
|
await CheckIsValidContact(newContact.number);
|
||||||
|
|
||||||
|
const profilePicUrl = await GetProfilePicUrl(newContact.number);
|
||||||
|
|
||||||
|
const contact = await CreateContactService({
|
||||||
|
...newContact,
|
||||||
|
profilePicUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
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 { 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);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("contact", {
|
||||||
|
action: "delete",
|
||||||
|
contactId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Contact deleted" });
|
||||||
|
};
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
const Contact = require("../models/Contact");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
const { getWbot, initWbot } = require("../libs/wbot");
|
|
||||||
|
|
||||||
exports.store = async (req, res, next) => {
|
|
||||||
const defaultWhatsapp = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!defaultWhatsapp) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.json({ error: "No default WhatsApp found. Check Connection page." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
const wbot = getWbot(defaultWhatsapp.id);
|
|
||||||
|
|
||||||
let phoneContacts;
|
|
||||||
|
|
||||||
try {
|
|
||||||
phoneContacts = await wbot.getContacts();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return res.status(500).json({
|
|
||||||
error: "Could not check whatsapp contact. Check connection page.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
phoneContacts.map(async ({ number, name }) => {
|
|
||||||
await Contact.create({ number, name });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "contacts imported" });
|
|
||||||
};
|
|
||||||
8
backend/src/controllers/ImportPhoneContactsController.ts
Normal file
8
backend/src/controllers/ImportPhoneContactsController.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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" });
|
||||||
|
};
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
const Message = require("../models/Message");
|
|
||||||
const Contact = require("../models/Contact");
|
|
||||||
const User = require("../models/User");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
|
|
||||||
const Ticket = require("../models/Ticket");
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
const { getWbot } = require("../libs/wbot");
|
|
||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
const { MessageMedia } = require("whatsapp-web.js");
|
|
||||||
|
|
||||||
const setMessagesAsRead = async ticket => {
|
|
||||||
const io = getIO();
|
|
||||||
const wbot = getWbot(ticket.whatsappId);
|
|
||||||
|
|
||||||
await Message.update(
|
|
||||||
{ read: true },
|
|
||||||
{
|
|
||||||
where: {
|
|
||||||
ticketId: ticket.id,
|
|
||||||
read: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await wbot.sendSeen(`${ticket.contact.number}@c.us`);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(
|
|
||||||
"Could not mark messages as read. Maybe whatsapp session disconnected?"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
io.to("notification").emit("ticket", {
|
|
||||||
action: "updateUnread",
|
|
||||||
ticketId: ticket.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.index = async (req, res, next) => {
|
|
||||||
const { ticketId } = req.params;
|
|
||||||
const { searchParam = "", pageNumber = 1 } = req.query;
|
|
||||||
|
|
||||||
const whereCondition = {
|
|
||||||
body: Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("body")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const limit = 20;
|
|
||||||
const offset = limit * (pageNumber - 1);
|
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(ticketId, {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Contact,
|
|
||||||
as: "contact",
|
|
||||||
include: "extraInfo",
|
|
||||||
attributes: ["id", "name", "number", "profilePicUrl"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: User,
|
|
||||||
as: "user",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ticket) {
|
|
||||||
return res.status(404).json({ error: "No ticket found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await setMessagesAsRead(ticket);
|
|
||||||
|
|
||||||
const ticketMessages = await ticket.getMessages({
|
|
||||||
where: whereCondition,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
order: [["createdAt", "DESC"]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await ticket.countMessages();
|
|
||||||
|
|
||||||
const serializedMessages = ticketMessages.map(message => {
|
|
||||||
return {
|
|
||||||
...message.dataValues,
|
|
||||||
mediaUrl: `${
|
|
||||||
message.mediaUrl
|
|
||||||
? `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${message.mediaUrl}`
|
|
||||||
: ""
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasMore = count > offset + ticketMessages.length;
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
messages: serializedMessages.reverse(),
|
|
||||||
ticket,
|
|
||||||
count,
|
|
||||||
hasMore,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.store = async (req, res, next) => {
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const { ticketId } = req.params;
|
|
||||||
const message = req.body;
|
|
||||||
const media = req.file;
|
|
||||||
let sentMessage;
|
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(ticketId, {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Contact,
|
|
||||||
as: "contact",
|
|
||||||
attributes: ["number", "name", "profilePicUrl"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ticket) {
|
|
||||||
return res.status(404).json({ error: "No ticket found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ticket.whatsappId) {
|
|
||||||
const defaultWhatsapp = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!defaultWhatsapp) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.json({ error: "No default WhatsApp found. Check Connection page." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await ticket.setWhatsapp(defaultWhatsapp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const wbot = getWbot(ticket.whatsappId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (media) {
|
|
||||||
const newMedia = MessageMedia.fromFilePath(req.file.path);
|
|
||||||
|
|
||||||
message.mediaUrl = req.file.filename;
|
|
||||||
if (newMedia.mimetype) {
|
|
||||||
message.mediaType = newMedia.mimetype.split("/")[0];
|
|
||||||
} else {
|
|
||||||
message.mediaType = "other";
|
|
||||||
}
|
|
||||||
|
|
||||||
sentMessage = await wbot.sendMessage(
|
|
||||||
`${ticket.contact.number}@c.us`,
|
|
||||||
newMedia
|
|
||||||
);
|
|
||||||
|
|
||||||
await ticket.update({ lastMessage: message.mediaUrl });
|
|
||||||
} else {
|
|
||||||
sentMessage = await wbot.sendMessage(
|
|
||||||
`${ticket.contact.number}@c.us`,
|
|
||||||
message.body
|
|
||||||
);
|
|
||||||
await ticket.update({ lastMessage: message.body });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(
|
|
||||||
"Could not create whatsapp message. Is session details valid? "
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sentMessage) {
|
|
||||||
message.id = sentMessage.id.id;
|
|
||||||
const newMessage = await ticket.createMessage(message);
|
|
||||||
|
|
||||||
const serialziedMessage = {
|
|
||||||
...newMessage.dataValues,
|
|
||||||
mediaUrl: `${
|
|
||||||
message.mediaUrl
|
|
||||||
? `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${message.mediaUrl}`
|
|
||||||
: ""
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
io.to(ticketId).to("notification").emit("appMessage", {
|
|
||||||
action: "create",
|
|
||||||
message: serialziedMessage,
|
|
||||||
ticket: ticket,
|
|
||||||
contact: ticket.contact,
|
|
||||||
});
|
|
||||||
|
|
||||||
await setMessagesAsRead(ticket);
|
|
||||||
|
|
||||||
return res.status(200).json({ newMessage, ticket });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
.status(500)
|
|
||||||
.json({ error: "Cannot sent whatsapp message. Check connection page." });
|
|
||||||
};
|
|
||||||
79
backend/src/controllers/MessageController.ts
Normal file
79
backend/src/controllers/MessageController.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
import { Message as WbotMessage } from "whatsapp-web.js";
|
||||||
|
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
|
||||||
|
import CreateMessageService from "../services/MessageServices/CreateMessageService";
|
||||||
|
import ListMessagesService from "../services/MessageServices/ListMessagesService";
|
||||||
|
import ShowTicketService from "../services/TicketServices/ShowTicketService";
|
||||||
|
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
|
||||||
|
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MessageData = {
|
||||||
|
body: string;
|
||||||
|
fromMe: boolean;
|
||||||
|
read: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
const { searchParam, pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { count, messages, ticket, hasMore } = await ListMessagesService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
await SetTicketMessagesAsRead(ticket);
|
||||||
|
|
||||||
|
return res.json({ count, messages, ticket, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
const { body, fromMe, read }: MessageData = req.body;
|
||||||
|
const media = req.file;
|
||||||
|
|
||||||
|
const ticket = await ShowTicketService(ticketId);
|
||||||
|
|
||||||
|
let sentMessage: WbotMessage;
|
||||||
|
|
||||||
|
if (media) {
|
||||||
|
sentMessage = await SendWhatsAppMedia({ media, ticket });
|
||||||
|
} else {
|
||||||
|
sentMessage = await SendWhatsAppMessage({ body, ticket });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMessage = {
|
||||||
|
id: sentMessage.id.id,
|
||||||
|
body,
|
||||||
|
fromMe,
|
||||||
|
read,
|
||||||
|
mediaType: sentMessage.type,
|
||||||
|
mediaUrl: media?.filename
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = await CreateMessageService({
|
||||||
|
messageData: newMessage,
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to(ticketId).to("notification").emit("appMessage", {
|
||||||
|
action: "create",
|
||||||
|
message,
|
||||||
|
ticket,
|
||||||
|
contact: ticket.contact
|
||||||
|
});
|
||||||
|
|
||||||
|
await SetTicketMessagesAsRead(ticket);
|
||||||
|
|
||||||
|
return res.status(200).json(message);
|
||||||
|
};
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const jwt = require("jsonwebtoken");
|
|
||||||
const authConfig = require("../config/auth");
|
|
||||||
|
|
||||||
const User = require("../models/User");
|
|
||||||
|
|
||||||
exports.store = async (req, res, next) => {
|
|
||||||
const { email, password } = req.body;
|
|
||||||
|
|
||||||
const user = await User.findOne({ where: { email: email } });
|
|
||||||
if (!user) {
|
|
||||||
return res.status(404).json({ error: "No user found with this email" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await user.checkPassword(password))) {
|
|
||||||
return res.status(401).json({ error: "Password does not match" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = jwt.sign(
|
|
||||||
{ email: user.email, userId: user.id },
|
|
||||||
authConfig.secret,
|
|
||||||
{
|
|
||||||
expiresIn: authConfig.expiresIn,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
token: token,
|
|
||||||
username: user.name,
|
|
||||||
profile: user.profile,
|
|
||||||
userId: user.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
16
backend/src/controllers/SessionController.ts
Normal file
16
backend/src/controllers/SessionController.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
import AuthUserService from "../services/UserServices/AuthUserSerice";
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
const { token, user } = await AuthUserService({ email, password });
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
token,
|
||||||
|
username: user.name,
|
||||||
|
profile: user.profile,
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
const Setting = require("../models/Setting");
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
|
||||||
if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can access this route." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await Setting.findAll();
|
|
||||||
|
|
||||||
return res.status(200).json(settings);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
|
||||||
if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can access this route." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
const { settingKey } = req.params;
|
|
||||||
const setting = await Setting.findByPk(settingKey);
|
|
||||||
|
|
||||||
if (!setting) {
|
|
||||||
return res.status(404).json({ error: "No setting found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await setting.update(req.body);
|
|
||||||
|
|
||||||
io.emit("settings", {
|
|
||||||
action: "update",
|
|
||||||
setting,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(setting);
|
|
||||||
};
|
|
||||||
41
backend/src/controllers/SettingController.ts
Normal file
41
backend/src/controllers/SettingController.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can access resource.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await ListSettingsService();
|
||||||
|
|
||||||
|
return res.status(200).json(settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can access this route.", 403);
|
||||||
|
}
|
||||||
|
const { settingKey: key } = req.params;
|
||||||
|
const { value } = req.body;
|
||||||
|
|
||||||
|
const setting = await UpdateSettingService({
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("settings", {
|
||||||
|
action: "update",
|
||||||
|
setting
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(setting);
|
||||||
|
};
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const { startOfDay, endOfDay, parseISO } = require("date-fns");
|
|
||||||
|
|
||||||
const Ticket = require("../models/Ticket");
|
|
||||||
const Contact = require("../models/Contact");
|
|
||||||
const Message = require("../models/Message");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
|
||||||
const {
|
|
||||||
pageNumber = 1,
|
|
||||||
status = "",
|
|
||||||
date = "",
|
|
||||||
searchParam = "",
|
|
||||||
showAll,
|
|
||||||
} = req.query;
|
|
||||||
|
|
||||||
const userId = req.user.id;
|
|
||||||
|
|
||||||
const limit = 20;
|
|
||||||
const offset = limit * (pageNumber - 1);
|
|
||||||
|
|
||||||
let includeCondition = [
|
|
||||||
{
|
|
||||||
model: Contact,
|
|
||||||
as: "contact",
|
|
||||||
attributes: ["name", "number", "profilePicUrl"],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let whereCondition = { userId: userId };
|
|
||||||
|
|
||||||
if (showAll === "true") {
|
|
||||||
whereCondition = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
whereCondition = {
|
|
||||||
...whereCondition,
|
|
||||||
status: status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchParam) {
|
|
||||||
includeCondition = [
|
|
||||||
...includeCondition,
|
|
||||||
{
|
|
||||||
model: Message,
|
|
||||||
as: "messages",
|
|
||||||
attributes: ["id", "body"],
|
|
||||||
where: {
|
|
||||||
body: Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("body")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
required: false,
|
|
||||||
duplicating: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
whereCondition = {
|
|
||||||
[Sequelize.Op.or]: [
|
|
||||||
{
|
|
||||||
"$contact.name$": Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("name")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ "$contact.number$": { [Sequelize.Op.like]: `%${searchParam}%` } },
|
|
||||||
{
|
|
||||||
"$message.body$": Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("body")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date) {
|
|
||||||
whereCondition = {
|
|
||||||
...whereCondition,
|
|
||||||
createdAt: {
|
|
||||||
[Sequelize.Op.between]: [
|
|
||||||
startOfDay(parseISO(date)),
|
|
||||||
endOfDay(parseISO(date)),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { count, rows: tickets } = await Ticket.findAndCountAll({
|
|
||||||
where: whereCondition,
|
|
||||||
distinct: true,
|
|
||||||
include: includeCondition,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
order: [["updatedAt", "DESC"]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasMore = count > offset + tickets.length;
|
|
||||||
|
|
||||||
return res.status(200).json({ count, tickets, hasMore });
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.store = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const defaultWhatsapp = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!defaultWhatsapp) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.json({ error: "No default WhatsApp found. Check Connection page." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const ticket = await defaultWhatsapp.createTicket(req.body);
|
|
||||||
|
|
||||||
const contact = await ticket.getContact();
|
|
||||||
|
|
||||||
const serializaedTicket = { ...ticket.dataValues, contact: contact };
|
|
||||||
|
|
||||||
io.to("notification").emit("ticket", {
|
|
||||||
action: "create",
|
|
||||||
ticket: serializaedTicket,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(ticket);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
const { ticketId } = req.params;
|
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(ticketId, {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Contact,
|
|
||||||
as: "contact",
|
|
||||||
attributes: ["name", "number", "profilePicUrl"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ticket) {
|
|
||||||
return res.status(404).json({ error: "No ticket found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await ticket.update(req.body);
|
|
||||||
|
|
||||||
io.to("notification").emit("ticket", {
|
|
||||||
action: "updateStatus",
|
|
||||||
ticket: ticket,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(ticket);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.delete = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
const { ticketId } = req.params;
|
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(ticketId);
|
|
||||||
|
|
||||||
if (!ticket) {
|
|
||||||
return res.status(400).json({ error: "No ticket found with this ID" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await ticket.destroy();
|
|
||||||
|
|
||||||
io.to("notification").emit("ticket", {
|
|
||||||
action: "delete",
|
|
||||||
ticketId: ticket.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "ticket deleted" });
|
|
||||||
};
|
|
||||||
92
backend/src/controllers/TicketController.ts
Normal file
92
backend/src/controllers/TicketController.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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 UpdateTicketService from "../services/TicketServices/UpdateTicketService";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
status: string;
|
||||||
|
date: string;
|
||||||
|
showAll: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TicketData {
|
||||||
|
contactId: number;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const {
|
||||||
|
pageNumber,
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
searchParam,
|
||||||
|
showAll
|
||||||
|
} = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
const { tickets, count, hasMore } = await ListTicketsService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber,
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
showAll,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ tickets, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { contactId, status }: TicketData = req.body;
|
||||||
|
|
||||||
|
const ticket = await CreateTicketService({ contactId, status });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to("notification").emit("ticket", {
|
||||||
|
action: "create",
|
||||||
|
ticket
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(ticket);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to("notification").emit("ticket", {
|
||||||
|
action: "updateStatus",
|
||||||
|
ticket
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(ticket);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
|
await DeleteTicketService(ticketId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to("notification").emit("ticket", {
|
||||||
|
action: "delete",
|
||||||
|
ticketId: +ticketId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "ticket deleted" });
|
||||||
|
};
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const Yup = require("yup");
|
|
||||||
const { Op } = require("sequelize");
|
|
||||||
|
|
||||||
const User = require("../models/User");
|
|
||||||
const Setting = require("../models/Setting");
|
|
||||||
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
|
||||||
if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can access this route." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { searchParam = "", pageNumber = 1 } = req.query;
|
|
||||||
|
|
||||||
const whereCondition = {
|
|
||||||
[Op.or]: [
|
|
||||||
{
|
|
||||||
name: Sequelize.where(
|
|
||||||
Sequelize.fn("LOWER", Sequelize.col("name")),
|
|
||||||
"LIKE",
|
|
||||||
"%" + searchParam.toLowerCase() + "%"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ email: { [Op.like]: `%${searchParam.toLowerCase()}%` } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let limit = 20;
|
|
||||||
let offset = limit * (pageNumber - 1);
|
|
||||||
|
|
||||||
const { count, rows: users } = await User.findAndCountAll({
|
|
||||||
attributes: ["name", "id", "email", "profile"],
|
|
||||||
where: whereCondition,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
order: [["createdAt", "DESC"]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasMore = count > offset + users.length;
|
|
||||||
|
|
||||||
return res.status(200).json({ users, count, hasMore });
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.store = async (req, res, next) => {
|
|
||||||
console.log(req.url);
|
|
||||||
const schema = Yup.object().shape({
|
|
||||||
name: Yup.string().required().min(2),
|
|
||||||
email: Yup.string()
|
|
||||||
.email()
|
|
||||||
.required()
|
|
||||||
.test(
|
|
||||||
"Check-email",
|
|
||||||
"An user with this email already exists",
|
|
||||||
async value => {
|
|
||||||
const userFound = await User.findOne({ where: { email: value } });
|
|
||||||
return !Boolean(userFound);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
password: Yup.string().required().min(5),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (req.url === "/signup") {
|
|
||||||
const { value: userCreation } = await Setting.findByPk("userCreation");
|
|
||||||
|
|
||||||
if (userCreation === "disabled") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "User creation is disabled by administrator." });
|
|
||||||
}
|
|
||||||
} else if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can create users." });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schema.validate(req.body);
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).json({ error: err.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const { name, id, email, profile } = await User.create(req.body);
|
|
||||||
|
|
||||||
io.emit("user", {
|
|
||||||
action: "create",
|
|
||||||
user: { name, id, email, profile },
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(201).json({ message: "User created!", userId: id });
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.show = async (req, res) => {
|
|
||||||
const { userId } = req.params;
|
|
||||||
|
|
||||||
const user = await User.findByPk(userId, {
|
|
||||||
attributes: ["id", "name", "email", "profile"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
res.status(400).json({ error: "No user found with this id." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(user);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
|
||||||
const schema = Yup.object().shape({
|
|
||||||
name: Yup.string().min(2),
|
|
||||||
email: Yup.string().email(),
|
|
||||||
password: Yup.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can edit users." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await schema.validate(req.body);
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
const { userId } = req.params;
|
|
||||||
|
|
||||||
const user = await User.findByPk(userId, {
|
|
||||||
attributes: ["name", "id", "email", "profile"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
res.status(404).json({ error: "No user found with this id." });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.profile === "admin" && req.body.profile === "user") {
|
|
||||||
const adminUsers = await User.count({ where: { profile: "admin" } });
|
|
||||||
if (adminUsers <= 1) {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "There must be at leat one admin user." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await user.update(req.body);
|
|
||||||
|
|
||||||
io.emit("user", {
|
|
||||||
action: "update",
|
|
||||||
user: user,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(user);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.delete = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
const { userId } = req.params;
|
|
||||||
|
|
||||||
const user = await User.findByPk(userId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
res.status(400).json({ error: "No user found with this id." });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.user.profile !== "admin") {
|
|
||||||
return res
|
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only administrators can edit users." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await user.destroy();
|
|
||||||
|
|
||||||
io.emit("user", {
|
|
||||||
action: "delete",
|
|
||||||
userId: userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "User deleted" });
|
|
||||||
};
|
|
||||||
109
backend/src/controllers/UserController.ts
Normal file
109
backend/src/controllers/UserController.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
type IndexQuery = {
|
||||||
|
searchParam: string;
|
||||||
|
pageNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can access this route.", 403); // should be handled better.
|
||||||
|
}
|
||||||
|
const { searchParam, pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
const { users, count, hasMore } = await ListUsersService({
|
||||||
|
searchParam,
|
||||||
|
pageNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({ users, count, hasMore });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { email, password, name, profile } = req.body;
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.url === "/signup" &&
|
||||||
|
(await CheckSettingsHelper("userCreation")) === "disabled"
|
||||||
|
) {
|
||||||
|
throw new AppError("User creation is disabled by administrator.", 403);
|
||||||
|
} else if (req.url !== "/signup" && req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can create users.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await CreateUserService({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
name,
|
||||||
|
profile
|
||||||
|
});
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "create",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
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 update = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
if (req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can edit users.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId } = req.params;
|
||||||
|
const userData = req.body;
|
||||||
|
|
||||||
|
const user = await UpdateUserService({ userData, userId });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "update",
|
||||||
|
user
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
if (req.user.profile !== "admin") {
|
||||||
|
throw new AppError("Only administrators can delete users.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DeleteUserService(userId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("user", {
|
||||||
|
action: "delete",
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "User deleted" });
|
||||||
|
};
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
const Yup = require("yup");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
const { getWbot, initWbot, removeWbot } = require("../libs/wbot");
|
|
||||||
const wbotMessageListener = require("../services/wbotMessageListener");
|
|
||||||
const wbotMonitor = require("../services/wbotMonitor");
|
|
||||||
|
|
||||||
exports.index = async (req, res) => {
|
|
||||||
const whatsapps = await Whatsapp.findAll();
|
|
||||||
|
|
||||||
return res.status(200).json(whatsapps);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.store = async (req, res) => {
|
|
||||||
const schema = Yup.object().shape({
|
|
||||||
name: Yup.string().required().min(2),
|
|
||||||
default: Yup.boolean()
|
|
||||||
.required()
|
|
||||||
.test(
|
|
||||||
"Check-default",
|
|
||||||
"Only one default whatsapp is permited",
|
|
||||||
async value => {
|
|
||||||
if (value === true) {
|
|
||||||
const whatsappFound = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
return !Boolean(whatsappFound);
|
|
||||||
} else return true;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schema.validate(req.body);
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).json({ error: err.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const whatsapp = await Whatsapp.create(req.body);
|
|
||||||
|
|
||||||
if (!whatsapp) {
|
|
||||||
return res.status(400).json({ error: "Cannot create whatsapp session." });
|
|
||||||
}
|
|
||||||
|
|
||||||
initWbot(whatsapp)
|
|
||||||
.then(() => {
|
|
||||||
wbotMessageListener(whatsapp);
|
|
||||||
wbotMonitor(whatsapp);
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
|
|
||||||
io.emit("whatsapp", {
|
|
||||||
action: "update",
|
|
||||||
whatsapp: whatsapp,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json(whatsapp);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.show = async (req, res) => {
|
|
||||||
const { whatsappId } = req.params;
|
|
||||||
const whatsapp = await Whatsapp.findByPk(whatsappId);
|
|
||||||
|
|
||||||
if (!whatsapp) {
|
|
||||||
return res.status(200).json({ message: "Session not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(whatsapp);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
|
||||||
const { whatsappId } = req.params;
|
|
||||||
|
|
||||||
const schema = Yup.object().shape({
|
|
||||||
name: Yup.string().required().min(2),
|
|
||||||
default: Yup.boolean()
|
|
||||||
.required()
|
|
||||||
.test(
|
|
||||||
"Check-default",
|
|
||||||
"Only one default whatsapp is permited",
|
|
||||||
async value => {
|
|
||||||
if (value === true) {
|
|
||||||
const whatsappFound = await Whatsapp.findOne({
|
|
||||||
where: { default: true },
|
|
||||||
});
|
|
||||||
if (whatsappFound) {
|
|
||||||
return !(whatsappFound.id !== +whatsappId);
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else return true;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schema.validate(req.body);
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).json({ error: err.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
|
|
||||||
const whatsapp = await Whatsapp.findByPk(whatsappId);
|
|
||||||
|
|
||||||
if (!whatsapp) {
|
|
||||||
return res.status(404).json({ message: "Whatsapp not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await whatsapp.update(req.body);
|
|
||||||
|
|
||||||
io.emit("whatsapp", {
|
|
||||||
action: "update",
|
|
||||||
whatsapp: whatsapp,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "Whatsapp updated" });
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.delete = async (req, res) => {
|
|
||||||
const io = getIO();
|
|
||||||
const { whatsappId } = req.params;
|
|
||||||
|
|
||||||
const whatsapp = await Whatsapp.findByPk(whatsappId);
|
|
||||||
|
|
||||||
if (!whatsapp) {
|
|
||||||
return res.status(404).json({ message: "Whatsapp not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await whatsapp.destroy();
|
|
||||||
removeWbot(whatsapp.id);
|
|
||||||
|
|
||||||
io.emit("whatsapp", {
|
|
||||||
action: "delete",
|
|
||||||
whatsappId: whatsapp.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "Whatsapp deleted." });
|
|
||||||
};
|
|
||||||
94
backend/src/controllers/WhatsAppController.ts
Normal file
94
backend/src/controllers/WhatsAppController.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import { initWbot, removeWbot } from "../libs/wbot";
|
||||||
|
import wbotMessageListener from "../services/WbotServices/wbotMessageListener";
|
||||||
|
import wbotMonitor from "../services/WbotServices/wbotMonitor";
|
||||||
|
|
||||||
|
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 Yup from "yup";
|
||||||
|
// import Whatsapp from "../models/Whatsapp";
|
||||||
|
// import { getIO } from "../libs/socket";
|
||||||
|
// import { getWbot, initWbot, removeWbot } from "../libs/wbot";
|
||||||
|
// import wbotMessageListener from "../services/wbotMessageListener";
|
||||||
|
// import wbotMonitor from "../services/wbotMonitor";
|
||||||
|
|
||||||
|
interface WhatsappData {
|
||||||
|
name: string;
|
||||||
|
status?: string;
|
||||||
|
isDefault?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const whatsapps = await ListWhatsAppsService();
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
const { name, status, isDefault }: WhatsappData = req.body;
|
||||||
|
|
||||||
|
const whatsapp = await CreateWhatsAppService({ name, status, isDefault });
|
||||||
|
|
||||||
|
initWbot(whatsapp)
|
||||||
|
.then(() => {
|
||||||
|
wbotMessageListener(whatsapp);
|
||||||
|
wbotMonitor(whatsapp);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const whatsapp = await UpdateWhatsAppService({ whatsappData, whatsappId });
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "update",
|
||||||
|
whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(whatsapp);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { whatsappId } = req.params;
|
||||||
|
|
||||||
|
await DeleteWhatsAppService(whatsappId);
|
||||||
|
removeWbot(+whatsappId);
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.emit("whatsapp", {
|
||||||
|
action: "delete",
|
||||||
|
whatsappId: +whatsappId
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Whatsapp deleted." });
|
||||||
|
};
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const dbConfig = require("../config/database");
|
|
||||||
|
|
||||||
const User = require("../models/User");
|
|
||||||
const Contact = require("../models/Contact");
|
|
||||||
const Ticket = require("../models/Ticket");
|
|
||||||
const Message = require("../models/Message");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
const ContactCustomField = require("../models/ContactCustomField");
|
|
||||||
const Setting = require("../models/Setting");
|
|
||||||
|
|
||||||
const models = [
|
|
||||||
User,
|
|
||||||
Contact,
|
|
||||||
Ticket,
|
|
||||||
Message,
|
|
||||||
Whatsapp,
|
|
||||||
ContactCustomField,
|
|
||||||
Setting,
|
|
||||||
];
|
|
||||||
|
|
||||||
class Database {
|
|
||||||
constructor() {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.sequelize = new Sequelize(dbConfig);
|
|
||||||
|
|
||||||
models
|
|
||||||
.map(model => model.init(this.sequelize))
|
|
||||||
.map(model => model.associate && model.associate(this.sequelize.models));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new Database();
|
|
||||||
28
backend/src/database/index.ts
Normal file
28
backend/src/database/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
// 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
|
||||||
|
];
|
||||||
|
|
||||||
|
sequelize.addModels(models);
|
||||||
|
|
||||||
|
export default sequelize;
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Users", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
passwordHash: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Users");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Contacts", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
profilePicUrl: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Contacts");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Tickets", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
defaultValue: "pending",
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
lastMessage: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
contactId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Contacts", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Users", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Tickets");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Messages", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
ack: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
read: {
|
|
||||||
type: Sequelize.BOOLEAN,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: false,
|
|
||||||
},
|
|
||||||
mediaType: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
mediaUrl: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Users", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
},
|
|
||||||
ticketId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Tickets", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Messages");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Whatsapps", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
},
|
|
||||||
qrcode: {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
battery: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
},
|
|
||||||
plugged: {
|
|
||||||
type: Sequelize.BOOLEAN,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Whatsapps");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("ContactCustomFields", {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
contactId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Contacts", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("ContactCustomFields");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Contacts", "email", {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: "",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Contacts", "email");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Messages", "userId");
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Messages", "userId", {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Users", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Messages", "fromMe", {
|
|
||||||
type: Sequelize.BOOLEAN,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Messages", "fromMe");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.changeColumn("Tickets", "lastMessage", {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.changeColumn("Tickets", "lastMessage", {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Users", "profile", {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: "admin",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Users", "profile");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.createTable("Settings", {
|
|
||||||
key: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.dropTable("Settings");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Whatsapps", "name", {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Whatsapps", "name");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Whatsapps", "default", {
|
|
||||||
type: Sequelize.BOOLEAN,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Whatsapps", "default");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface, Sequelize) => {
|
|
||||||
return queryInterface.addColumn("Tickets", "whatsappId", {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
references: { model: "Whatsapps", key: "id" },
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.removeColumn("Tickets", "whatsappId");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Tickets", "whatsappId", {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
references: { model: "Whatsapps", key: "id" },
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
onDelete: "SET NULL"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Tickets", "whatsappId");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("Whatsapps", "default", "isDefault");
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.renameColumn("Whatsapps", "isDefault", "default");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: queryInterface => {
|
|
||||||
return queryInterface.bulkInsert(
|
|
||||||
"Settings",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: "userCreation",
|
|
||||||
value: "enabled",
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
down: queryInterface => {
|
|
||||||
return queryInterface.bulkDelete("Settings", null, {});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "userCreation",
|
||||||
|
value: "enabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
||||||
12
backend/src/errors/AppError.ts
Normal file
12
backend/src/errors/AppError.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
class AppError {
|
||||||
|
public readonly message: string;
|
||||||
|
|
||||||
|
public readonly statusCode: number;
|
||||||
|
|
||||||
|
constructor(message: string, statusCode = 400) {
|
||||||
|
this.message = message;
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppError;
|
||||||
16
backend/src/helpers/CheckSettings.ts
Normal file
16
backend/src/helpers/CheckSettings.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import Setting from "../models/Setting";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
const CheckSettings = async (key: string): Promise<string> => {
|
||||||
|
const setting = await Setting.findOne({
|
||||||
|
where: { key }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!setting) {
|
||||||
|
throw new AppError("No setting found with this id.", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CheckSettings;
|
||||||
16
backend/src/helpers/GetDefaultWhatsApp.ts
Normal file
16
backend/src/helpers/GetDefaultWhatsApp.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
|
||||||
|
const GetDefaultWhatsApp = async (): Promise<Whatsapp> => {
|
||||||
|
const defaultWhatsapp = await Whatsapp.findOne({
|
||||||
|
where: { isDefault: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defaultWhatsapp) {
|
||||||
|
throw new AppError("No default WhatsApp found. Check Connection page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultWhatsapp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GetDefaultWhatsApp;
|
||||||
22
backend/src/helpers/GetTicketWbot.ts
Normal file
22
backend/src/helpers/GetTicketWbot.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Client as Session } from "whatsapp-web.js";
|
||||||
|
import { getWbot } from "../libs/wbot";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import GetDefaultWhatsApp from "./GetDefaultWhatsApp";
|
||||||
|
import Ticket from "../models/Ticket";
|
||||||
|
|
||||||
|
const GetTicketWbot = async (ticket: Ticket): Promise<Session> => {
|
||||||
|
if (!ticket.whatsappId) {
|
||||||
|
const defaultWhatsapp = await GetDefaultWhatsApp();
|
||||||
|
|
||||||
|
if (!defaultWhatsapp) {
|
||||||
|
throw new AppError("No default WhatsApp found. Check Connection page.");
|
||||||
|
}
|
||||||
|
await ticket.$set("whatsapp", defaultWhatsapp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wbot = getWbot(ticket.whatsappId);
|
||||||
|
|
||||||
|
return wbot;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GetTicketWbot;
|
||||||
33
backend/src/helpers/SetTicketMessagesAsRead.ts
Normal file
33
backend/src/helpers/SetTicketMessagesAsRead.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { getIO } from "../libs/socket";
|
||||||
|
import Message from "../models/Message";
|
||||||
|
import Ticket from "../models/Ticket";
|
||||||
|
import GetTicketWbot from "./GetTicketWbot";
|
||||||
|
|
||||||
|
const SetTicketMessagesAsRead = async (ticket: Ticket): Promise<void> => {
|
||||||
|
await Message.update(
|
||||||
|
{ read: true },
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
ticketId: ticket.id,
|
||||||
|
read: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const wbot = await GetTicketWbot(ticket);
|
||||||
|
await wbot.sendSeen(`${ticket.contact.number}@c.us`);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(
|
||||||
|
"Could not mark messages as read. Maybe whatsapp session disconnected?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = getIO();
|
||||||
|
io.to("notification").emit("ticket", {
|
||||||
|
action: "updateUnread",
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SetTicketMessagesAsRead;
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
let io;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
init: httpServer => {
|
|
||||||
io = require("socket.io")(httpServer);
|
|
||||||
return io;
|
|
||||||
},
|
|
||||||
getIO: () => {
|
|
||||||
if (!io) {
|
|
||||||
throw new Error("Socket IO not initialized");
|
|
||||||
}
|
|
||||||
return io;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
32
backend/src/libs/socket.ts
Normal file
32
backend/src/libs/socket.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import socketIo, { Server as SocketIO } from "socket.io";
|
||||||
|
import { Server } from "http";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
let io: SocketIO;
|
||||||
|
|
||||||
|
export const initIO = (httpServer: Server): SocketIO => {
|
||||||
|
io = socketIo(httpServer);
|
||||||
|
io.on("connection", socket => {
|
||||||
|
console.log("Client Connected");
|
||||||
|
socket.on("joinChatBox", ticketId => {
|
||||||
|
console.log("A client joined a ticket channel");
|
||||||
|
socket.join(ticketId);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("joinNotification", () => {
|
||||||
|
console.log("A client joined notification channel");
|
||||||
|
socket.join("notification");
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log("Client disconnected");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return io;
|
||||||
|
};
|
||||||
|
export const getIO = (): SocketIO => {
|
||||||
|
if (!io) {
|
||||||
|
throw new AppError("Socket IO not initialized");
|
||||||
|
}
|
||||||
|
return io;
|
||||||
|
};
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
const qrCode = require("qrcode-terminal");
|
|
||||||
const { Client } = require("whatsapp-web.js");
|
|
||||||
const Whatsapp = require("../models/Whatsapp");
|
|
||||||
const { getIO } = require("../libs/socket");
|
|
||||||
|
|
||||||
let sessions = [];
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
initWbot: async whatsapp => {
|
|
||||||
try {
|
|
||||||
const io = getIO();
|
|
||||||
const sessionName = whatsapp.name;
|
|
||||||
let sessionCfg;
|
|
||||||
|
|
||||||
if (whatsapp && whatsapp.session) {
|
|
||||||
sessionCfg = JSON.parse(whatsapp.session);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
|
|
||||||
if (sessionIndex !== -1) {
|
|
||||||
sessions[sessionIndex].destroy();
|
|
||||||
sessions.splice(sessionIndex, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const wbot = new Client({
|
|
||||||
session: sessionCfg,
|
|
||||||
restartOnAuthFail: true,
|
|
||||||
});
|
|
||||||
wbot.initialize();
|
|
||||||
|
|
||||||
wbot.on("qr", async qr => {
|
|
||||||
console.log("Session:", sessionName);
|
|
||||||
|
|
||||||
qrCode.generate(qr, { small: true });
|
|
||||||
|
|
||||||
await whatsapp.update({ qrcode: qr, status: "qrcode" });
|
|
||||||
|
|
||||||
io.emit("whatsappSession", {
|
|
||||||
action: "update",
|
|
||||||
session: whatsapp,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
wbot.on("authenticated", async session => {
|
|
||||||
console.log("Session:", sessionName, "AUTHENTICATED");
|
|
||||||
|
|
||||||
await whatsapp.update({
|
|
||||||
session: JSON.stringify(session),
|
|
||||||
status: "authenticated",
|
|
||||||
});
|
|
||||||
|
|
||||||
io.emit("whatsappSession", {
|
|
||||||
action: "update",
|
|
||||||
session: whatsapp,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
wbot.on("auth_failure", async msg => {
|
|
||||||
console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg);
|
|
||||||
|
|
||||||
await whatsapp.update({ session: "" });
|
|
||||||
});
|
|
||||||
|
|
||||||
wbot.on("ready", async () => {
|
|
||||||
console.log("Session:", sessionName, "READY");
|
|
||||||
|
|
||||||
await whatsapp.update({
|
|
||||||
status: "CONNECTED",
|
|
||||||
qrcode: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
io.emit("whatsappSession", {
|
|
||||||
action: "update",
|
|
||||||
session: whatsapp,
|
|
||||||
});
|
|
||||||
|
|
||||||
wbot.sendPresenceAvailable();
|
|
||||||
});
|
|
||||||
|
|
||||||
wbot.id = whatsapp.id;
|
|
||||||
sessions.push(wbot);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
getWbot: whatsappId => {
|
|
||||||
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
|
|
||||||
|
|
||||||
if (sessionIndex === -1) {
|
|
||||||
console.log("This Wbot session is not initialized");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sessions[sessionIndex];
|
|
||||||
},
|
|
||||||
|
|
||||||
removeWbot: whatsappId => {
|
|
||||||
try {
|
|
||||||
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
|
|
||||||
if (sessionIndex !== -1) {
|
|
||||||
sessions[sessionIndex].destroy();
|
|
||||||
sessions.splice(sessionIndex, 1);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
112
backend/src/libs/wbot.ts
Normal file
112
backend/src/libs/wbot.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import qrCode from "qrcode-terminal";
|
||||||
|
import { Client } from "whatsapp-web.js";
|
||||||
|
import { getIO } from "./socket";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
|
||||||
|
interface Session extends Client {
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessions: Session[] = [];
|
||||||
|
|
||||||
|
export const initWbot = async (whatsapp: Whatsapp): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const io = getIO();
|
||||||
|
const sessionName = whatsapp.name;
|
||||||
|
let sessionCfg;
|
||||||
|
|
||||||
|
if (whatsapp && whatsapp.session) {
|
||||||
|
sessionCfg = JSON.parse(whatsapp.session);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
|
||||||
|
if (sessionIndex !== -1) {
|
||||||
|
sessions[sessionIndex].destroy();
|
||||||
|
sessions.splice(sessionIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wbot: Session = new Client({
|
||||||
|
session: sessionCfg,
|
||||||
|
restartOnAuthFail: true
|
||||||
|
});
|
||||||
|
wbot.initialize();
|
||||||
|
|
||||||
|
wbot.on("qr", async qr => {
|
||||||
|
console.log("Session:", sessionName);
|
||||||
|
|
||||||
|
qrCode.generate(qr, { small: true });
|
||||||
|
|
||||||
|
await whatsapp.update({ qrcode: qr, status: "qrcode" });
|
||||||
|
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wbot.on("authenticated", async session => {
|
||||||
|
console.log("Session:", sessionName, "AUTHENTICATED");
|
||||||
|
|
||||||
|
await whatsapp.update({
|
||||||
|
session: JSON.stringify(session),
|
||||||
|
status: "authenticated"
|
||||||
|
});
|
||||||
|
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wbot.on("auth_failure", async msg => {
|
||||||
|
console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg);
|
||||||
|
|
||||||
|
await whatsapp.update({ session: "" });
|
||||||
|
});
|
||||||
|
|
||||||
|
wbot.on("ready", async () => {
|
||||||
|
console.log("Session:", sessionName, "READY");
|
||||||
|
|
||||||
|
await whatsapp.update({
|
||||||
|
status: "CONNECTED",
|
||||||
|
qrcode: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
io.emit("whatsappSession", {
|
||||||
|
action: "update",
|
||||||
|
session: whatsapp
|
||||||
|
});
|
||||||
|
|
||||||
|
wbot.sendPresenceAvailable();
|
||||||
|
});
|
||||||
|
|
||||||
|
wbot.id = whatsapp.id;
|
||||||
|
sessions.push(wbot);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWbot = (whatsappId: number): Session => {
|
||||||
|
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
|
||||||
|
|
||||||
|
if (sessionIndex === -1) {
|
||||||
|
throw new AppError(
|
||||||
|
"This WhatsApp session is not initialized. Check connections page."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sessions[sessionIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeWbot = (whatsappId: number): void => {
|
||||||
|
try {
|
||||||
|
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
|
||||||
|
if (sessionIndex !== -1) {
|
||||||
|
sessions[sessionIndex].destroy();
|
||||||
|
sessions.splice(sessionIndex, 1);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
const jwt = require("jsonwebtoken");
|
|
||||||
const util = require("util");
|
|
||||||
|
|
||||||
const User = require("../models/User");
|
|
||||||
const authConfig = require("../config/auth");
|
|
||||||
|
|
||||||
module.exports = async (req, res, next) => {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return res.status(401).json({ error: "Token not provided" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, token] = authHeader.split(" ");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decoded = await util.promisify(jwt.verify)(token, authConfig.secret);
|
|
||||||
|
|
||||||
const user = await User.findByPk(decoded.userId, {
|
|
||||||
attributes: ["id", "name", "profile", "email"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return res
|
|
||||||
.status(401)
|
|
||||||
.json({ error: "The token corresponding user does not exists." });
|
|
||||||
}
|
|
||||||
|
|
||||||
req.user = user;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return res.status(401).json({ error: "Invalid Token" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
39
backend/src/middleware/isAuth.ts
Normal file
39
backend/src/middleware/isAuth.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { verify } from "jsonwebtoken";
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
|
import AppError from "../errors/AppError";
|
||||||
|
import authConfig from "../config/auth";
|
||||||
|
|
||||||
|
interface TokenPayload {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
profile: string;
|
||||||
|
iat: number;
|
||||||
|
exp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAuth = (req: Request, res: Response, next: NextFunction): void => {
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new AppError("Token not provided.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, token] = authHeader.split(" ");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = verify(token, authConfig.secret);
|
||||||
|
const { id, profile } = decoded as TokenPayload;
|
||||||
|
|
||||||
|
req.user = {
|
||||||
|
id,
|
||||||
|
profile
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppError("Invalid token.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isAuth;
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
class Contact extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
name: { type: Sequelize.STRING },
|
|
||||||
number: { type: Sequelize.STRING, allowNull: false, unique: true },
|
|
||||||
email: { type: Sequelize.STRING, allowNull: false, defaultValue: "" },
|
|
||||||
profilePicUrl: { type: Sequelize.STRING },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(models) {
|
|
||||||
this.hasMany(models.Ticket, { foreignKey: "contactId", as: "contact" });
|
|
||||||
this.hasMany(models.ContactCustomField, {
|
|
||||||
foreignKey: "contactId",
|
|
||||||
as: "extraInfo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Contact;
|
|
||||||
53
backend/src/models/Contact.ts
Normal file
53
backend/src/models/Contact.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
PrimaryKey,
|
||||||
|
AutoIncrement,
|
||||||
|
AllowNull,
|
||||||
|
Unique,
|
||||||
|
Default,
|
||||||
|
HasMany
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
import ContactCustomField from "./ContactCustomField";
|
||||||
|
import Ticket from "./Ticket";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class Contact extends Model<Contact> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Unique
|
||||||
|
@Column
|
||||||
|
number: string;
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Default("")
|
||||||
|
@Column
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
profilePicUrl: string;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@HasMany(() => Ticket)
|
||||||
|
tickets: Ticket[];
|
||||||
|
|
||||||
|
@HasMany(() => ContactCustomField)
|
||||||
|
extraInfo: ContactCustomField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Contact;
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
class ContactCustomField extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
name: { type: Sequelize.STRING },
|
|
||||||
value: { type: Sequelize.STRING },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(models) {
|
|
||||||
this.belongsTo(models.Contact, {
|
|
||||||
foreignKey: "contactId",
|
|
||||||
as: "extraInfo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ContactCustomField;
|
|
||||||
41
backend/src/models/ContactCustomField.ts
Normal file
41
backend/src/models/ContactCustomField.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
PrimaryKey,
|
||||||
|
AutoIncrement,
|
||||||
|
ForeignKey,
|
||||||
|
BelongsTo
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
import Contact from "./Contact";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class ContactCustomField extends Model<ContactCustomField> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@ForeignKey(() => Contact)
|
||||||
|
@Column
|
||||||
|
contactId: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => Contact)
|
||||||
|
contact: Contact;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactCustomField;
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
class Message extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
ack: { type: Sequelize.INTEGER, defaultValue: 0 },
|
|
||||||
read: { type: Sequelize.BOOLEAN, defaultValue: false },
|
|
||||||
fromMe: { type: Sequelize.BOOLEAN, defaultValue: false },
|
|
||||||
body: { type: Sequelize.TEXT },
|
|
||||||
mediaUrl: { type: Sequelize.STRING },
|
|
||||||
mediaType: { type: Sequelize.STRING },
|
|
||||||
createdAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Sequelize.DATE(6),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(models) {
|
|
||||||
this.belongsTo(models.Ticket, { foreignKey: "ticketId", as: "messages" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Message;
|
|
||||||
66
backend/src/models/Message.ts
Normal file
66
backend/src/models/Message.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
DataType,
|
||||||
|
PrimaryKey,
|
||||||
|
AutoIncrement,
|
||||||
|
Default,
|
||||||
|
BelongsTo,
|
||||||
|
ForeignKey
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
import Ticket from "./Ticket";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class Message extends Model<Message> {
|
||||||
|
@PrimaryKey
|
||||||
|
@Column
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Default(0)
|
||||||
|
@Column
|
||||||
|
ack: number;
|
||||||
|
|
||||||
|
@Default(false)
|
||||||
|
@Column
|
||||||
|
read: boolean;
|
||||||
|
|
||||||
|
@Default(false)
|
||||||
|
@Column
|
||||||
|
fromMe: boolean;
|
||||||
|
|
||||||
|
@Column(DataType.TEXT)
|
||||||
|
body: string;
|
||||||
|
|
||||||
|
@Column(DataType.STRING)
|
||||||
|
get mediaUrl(): string | null {
|
||||||
|
if (this.getDataValue("mediaUrl")) {
|
||||||
|
return `${process.env.BACKEND_URL}:${
|
||||||
|
process.env.PROXY_PORT
|
||||||
|
}/public/${this.getDataValue("mediaUrl")}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column
|
||||||
|
mediaType: string;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
@Column(DataType.DATE(6))
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
@Column(DataType.DATE(6))
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@ForeignKey(() => Ticket)
|
||||||
|
@Column
|
||||||
|
ticketId: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => Ticket)
|
||||||
|
ticket: Ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Message;
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
class Setting extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
value: { type: Sequelize.TEXT, allowNull: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Setting;
|
|
||||||
26
backend/src/models/Setting.ts
Normal file
26
backend/src/models/Setting.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
PrimaryKey
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class Setting extends Model<Setting> {
|
||||||
|
@PrimaryKey
|
||||||
|
@Column
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Setting;
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const Message = require("./Message");
|
|
||||||
|
|
||||||
class Ticket extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
status: { type: Sequelize.STRING, defaultValue: "pending" },
|
|
||||||
userId: { type: Sequelize.INTEGER, defaultValue: null },
|
|
||||||
unreadMessages: { type: Sequelize.VIRTUAL },
|
|
||||||
lastMessage: { type: Sequelize.STRING },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addHook("afterFind", async result => {
|
|
||||||
if (result && result.length > 0) {
|
|
||||||
await Promise.all(
|
|
||||||
result.map(async ticket => {
|
|
||||||
ticket.unreadMessages = await Message.count({
|
|
||||||
where: { ticketId: ticket.id, read: false },
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addHook("beforeUpdate", async ticket => {
|
|
||||||
ticket.unreadMessages = await Message.count({
|
|
||||||
where: { ticketId: ticket.id, read: false },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(models) {
|
|
||||||
this.belongsTo(models.Contact, { foreignKey: "contactId", as: "contact" });
|
|
||||||
this.belongsTo(models.User, { foreignKey: "userId", as: "user" });
|
|
||||||
this.belongsTo(models.Whatsapp, {
|
|
||||||
foreignKey: "whatsappId",
|
|
||||||
as: "whatsapp",
|
|
||||||
});
|
|
||||||
this.hasMany(models.Message, { foreignKey: "ticketId", as: "messages" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Ticket;
|
|
||||||
89
backend/src/models/Ticket.ts
Normal file
89
backend/src/models/Ticket.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
DataType,
|
||||||
|
PrimaryKey,
|
||||||
|
ForeignKey,
|
||||||
|
BelongsTo,
|
||||||
|
HasMany,
|
||||||
|
AutoIncrement,
|
||||||
|
AfterFind,
|
||||||
|
BeforeUpdate
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
|
||||||
|
import Contact from "./Contact";
|
||||||
|
import Message from "./Message";
|
||||||
|
import User from "./User";
|
||||||
|
import Whatsapp from "./Whatsapp";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class Ticket extends Model<Ticket> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ defaultValue: "pending" })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@Column(DataType.VIRTUAL)
|
||||||
|
unreadMessages: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
lastMessage: string;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@ForeignKey(() => User)
|
||||||
|
@Column
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => User)
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@ForeignKey(() => Contact)
|
||||||
|
@Column
|
||||||
|
contactId: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => Contact)
|
||||||
|
contact: Contact;
|
||||||
|
|
||||||
|
@ForeignKey(() => Whatsapp)
|
||||||
|
@Column
|
||||||
|
whatsappId: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => Whatsapp)
|
||||||
|
whatsapp: Whatsapp;
|
||||||
|
|
||||||
|
@HasMany(() => Message)
|
||||||
|
messages: Message[];
|
||||||
|
|
||||||
|
@AfterFind
|
||||||
|
static async countTicketsUnreadMessages(tickets: Ticket[]): Promise<void> {
|
||||||
|
if (tickets && tickets.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
tickets.map(async ticket => {
|
||||||
|
ticket.unreadMessages = await Message.count({
|
||||||
|
where: { ticketId: ticket.id, read: false }
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeUpdate
|
||||||
|
static async countTicketUnreadMessags(ticket: Ticket): Promise<void> {
|
||||||
|
ticket.unreadMessages = await Message.count({
|
||||||
|
where: { ticketId: ticket.id, read: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ticket;
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
const bcrypt = require("bcryptjs");
|
|
||||||
|
|
||||||
class User extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
name: { type: Sequelize.STRING },
|
|
||||||
password: { type: Sequelize.VIRTUAL },
|
|
||||||
profile: { type: Sequelize.STRING, defaultValue: "admin" },
|
|
||||||
passwordHash: { type: Sequelize.STRING },
|
|
||||||
email: { type: Sequelize.STRING },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addHook("beforeSave", async user => {
|
|
||||||
if (user.password) {
|
|
||||||
user.passwordHash = await bcrypt.hash(user.password, 8);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPassword(password) {
|
|
||||||
return bcrypt.compare(password, this.passwordHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = User;
|
|
||||||
63
backend/src/models/User.ts
Normal file
63
backend/src/models/User.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
DataType,
|
||||||
|
BeforeCreate,
|
||||||
|
BeforeUpdate,
|
||||||
|
PrimaryKey,
|
||||||
|
AutoIncrement,
|
||||||
|
Default,
|
||||||
|
HasMany
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
import { hash, compare } from "bcryptjs";
|
||||||
|
import Ticket from "./Ticket";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class User extends Model<User> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column(DataType.VIRTUAL)
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
passwordHash: string;
|
||||||
|
|
||||||
|
@Default("admin")
|
||||||
|
@Column
|
||||||
|
profile: string;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@HasMany(() => Ticket)
|
||||||
|
tickets: Ticket[];
|
||||||
|
|
||||||
|
@BeforeUpdate
|
||||||
|
@BeforeCreate
|
||||||
|
static hashPassword = async (instance: User): Promise<void> => {
|
||||||
|
if (instance.password) {
|
||||||
|
instance.passwordHash = await hash(instance.password, 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public checkPassword = async (password: string): Promise<boolean> => {
|
||||||
|
return compare(password, this.getDataValue("passwordHash"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default User;
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const Sequelize = require("sequelize");
|
|
||||||
|
|
||||||
class Whatsapp extends Sequelize.Model {
|
|
||||||
static init(sequelize) {
|
|
||||||
super.init(
|
|
||||||
{
|
|
||||||
session: { type: Sequelize.TEXT },
|
|
||||||
qrcode: { type: Sequelize.TEXT },
|
|
||||||
name: { type: Sequelize.STRING, unique: true, allowNull: false },
|
|
||||||
status: { type: Sequelize.STRING },
|
|
||||||
battery: { type: Sequelize.STRING },
|
|
||||||
plugged: { type: Sequelize.BOOLEAN },
|
|
||||||
default: {
|
|
||||||
type: Sequelize.BOOLEAN,
|
|
||||||
defaultValue: false,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(models) {
|
|
||||||
this.hasMany(models.Ticket, { foreignKey: "whatsappId", as: "tickets" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Whatsapp;
|
|
||||||
59
backend/src/models/Whatsapp.ts
Normal file
59
backend/src/models/Whatsapp.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
Model,
|
||||||
|
DataType,
|
||||||
|
PrimaryKey,
|
||||||
|
AutoIncrement,
|
||||||
|
Default,
|
||||||
|
AllowNull,
|
||||||
|
HasMany,
|
||||||
|
Unique
|
||||||
|
} from "sequelize-typescript";
|
||||||
|
import Ticket from "./Ticket";
|
||||||
|
|
||||||
|
@Table
|
||||||
|
class Whatsapp extends Model<Whatsapp> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@AllowNull
|
||||||
|
@Unique
|
||||||
|
@Column(DataType.TEXT)
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column(DataType.TEXT)
|
||||||
|
session: string;
|
||||||
|
|
||||||
|
@Column(DataType.TEXT)
|
||||||
|
qrcode: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
battery: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
plugged: boolean;
|
||||||
|
|
||||||
|
@Default(false)
|
||||||
|
@AllowNull
|
||||||
|
@Column
|
||||||
|
isDefault: boolean;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdatedAt
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@HasMany(() => Ticket)
|
||||||
|
tickets: Ticket[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Whatsapp;
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
|
|
||||||
const AuthRoutes = require("./routes/auth");
|
|
||||||
const TicketsRoutes = require("./routes/tickets");
|
|
||||||
const MessagesRoutes = require("./routes/messages");
|
|
||||||
const ContactsRoutes = require("./routes/contacts");
|
|
||||||
const WhatsRoutes = require("./routes/whatsapp");
|
|
||||||
const UsersRoutes = require("./routes/users");
|
|
||||||
const SettingsRoutes = require("./routes/settings");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.use("/auth", AuthRoutes);
|
|
||||||
routes.use(TicketsRoutes);
|
|
||||||
routes.use(MessagesRoutes);
|
|
||||||
routes.use(ContactsRoutes);
|
|
||||||
routes.use(WhatsRoutes);
|
|
||||||
routes.use(UsersRoutes);
|
|
||||||
routes.use(SettingsRoutes);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
const SessionController = require("../../controllers/SessionController");
|
|
||||||
const UserController = require("../../controllers/UserController");
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.post("/signup", UserController.store);
|
|
||||||
|
|
||||||
routes.post("/login", SessionController.store);
|
|
||||||
|
|
||||||
routes.get("/check", isAuth, (req, res) => {
|
|
||||||
res.status(200).json({ authenticated: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
|
|
||||||
const ContactController = require("../../controllers/ContactController");
|
|
||||||
const ImportPhoneContactsController = require("../../controllers/ImportPhoneContactsController");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.post("/contacts/import", isAuth, ImportPhoneContactsController.store);
|
|
||||||
|
|
||||||
routes.get("/contacts", isAuth, ContactController.index);
|
|
||||||
|
|
||||||
routes.get("/contacts/:contactId", isAuth, ContactController.show);
|
|
||||||
|
|
||||||
routes.post("/contacts", isAuth, ContactController.store);
|
|
||||||
|
|
||||||
routes.put("/contacts/:contactId", isAuth, ContactController.update);
|
|
||||||
|
|
||||||
routes.delete("/contacts/:contactId", isAuth, ContactController.delete);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
|
|
||||||
const MessageController = require("../../controllers/MessageController");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.get("/messages/:ticketId", isAuth, MessageController.index);
|
|
||||||
|
|
||||||
routes.post("/messages/:ticketId", isAuth, MessageController.store);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
|
|
||||||
const SettingController = require("../../controllers/SettingController");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.get("/settings", isAuth, SettingController.index);
|
|
||||||
|
|
||||||
// routes.get("/settings/:settingKey", isAuth, SettingsController.show);
|
|
||||||
|
|
||||||
routes.put("/settings/:settingKey", isAuth, SettingController.update);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
|
|
||||||
const TicketController = require("../../controllers/TicketController");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.get("/tickets", isAuth, TicketController.index);
|
|
||||||
|
|
||||||
routes.post("/tickets", isAuth, TicketController.store);
|
|
||||||
|
|
||||||
routes.put("/tickets/:ticketId", isAuth, TicketController.update);
|
|
||||||
|
|
||||||
routes.delete("/tickets/:ticketId", isAuth, TicketController.delete);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
const express = require("express");
|
|
||||||
|
|
||||||
const isAuth = require("../../middleware/is-auth");
|
|
||||||
const UserController = require("../../controllers/UserController");
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
|
|
||||||
routes.get("/users", isAuth, UserController.index);
|
|
||||||
|
|
||||||
routes.post("/users", isAuth, UserController.store);
|
|
||||||
|
|
||||||
routes.put("/users/:userId", isAuth, UserController.update);
|
|
||||||
|
|
||||||
routes.get("/users/:userId", isAuth, UserController.show);
|
|
||||||
|
|
||||||
routes.delete("/users/:userId", isAuth, UserController.delete);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user