diff --git a/.gitignore b/.gitignore index c86c332..19b5ead 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,10 @@ WWebJS .env.development.local .env.test.local .env.production.local +.env.save +nano.save npm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* + diff --git a/TEST_SERVER1/test/api/.env b/TEST_SERVER1/test/api/.env new file mode 100644 index 0000000..98b5db3 --- /dev/null +++ b/TEST_SERVER1/test/api/.env @@ -0,0 +1,8 @@ +PORT=8019 +PORT_START=8020 +BASE_URL=http://localhost +PASS="strongpassword, strongpassword32" +DB_MONGO_URL=mongodb://localhost:27017 +DB_MONGO_NAME=session_out_omnihit_db +DB_MONGO_NAME_CAMPAIGN=broker_omnihit + \ No newline at end of file diff --git a/TEST_SERVER1/test/api/app.js b/TEST_SERVER1/test/api/app.js new file mode 100644 index 0000000..23ac5ce --- /dev/null +++ b/TEST_SERVER1/test/api/app.js @@ -0,0 +1,520 @@ +const express = require('express') +const bodyparser = require('body-parser') +const dotenv = require('dotenv') +dotenv.config({ path: '.env' }) +const copyFolder = require('./helpers/copyFolder') +const createDir = require('./helpers/createDir') +const createFile = require('./helpers/createFile') +const path = require('path') +const db_info = require('./db_conn') +const fs = require('fs') +let mysql_conn = require('./helpers/mysql_conn.js') +const { exec, execSync, spawn } = require('child_process') + +const startPm2Process = require('./helpers/startPm2Process') +const removeDir = require('./helpers/remove_dir') +const setSessionName = require('./helpers/setSessionNumber') +const getNumberFromName = require('./helpers/getNumberSequence') +const pm2 = require('pm2') +const bcrypt = require('bcrypt') +const OmnihitDBConn = require('./model/db_conn') + +const app = express() + +app.use(bodyparser.json()) + +app.get('/', function (req, res) { + return res.send('Express + TypeScript Server') +}) + +app.post('/api/session', async function (req, res) { + let { app_name, whatsappId, client_url, number } = req.body + + let oldNumber = '' + + if (app_name) { + app_name = app_name.trim() + } + + console.log('__dirname: ', path.join(__dirname, '..', app_name)) + + console.log( + 'app_name: ', + app_name, + ' | whatsappId: ', + whatsappId, + ' | client_url: ', + client_url + ) + + const sessionsPath = path.join(__dirname, '..', 'sessions') + + const directoriesInDIrectory = fs + .readdirSync(sessionsPath, { withFileTypes: true }) + .filter((item) => item.isDirectory()) + .map((item) => item.name) + + console.log('directoriesInDIrectory: ', directoriesInDIrectory) + + const dirExist = directoriesInDIrectory.filter((e) => e.trim() == app_name) + + let dirSessionsApp = path.join(sessionsPath, app_name) + + if (dirExist.length == 0) { + let create = createDir(dirSessionsApp) + + if (!create) { + res.status(500).json({ message: 'Cannot create the directory path!' }) + return + } + } + + + let appPort = [] + + let existSubDir = false + + for (let i = 0; i < directoriesInDIrectory.length; i++) { + console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i]) + + const subDir = fs + .readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), { + withFileTypes: true, + }) + .filter((item) => item.isDirectory()) + .map((item) => item.name) + + for (let x = 0; x < subDir.length; x++) { + console.log('subdir: ', subDir[x]) + + let whatsId = subDir[x].split('_')[0] + + if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) { + let currPath = path.join( + sessionsPath, + directoriesInDIrectory[i], + subDir[x] + ) + + console.log( + 'PATH: ', + path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]) + ) + + oldNumber = subDir[x].split('_')[1] + + if (oldNumber != number) { + deletePm2Process(subDir[x], currPath) + + removeDir(currPath) + } else { + res.send('ok') + return + } + } + + let auxPort = subDir[x].split('_')[3] + + console.log('---------> auxPort: ' + auxPort) + + if (auxPort) { + auxPort = +auxPort.trim() + + if (!isNaN(auxPort)) { + appPort.push(auxPort) + } + } + + existSubDir = true + } + } + + appPort = existSubDir ? Math.max(...appPort) + 1 : process.env.PORT_START + + console.log('new port: ', appPort) + + let dirSessionAppName + + let numberSession = 1 + + let lstPass = process.env.PASS + + if (!lstPass) { + console.log('PASS VARIABLE NOT FOUND INTO .ENV!') + return res.send('OK') + } + + let db_credentials + try { + db_credentials = await OmnihitDBConn.findOne({ client_url }) + + if (!db_credentials) { + db_credentials = new OmnihitDBConn({ + client_url: client_url, + db_conf: { + DB: '', + DB_HOST: '', + DB_USER: '', + DB_PASS: '', + DB_PORT: '', + }, + }) + await db_credentials.save() + return res.send('ok') + } + } catch (error) { + console.log(error) + } + + if (db_credentials && db_credentials.db_conf.DB.trim().length > 0) { + lstPass = lstPass.split(',') + let password = null + + // password = await lstPass.find( + // async (pass) => + // await bcrypt.compare(pass.trim(), db_credentials.db_conf.DB_PASS) + // ) + + for (let i = 0; i < lstPass.length; i++) { + const hasPass = await bcrypt.compare( + lstPass[i].trim(), + db_credentials.db_conf.DB_PASS + ) + + if (hasPass) { + password = lstPass[i].trim() + break + } + } + + if (password) { + db_credentials.db_conf.DB_PASS = password + } else { + return res.send('ok') + } + } + + if ( + db_credentials.db_conf && + Object.keys(db_credentials.db_conf).length > 0 + ) { + const whatsapp_numbers = await new Promise((resolve, reject) => { + mysql_conn(db_credentials.db_conf).query( + 'SELECT name, number FROM Whatsapps WHERE name LIKE ?', + [`%${number}%`], + (err, result) => { + if (err) { + reject(err) + } else { + resolve(result) + } + } + ) + }) + + let session_num = [] + + if (whatsapp_numbers && whatsapp_numbers.length > 0) { + console.log('whatsapp_numbers.length: ', whatsapp_numbers.length) + + if (whatsapp_numbers.length == 5) { + res.status(400).json({ + message: 'Cannot create more than 4 sessions from the same number', + }) + return + } + + let regex = /-> [a-zA-Z]\d$/ + + let numbered_sessions = whatsapp_numbers.filter((e) => regex.test(e.name)) + + console.log('numbered_sessions: ', numbered_sessions) + + session_num = numbered_sessions.map((e) => + parseInt( + e.name + .split('->') + [e.name.split('->').length - 1].trim() + .match(/\d+/)[0] + ) + ) + + console.log('session_num', session_num) + } + + let index = 1 + + while (index <= 4) { + if (!session_num.includes(index)) { + console.log(index) + numberSession = index + break + } + index++ + } + } + + // numberSession = Math.max(...session_number) + 1 + + console.log('Number session: ', numberSession) + + // } + + dirSessionAppName = `${whatsappId}_${number}_${numberSession}_${appPort}` + + destDir = path.join(dirSessionsApp, dirSessionAppName) + + originDir = path.join(__dirname, '..', 'whats') + + copyFolder(originDir, destDir) + + if ( + db_credentials.db_conf && + Object.keys(db_credentials.db_conf).length > 0 + ) { + console.log('***SUCCESS SEED DIRECTORY CREATED***') + + let whatsName + + const whatsapp = await new Promise((resolve, reject) => { + mysql_conn(db_credentials.db_conf).query( + 'SELECT name from Whatsapps where id = ?', + [whatsappId], + (err, result) => { + if (err) { + reject(err) + } else { + resolve(result) + } + } + ) + }) + + if (whatsapp[0]['name']?.split('->')?.length > 0) { + whatsName = `${whatsapp[0]['name'].split('->')[0]} -> S${numberSession}` + } else { + whatsName = `${whatsapp[0]['name']} -> S${numberSession}` + } + + console.log('whatsName: ', whatsName) + + console.log( + `url: ${process.env.BASE_URL}:${appPort}\n whatsname: ${whatsName}\n whatsappId: ${whatsappId}` + ) + + await new Promise((resolve, reject) => { + mysql_conn(db_credentials.db_conf).query( + 'UPDATE Whatsapps SET url = ?, name = ? where id = ?', + [`${process.env.BASE_URL}:${appPort}`, `${whatsName}`, whatsappId], + + function (err, result) { + if (err) { + reject(err) + console.log('===> ERROR: ' + err) + } else { + resolve(result) + // console.log('RESULT: ', result) + } + // else + // console.log('myslq result: ', result); + } + ) + }) + + let whatsappName = `${number} - s${numberSession}` + + console.log('-------------- numberSession', numberSession) + + if (whatsapp.length > 0) { + if (whatsapp[0]['name'].split(' ').length > 0) { + whatsappName = `${whatsapp[0]['name'].split(' ')[0] + } - S${numberSession}` + } + } + + console.log('whatsapp: ', whatsapp) + console.log("whatsapp[0]['name']: ", whatsapp[0]['name']) + + const keys = Object.keys(db_credentials.db_conf) + + var stream = fs.createWriteStream(path.join(destDir, '.env')) + stream.once('open', function (fd) { + stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n') + stream.write(`MOBILEUID=${number}\n`) + stream.write(`MOBILENAME=${whatsappName}\n`) + stream.write(`OLD_MOBILEUID=${oldNumber}\n`) + stream.write('\n') + + stream.write('# PORT NUMBER FOR THIS API\n') + stream.write(`PORT=${appPort}\n`) + stream.write('\n') + + stream.write('# URL FROM THE OMNIHIT BACKEND API\n') + stream.write(`CLIENT_URL=${client_url}\n`) + stream.write('\n') + + stream.write('# OMNIHIT DATABASE\n') + keys.forEach((key, index) => { + stream.write(`${key}=${db_credentials.db_conf[key]}\n`) + }) + stream.write('\n') + + stream.write( + `# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE\n` + ) + stream.write(`WHATSAPP_ID=${whatsappId}\n`) + stream.write('\n') + + stream.write('# MONGO CONNECTION\n') + stream.write(`DB_MONGO_URL=${process.env.DB_MONGO_URL}\n`) + stream.write('\n') + + stream.write('# MONGO COLLECTION\n') + stream.write(`DB_MONGO_NAME=${process.env.DB_MONGO_NAME_CAMPAIGN}\n`) + stream.write('\n') + + stream.end() + }) + + console.log('----------------destDir: ', destDir) + + execSync(`npm install`, { cwd: destDir }, (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`) + return + } + if (stderr) { + console.log(`stderr: ${stderr}`) + return + } + console.log(`stdout: ${stdout}`) + }) + + const env = { + PORT: appPort, + DB_MONGO_URL: process.env.DB_MONGO_URL, + DB_MONGO_NAME: process.env.DB_MONGO_NAME_CAMPAIGN + } + + startPm2Process(dirSessionAppName, 'app.js', destDir, env) + } + + res.send('OK') +}) + +app.post('/api/session/edit', async function (req, res) { + const { app_name, whatsappId, client_url, number } = req.body +}) + +app.post('/api/session/del', async function (req, res) { + let { whatsappId, app_name } = req.body + + if (app_name) { + app_name = app_name.trim() + } + + const sessionsPath = path.join(__dirname, '..', 'sessions') + + const directoriesInDIrectory = fs + .readdirSync(sessionsPath, { withFileTypes: true }) + .filter((item) => item.isDirectory()) + .map((item) => item.name) + + console.log('directoriesInDIrectory: ', directoriesInDIrectory) + + const dirExist = directoriesInDIrectory.filter((e) => e.trim() == app_name) + + console.log('dirExist: ', dirExist) + + if (dirExist.length == 0) res.send('ok') + + for (let i = 0; i < directoriesInDIrectory.length; i++) { + console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i]) + + const subDir = fs + .readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), { + withFileTypes: true, + }) + .filter((item) => item.isDirectory()) + .map((item) => item.name) + + for (let x = 0; x < subDir.length; x++) { + console.log('subdir: ', subDir[x]) + + let whatsId = subDir[x].split('_')[0] + + if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) { + let currPath = path.join( + sessionsPath, + directoriesInDIrectory[i], + subDir[x] + ) + + deletePm2Process(subDir[x], currPath) + + console.log('currPath: ', currPath) + + removeDir(currPath) + + return res.send('ok') + } + } + } + + res.send('ok') +}) + +app.listen(process.env.PORT || 8003, function () { + console.log( + '\u26A1[server]: Server is running at Port ::: ' + process.env.PORT || 8003 + ) +}) + +process.on('uncaughtException', function (err) { + console.error(' ') + console.error( + '----- ' + new Date().toUTCString() + ' ----------------------------------' + ) + console.error('Erro uncaughtException: ', err.message) + console.error(err.stack) + console.error(' ') + return +}) + +function deletePm2Process(process_name, currPath) { + pm2.connect(function (err) { + if (err) { + console.error(err) + } + + pm2.list(function (err, processes) { + if (err) { + console.error(err) + } + + processes.forEach(function (process) { + console.log('.........process.name: ', process.name) + + if (process.name === process_name) { + execSync( + `pm2 delete ${process_name} && pm2 save --force`, + { cwd: currPath }, + (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`) + return + } + if (stderr) { + console.log(`stderr: ${stderr}`) + return + } + console.log(`stdout: ${stdout}`) + } + ) + } + }) + + pm2.disconnect() + }) + }) +} diff --git a/TEST_SERVER1/test/api/db/connMongo.js b/TEST_SERVER1/test/api/db/connMongo.js new file mode 100644 index 0000000..2026782 --- /dev/null +++ b/TEST_SERVER1/test/api/db/connMongo.js @@ -0,0 +1,11 @@ +const mongoose = require('mongoose') +require('dotenv').config({ path: `${process.cwd()}/.env` }) + +async function main(){ + await mongoose.connect(process.env.DB_MONGO_URL, { dbName: process.env.DB_MONGO_NAME }) + console.log('Conectou ao Mongoose!') +} + +main().catch((err)=>console.log(err)) + +module.exports = mongoose \ No newline at end of file diff --git a/TEST_SERVER1/test/api/db_conn.js b/TEST_SERVER1/test/api/db_conn.js new file mode 100644 index 0000000..93c45dd --- /dev/null +++ b/TEST_SERVER1/test/api/db_conn.js @@ -0,0 +1,27 @@ +const db = [ + + { + client_url: "http://localhost:8080", + db_conf: { + DB: "whaticket", + DB_HOST: "localhost", + DB_USER: "whaticket", + DB_PASS: "strongpassword", + DB_PORT: "3306" + } + }, + + { + client_url: "http://localhost:8081", + db_conf: { + DB: "whaticket", + DB_HOST: "localhost", + DB_USER: "whaticket", + DB_PASS: "strongpassword", + DB_PORT: "3306" + } + } + +] + +module.exports = db; \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/PassHash.js b/TEST_SERVER1/test/api/helpers/PassHash.js new file mode 100644 index 0000000..81d1be1 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/PassHash.js @@ -0,0 +1,20 @@ +const bcrypt = require('bcrypt') + +// const pass = async () => { +// // create a password +// const salt = await bcrypt.genSalt(12) +// const passwordHash = await bcrypt.hash('7901228899', salt) +// console.log(passwordHash) +// } +// pass() + +const passDec = async () => { + const _pass = await bcrypt.compare( + 'strongpassword', + '$2b$12$PZ8N1jU77nnNUCCGyKTMNOi2QI7X/SgPsISVQfr.cQ/jgdx5Z7AqC' + ) + console.log('_pass: ', _pass) +} +passDec() + +console.log('process.cwd(): ', process.cwd()) diff --git a/TEST_SERVER1/test/api/helpers/copyFolder.js b/TEST_SERVER1/test/api/helpers/copyFolder.js new file mode 100644 index 0000000..5af7bd8 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/copyFolder.js @@ -0,0 +1,17 @@ +const fsPromises = require("fs/promises"); +const fs = require('fs-extra') + +// Delete a directory and its children +function copyFolder(sourcePath, destPath) { + + fs.copySync(sourcePath, destPath, { overwrite: true }, (err) => { + if (err) { + console.error(err); + } else { + console.log("Copy dir success!"); + } + }); + +} + +module.exports = copyFolder; \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/createDir.js b/TEST_SERVER1/test/api/helpers/createDir.js new file mode 100644 index 0000000..f8626e0 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/createDir.js @@ -0,0 +1,22 @@ +const fs = require('fs'); + +function createDir(dir) { + + // create new directory + try { + // check if directory already exists + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + console.log("Directory is created."); + } else { + console.log("Directory already exists."); + } + } catch (err) { + console.log(err); + return false + } + + return true +} + +module.exports = createDir; \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/createFile.js b/TEST_SERVER1/test/api/helpers/createFile.js new file mode 100644 index 0000000..ff85bcd --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/createFile.js @@ -0,0 +1,17 @@ +const fs = require('fs'); + +function createFile(saveDir, rows, fileName) { + + var stream = fs.createWriteStream(path.join(saveDir, fileName)); + + stream.once('open', function (fd) { + + rows.forEach(element => { + stream.write(element); + }); + + stream.end(); + }); +} + +module.exports = createFile \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/findAndDeletePm2Process.js b/TEST_SERVER1/test/api/helpers/findAndDeletePm2Process.js new file mode 100644 index 0000000..c13fe33 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/findAndDeletePm2Process.js @@ -0,0 +1,46 @@ +const pm2 = require('pm2'); + + +function findAndDeletePm2Process(pm2_process_name) { + + pm2.connect(function (err) { + if (err) { + console.error(err); + // process.exit(2); + } + + pm2.list(function (err, processes) { + if (err) { + console.error(err); + // process.exit(2); + } + + const processToDelete = processes.find(process => process.name === pm2_process_name); + + if (!processToDelete) { + console.error('Process not found'); + // process.exit(2); + } + else { + + pm2.delete(processToDelete.pm_id, function (err) { + if (err) { + console.error(err); + // process.exit(2); + } + else{ + console.log(`Process deleted: ${pm2_process_name}`) + } + + pm2.disconnect(); + }); + + } + + }); + }); + + +} + +module.exports = findAndDeletePm2Process \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/getNumberSequence.js b/TEST_SERVER1/test/api/helpers/getNumberSequence.js new file mode 100644 index 0000000..92ecfcd --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/getNumberSequence.js @@ -0,0 +1,39 @@ + +function getNumberFromName(name) { + + name = name + ' ' + + let number = name.split('') + + let newNumber = '' + let list = [] + + for (let i = 0; i < number.length; i++) { + + if (!isNaN(Number(number[i])) && number[i].trim().length > 0) { + newNumber += number[i] + } + else { + if (!isNaN(Number(newNumber)) && newNumber.trim().length > 0) { + list.push(newNumber) + } + newNumber = '' + } + } + + // longestString = list.filter(str => str.length === Math.max(...list.map(s => s.length)))[0]; + + // console.log('list: ', list) + + + let longestString = ""; // variable to store the longest string + for (let i = 0; i < list.length; i++) { + if (list[i].length > longestString.length) { + longestString = list[i]; + } + } + + return longestString +} + +module.exports = getNumberFromName; \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/mysql_conn.js b/TEST_SERVER1/test/api/helpers/mysql_conn.js new file mode 100644 index 0000000..a75fa8f --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/mysql_conn.js @@ -0,0 +1,109 @@ +const dotenv = require('dotenv'); +dotenv.config({ path: `${process.cwd()}/.env` }); +const path = require('path') + + +function mysql_conn(config) { + // Ubicua Plataform - MYSQL Module + try { + var mysql_npm = require('mysql'); + } catch (err) { + console.log("Cannot find `mysql` module. Is it installed ? Try `npm install mysql` or `npm install`."); + } + + var db_config = { + host: config.DB_HOST, + user: config.DB_USER, + password: config.DB_PASS, + database: config.DB, + charset: 'utf8mb4_general_ci', + port: config.DB_PORT + }; + + + + //- + //- Create the connection variable + //- + var connection = mysql_npm.createPool(db_config); + + + //- + //- Establish a new connection + //- + connection.getConnection(function (err) { + if (err) { + // mysqlErrorHandling(connection, err); + console.log("\n\t *** Cannot establish a connection with the database. ***"); + + connection = reconnect(connection); + } else { + console.log("\n\t *** New connection established with the database. ***") + } + }); + + + //- + //- Reconnection function + //- + function reconnect(connection) { + console.log("\n New connection tentative..."); + + //- Create a new one + connection = mysql_npm.createPool(db_config); + + //- Try to reconnect + connection.getConnection(function (err) { + if (err) { + //- Try to connect every 2 seconds. + setTimeout(reconnect(connection), 2000); + } else { + console.log("\n\t *** New connection established with the database. ***") + return connection; + } + }); + } + + + //- + //- Error listener + //- + connection.on('error', function (err) { + + //- + //- The server close the connection. + //- + if (err.code === "PROTOCOL_CONNECTION_LOST") { + console.log("/!\\ Cannot establish a connection with the database. /!\\ (" + err.code + ")"); + return reconnect(connection); + } + + else if (err.code === "PROTOCOL_ENQUEUE_AFTER_QUIT") { + console.log("/!\\ Cannot establish a connection with the database. /!\\ (" + err.code + ")"); + return reconnect(connection); + } + + else if (err.code === "PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR") { + console.log("/!\\ Cannot establish a connection with the database. /!\\ (" + err.code + ")"); + return reconnect(connection); + } + + else if (err.code === "PROTOCOL_ENQUEUE_HANDSHAKE_TWICE") { + console.log("/!\\ Cannot establish a connection with the database. /!\\ (" + err.code + ")"); + } + + else { + console.log("/!\\ Cannot establish a connection with the database. /!\\ (" + err.code + ")"); + return reconnect(connection); + } + + }); + + return connection +} + + +//- +//- Export +//- +module.exports = mysql_conn; diff --git a/TEST_SERVER1/test/api/helpers/postData.js b/TEST_SERVER1/test/api/helpers/postData.js new file mode 100644 index 0000000..1fb0f5d --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/postData.js @@ -0,0 +1,17 @@ +import axios from "axios"; + +async function postData(url, body = {}) { + + let response; + + try { + response = await axios.post(url, body); + console.log(response.data); // handle successful response + } catch (error) { + console.error(error); // handle error + } + + return response +} + +module.exports = postData \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/remove_dir.js b/TEST_SERVER1/test/api/helpers/remove_dir.js new file mode 100644 index 0000000..8f48f22 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/remove_dir.js @@ -0,0 +1,28 @@ +const fsPromises = require("fs/promises"); +const fs = require('fs') + +// Delete a directory and its children +const removeDir = async (dirPath) => { + + if (fs.existsSync(dirPath)) { + + try { + await fsPromises.rm(dirPath, { recursive: true, force: true }); + console.log("Directory removed!"); + + return true + } + catch (err) { + console.log('An error occurred while removing the directory: ', err); + } + + } + else { + console.log('Directory not found to remove: ', dirPath) + } + + return false + +} + +module.exports = removeDir ; \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/sessionCRUD.js b/TEST_SERVER1/test/api/helpers/sessionCRUD.js new file mode 100644 index 0000000..6afcd5c --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/sessionCRUD.js @@ -0,0 +1,152 @@ + +import os from 'os'; +import dir from 'path'; +import fs from 'fs'; + +export const setJSON = (obj) => { + + const sessionControlFile = dir.join(process.cwd(), `sessionsDir.json`); + + try { + + if (fs.existsSync(sessionControlFile)) { + + const sessionsDir = fs.readFileSync(sessionControlFile, { encoding: 'utf8', flag: 'r' }); + + let lstRestore = JSON.parse(sessionsDir) + + lstRestore.push(obj) + + fs.writeFileSync(sessionControlFile, JSON.stringify(lstRestore), "utf8"); + + } else { + + console.log('sessionsDir.json file not found! It will be created.'); + + if (Array.isArray(obj)) { + + fs.writeFileSync(sessionControlFile, JSON.stringify(obj), "utf8"); + + } + else { + + fs.writeFileSync(sessionControlFile, JSON.stringify([obj]), "utf8"); + + } + + + } + + } catch (error) { + console.log('There was an error on try to read the sessionsDir.json file: ', error) + } + +} + +export const shifRestoreControll = () => { + + const sessionControlFile = dir.join(os.tmpdir(), `sessionsDir.json`); + + try { + + if (fs.existsSync(sessionControlFile)) { + + const sessionsDir = fs.readFileSync(sessionControlFile, { encoding: 'utf8', flag: 'r' }); + + let lstRestore: any = JSON.parse(sessionsDir) + + let whatsapp: any = lstRestore.shift() + + fs.writeFileSync(sessionControlFile, JSON.stringify(lstRestore), "utf8"); + + return whatsapp + + } + + } catch (error) { + console.log('There was an error on try to read the sessionsDir.json file: ', error) + } + + return {} + +} + +export const delRestoreControllFile = () => { + + const sessionControlFile = dir.join(os.tmpdir(), `sessionsDir.json`); + + try { + + if (fs.existsSync(sessionControlFile)) { + + fs.unlinkSync(sessionControlFile) + + } else { + + console.log('sessionsDir.json file not found!'); + + } + + } catch (error) { + console.log('There was an error on try delete the sessionsDir.json file: ', error) + } + +} + + + + +export const getRestoreControll = () => { + + const sessionControlFile = dir.join(os.tmpdir(), `sessionsDir.json`); + + try { + + if (fs.existsSync(sessionControlFile)) { + + const sessionsDir = fs.readFileSync(sessionControlFile, { encoding: 'utf8', flag: 'r' }); + + let lstRestore: any = JSON.parse(sessionsDir) + + return lstRestore + + + } else { + + console.log('sessionsDir.json file not found!'); + + } + + } catch (error) { + console.log('There was an error on try to read the sessionsDir.json file: ', error) + } + + return [] + +} + + +export const _restore = async (whatsapp: Whatsapp, msg_file_title: string) => { + + return + + if (whatsapp.status != 'RESTORING' && whatsapp.status != 'qrcode') { + + console.log('THE WHATSAAP ID: ', whatsapp.id, ' WILL BE RESTORED SOON!') + + await whatsapp.update({ status: "RESTORING", }); + + const io = getIO(); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + + // await insertOrUpeateWhatsCache(`whatsapp:${whatsapp.id}`, { status: "RESTORING", }) + + setTimeout(async () => await autoRestore(whatsapp.id, msg_file_title), 95000); + + } + +} \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/setSessionNumber.js b/TEST_SERVER1/test/api/helpers/setSessionNumber.js new file mode 100644 index 0000000..eb94fce --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/setSessionNumber.js @@ -0,0 +1,105 @@ +const fs = require('fs'); +let mysql_conn = require('./mysql_conn.js'); + +async function sessionNumber(db_info, whatsappId, number, dirSessionsApp) { + + let numberSession = 1 + + let whatsappName + + const dirSessionsNumberAppDirectories = fs.readdirSync(dirSessionsApp, { withFileTypes: true }) + .filter((item) => item.isDirectory() && item.name.includes(`${number}`)) + .map((item) => item.name); + + if (dirSessionsNumberAppDirectories.length > 0) { + + let session_number = dirSessionsNumberAppDirectories.map((e) => +e.split('_')[2]) + + numberSession = Math.max(...session_number) + 1 + + console.log('Number session: ', numberSession) + + + if (numberSession > 4) { + res.status(400).json({ message: 'Cannot create more than 4 sessions from the same number' }) + return + } + + } + + + let db = db_info.filter((e) => e.client_url == client_url) + + + if (db && db.length > 0) { + + db = db[0].db_conf + + let whatsName + + const whatsapp = await new Promise((resolve, reject) => { + + mysql_conn(db).query("SELECT name from Whatsapps where id = ?", [whatsappId], (err, result) => { + + if (err) { + reject(err) + } + else { + resolve(result) + } + }); + + }) + + if (whatsapp[0]["name"].split('->').length > 0) { + whatsName = `${whatsapp[0]["name"].split('->')[0]} -> S${numberSession}` + } + else { + whatsName = `${whatsapp[0]["name"]} -> S${numberSession}` + } + + console.log('whatsName: ', whatsName) + + console.log(`url: ${process.env.BASE_URL}:${appPort}\n whatsname: ${whatsName}\n whatsappId: ${whatsappId}`) + + // await new Promise((resolve, reject) => { + // mysql_conn(db).query("UPDATE Whatsapps SET name = ? where id = ?", [ `${whatsName}`, whatsappId], + + // function (err, result) { + // if (err) { + // reject(err) + // console.log("===> ERROR: " + err); + // } + // else { + // resolve(result) + // console.log('RESULT: ', result) + // } + // // else + // // console.log('myslq result: ', result); + // }); + // }) + + whatsappName = `${number} - s${numberSession}` + + console.log('-------------- numberSession', numberSession) + + if (whatsapp.length > 0) { + + if (whatsapp[0]['name'].split(' ').length > 0) { + + whatsappName = `${whatsapp[0]['name'].split(' ')[0]} - S${numberSession}` + + } + + } + + + } + + console.log('---------> whatsappName', whatsappName) + + return whatsappName + +} + +module.exports = sessionNumber \ No newline at end of file diff --git a/TEST_SERVER1/test/api/helpers/startPm2Process.js b/TEST_SERVER1/test/api/helpers/startPm2Process.js new file mode 100644 index 0000000..5c27170 --- /dev/null +++ b/TEST_SERVER1/test/api/helpers/startPm2Process.js @@ -0,0 +1,50 @@ +const pm2 = require('pm2') +const { execSync } = require("child_process") + +function startPm2Process(process_name, file, path, env) { + + pm2.connect(function (err) { + if (err) { + console.error(err) + // process.exit(2); + } + + console.log('ENV PM2: ', env) + + pm2.start({ + name: process_name, + script: file, + cwd: path, + env + // env: { + // NODE_ENV: 'production', + + // PORT: port, + // } + // additional options here if needed + }, function (err, apps) { + if (err) { + console.error(err) + // process.exit(2); + } + else { + execSync(`pm2 save --force`, { cwd: path }, (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`) + return + } + if (stderr) { + console.log(`stderr: ${stderr}`) + return + } + console.log(`stdout: ${stdout}`) + }) + } + + pm2.disconnect() + }) + }) + +} + +module.exports = startPm2Process \ No newline at end of file diff --git a/TEST_SERVER1/test/api/model/db_conn.js b/TEST_SERVER1/test/api/model/db_conn.js new file mode 100644 index 0000000..5f677b3 --- /dev/null +++ b/TEST_SERVER1/test/api/model/db_conn.js @@ -0,0 +1,33 @@ +const mongoose = require('../db/connMongo') +const { Schema } = mongoose + +const OmnihitDBConn = mongoose.model( + 'Omnihit_db_conn', + new Schema( + { + client_url: { + type: String, + }, + db_conf: { + DB: { + type: String, + }, + DB_HOST: { + type: String, + }, + DB_USER: { + type: String, + }, + DB_PASS: { + type: String, + }, + DB_PORT: { + type: String, + }, + }, + }, + { timestamps: true } + ) +) + +module.exports = OmnihitDBConn diff --git a/TEST_SERVER1/test/api/package-lock.json b/TEST_SERVER1/test/api/package-lock.json new file mode 100644 index 0000000..06387b3 --- /dev/null +++ b/TEST_SERVER1/test/api/package-lock.json @@ -0,0 +1,3522 @@ +{ + "name": "api-sessions-controller", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "api-sessions-controller", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bcrypt": "^5.1.0", + "body-parser": "^1.20.1", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "fs-extra": "^11.1.0", + "mongoose": "^7.4.0", + "mysql": "^2.18.1", + "nodemon": "^2.0.20", + "socket.io": "^4.5.4" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", + "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mongodb": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", + "dependencies": { + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "@mongodb-js/zstd": "^1.1.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.0.tgz", + "integrity": "sha512-oHE1eqodfKzugXRlQxpo+msIea7jPcRoayDuEMr50+bYwM/juA5f+1stjkWlXcg6vo1PdJFVA6DGaKOPLuG5mA==", + "dependencies": { + "bson": "^5.4.0", + "kareem": "2.5.1", + "mongodb": "5.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + }, + "@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "bson": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", + "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "mongodb": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", + "requires": { + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "mongoose": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.0.tgz", + "integrity": "sha512-oHE1eqodfKzugXRlQxpo+msIea7jPcRoayDuEMr50+bYwM/juA5f+1stjkWlXcg6vo1PdJFVA6DGaKOPLuG5mA==", + "requires": { + "bson": "^5.4.0", + "kareem": "2.5.1", + "mongodb": "5.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" + }, + "mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "requires": { + "debug": "4.x" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/TEST_SERVER1/test/api/package.json b/TEST_SERVER1/test/api/package.json new file mode 100644 index 0000000..389f14b --- /dev/null +++ b/TEST_SERVER1/test/api/package.json @@ -0,0 +1,24 @@ +{ + "name": "api-sessions-controller", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "nodemon app.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Adriano ", + "license": "MIT", + "dependencies": { + "bcrypt": "^5.1.0", + "body-parser": "^1.20.1", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "fs-extra": "^11.1.0", + "mongoose": "^7.4.0", + "mysql": "^2.18.1", + "nodemon": "^2.0.20", + "socket.io": "^4.5.4" + } +} diff --git a/TEST_SERVER1/test/whats/.env b/TEST_SERVER1/test/whats/.env new file mode 100644 index 0000000..9141735 --- /dev/null +++ b/TEST_SERVER1/test/whats/.env @@ -0,0 +1,25 @@ +# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE +MOBILEUID=5517988310949 +MOBILENAME=test - S1 + +# PORT NUMBER FOR THIS API +PORT=8029 + +# URL FROM THE OMNIHIT BACKEND API +CLIENT_URL=http://localhost:8080 + +# OMNIHIT DATABASE +DB=whaticket +DB_HOST=localhost +DB_USER=whaticket +DB_PASS=strongpassword +DB_PORT=3306 + +# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE +WHATSAPP_ID=223 + +# MONGO CONNECTION +DB_MONGO_URL=mongodb://localhost:27017 + +# MONGO COLLECTION +DB_MONGO_NAME=broker_omnihit \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/.gitignore b/TEST_SERVER1/test/whats/.gitignore new file mode 100644 index 0000000..e4750fb --- /dev/null +++ b/TEST_SERVER1/test/whats/.gitignore @@ -0,0 +1,31 @@ + + +# dependencies +node_modules +/node_modules + +/medias/*.* +/medias/in/*.* + + +/WWebJS/session-OmniHIT/Default/** + + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +DevToolsActivePort* + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/TEST_SERVER1/test/whats/app.js b/TEST_SERVER1/test/whats/app.js new file mode 100644 index 0000000..f8c030e --- /dev/null +++ b/TEST_SERVER1/test/whats/app.js @@ -0,0 +1,1260 @@ +'use strict' + +const { initIO } = require("./helpers/socket") + +const { ObjectId } = require('mongodb') + + +const backup_session = require('./helpers/backup_session') +const restore = require('./helpers/restore') + +const dbcloud = require('./funcs/dbcloud.js') + +const { Client, Location, List, Buttons, LocalAuth } = require('whatsapp-web.js/index') + + +const qrencode = require('qr-encode') +const axios = require('axios').default +const bodyparser = require('body-parser') +const path = require('path') +const fs = require('fs') +const express = require('express') +const FormData = require('form-data') + +// const { MessageMedia } = require('./node_modules/whatsapp-web.js/src/structures'); +let whatsappWebGlobalPath = path.join(process.env.NODE_PATH, 'whatsapp-web.js', '/src/structures') +whatsappWebGlobalPath = whatsappWebGlobalPath.replace(':', '') + +console.log('whatsappWebGlobalPath: ', whatsappWebGlobalPath) +console.log('process.env.NODE_PATH: ', process.env.NODE_PATH) + +const { MessageMedia } = require(whatsappWebGlobalPath) + +const logger = require('logger') + +const dotenv = require('dotenv') +dotenv.config({ path: '.env' }) + +const mime = require('mime') +const qrcode = require('qrcode-terminal') +const omnihit = require('./funcs/omnihit.js') +const { allowedNodeEnvironmentFlags } = require('process') + +const checkInternetConnection = require('./helpers/checkIntenet') + +const mongo = require('./funcs/mongoConn') + +const db = mongo.db(process.env.DB_MONGO_NAME) + +let auxCampaignMessage = '' + + + +require("./funcs/tools.js")() +let scheduler_messages_outbound +let scheduler_monitor +let scheduler_monitor_cell +let scheduler_campaign_monitor +let scheduler_internet_conn + +let client +// const PORT = 80; +let sendSeen = false +let unreadMessaesProcess + +const { imageUpload } = require('./helpers/image-uploader') + +var QRCODE = "0" +var mobileuid +var destroy +var changeInternetConn = true + +let asking_qrcode = false + +const dbcc = require('./helpers/mysql_conn.js') + +const removeDir = require('./helpers/remove_dir') + + +// (async()=>{ +// backup_session(destroy) +// clearTimeout(destroy) +// }) +// console.log('PASSOU............') +// return + + +// Sleep +const sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function getRandomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min +} + +const app = express() + +app.use(express.static(path.join(__dirname, 'public'))) +//app.use(bodyparser.urlencoded({ extended: true })); +app.use(bodyparser.json()) + +const sessionName = process.env.MOBILEUID + + + +// console.log('DIRNAME: ', path.join(__dirname, '.wwebjs_auth', 'session-omnihit_sesssion')) + +//TEST DEL + +console.log('process.env.CLIENT_URL: ', process.env.CLIENT_URL) + +console.log('1') +// Connect to server +var io = require('socket.io-client') + + + + +// const socket = initIO('http://localhost:8024') + + +const socketIo = io(process.env.CLIENT_URL, { reconnect: true, maxHttpBufferSize: 1e8 }) + +socketIo.on('connect', async function () { + console.log('Made socket connection', socketIo.id) + + console.log('process.env.WHATSAPP_ID: ', process.env.WHATSAPP_ID) + + socketIo.emit('joinWhatsSession', process.env.WHATSAPP_ID) + + sendSeen = true + + if (mobileuid) { + + console.log('Socket conectado com o cliente: ', mobileuid) + + setTimeout(async () => { + + console.log('Entro no syncUnreadMessages ON CONNECTED SOCKET') + await syncUnreadMessages(client) + + client.sendPresenceAvailable() + + }, 5000) + } + +}) + +socketIo.on('message_from_server', function () { + console.log('message_from_server data: ') +}) + +// socketIo.on('disconnect', function () { +// console.log('disconnect'); +// }); + +// var count = 0 + +// // Send a message to the server 3 seconds after initial connection. +// setInterval(function () { +// // socketIo.emit('message_from_client', 'Sent an event from the client!'); + +// const data = { +// id: "64d7fa0ce984e1cb781baee0", +// sent: count +// } + +// socketIo.emit("campaign_message_sent", data) + +// count++ + +// }, 3000) + +socketIo.on('connect_error', async function (err) { + + console.log('connection errror', err) + + sendSeen = false + + if (mobileuid) { + client.sendPresenceUnavailable() + } +}) +// + +const wwebVersion = '2.2402.5'; + + +//NOVA OPÇÃO MD +client = new Client({ + authStrategy: new LocalAuth({ clientId: 'omnihit_sesssion' }), + puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'], executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable' }, + webVersionCache: { + type: 'remote', + remotePath: `https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/${wwebVersion}.html`, + }, +}) + +client.initialize() + +client.on("qr", async qr => { + + console.log("Session:", sessionName) + + // Generate and scan this code with your phone + QRCODE = qr + qrcode.generate(qr, { small: true }) + console.log('QR RECEIVED', qr) + + // omnihit.qrcode(process.env.MOBILEUID, process.env.MOBILENAME, qr); + // omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, "STARTUP"); + + asking_qrcode = true + + await new Promise((resolve, reject) => { + + dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ? where id = ?", [qr, 'qrcode', 0, process.env.WHATSAPP_ID], + + function (err, result) { + + if (err) { + console.log("ERROR: " + err) + reject(err) + } + else { + resolve(result) + } + }) + + }) + + let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' + + try { + + let response = await axios.post(url, { whatsappId: process.env.WHATSAPP_ID }) + + } catch (error) { + console.log('There was an error on POST THE DATA TO URL: ', url, '\n' + error) + } + +}) + +client.on("authenticated", async session => { + console.log(`Session: ${sessionName} AUTHENTICATED`) +}) + +client.on("auth_failure", async msg => { + console.log( + `Session: ${sessionName} AUTHENTICATION FAILURE! Reason: ${msg}` + ) + + // omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, "AUTHFAILURE"); + + //reject(new Error("Error starting whatsapp session.")); +}) + +client.on("ready", async () => { + console.log(`Session: ${sessionName} READY`) + + // console.log('>>>>>>>>>>>>>> ready client.ts MOBILE NUMBER: ', client.info["wid"]["user"]) + + mobileuid = client.info["wid"]["user"] + console.log(new Date().toISOString() + " >>> Mobile UID ::: " + mobileuid) + + + // logger.info(`Session: ${sessionName} READY`); + + + + const whatsapp = await new Promise((resolve, reject) => { + + dbcc.query("SELECT * from Whatsapps where id = ?", [process.env.WHATSAPP_ID], (err, result) => { + + if (err) { + reject(err) + } + else { + resolve(result) + } + }) + + }) + + + if (whatsapp[0]['name'].includes(client.info["wid"]["user"])) { + console.log('-----------------> THIS IS THE RIGHT NUMBER') + + } + else { + console.log('-----------------> THIS IS THE WRONG NUMBER') + let read_number = client.info["wid"]["user"] + + + let url = process.env.CLIENT_URL + '/whatsapp/connection/number' + + try { + + await client.logout() + + let response = await axios.post(url, { number: read_number }) + + } catch (error) { + console.log('There was an error on POST THE DATA TO URL: ', url, '\n' + error) + } + + return + } + + + if (process.env.OLD_MOBILEUID) { + const ticketSettingsId = await new Promise((resolve, reject) => { + + dbcc.query("select id from SettingTickets where number = ?", [process.env.OLD_MOBILEUID,], (err, result) => { + + if (err) { + reject(err) + } + else { + // resolve(result) + const idArray = result.map(row => row.id) + resolve(idArray) + } + }) + + }) + + + if (ticketSettingsId?.length > 0) { + await new Promise((resolve, reject) => { + const idsToUpdate = ticketSettingsId // Assuming ticketSettingsId is an array of IDs + + // Create placeholders for the IN clause based on the number of elements in idsToUpdate + const placeholders = Array(idsToUpdate.length).fill('?').join(',') + + dbcc.query( + `UPDATE SettingTickets SET number = ? WHERE id IN (${placeholders})`, + [client.info["wid"]["user"], ...idsToUpdate], // Spread the array to pass individual values + function (err, result) { + if (err) { + console.log("ERROR: " + err) + reject(err) + } else { + resolve(result) + } + } + ) + }) + } + } + + + + await new Promise((resolve, reject) => { + + dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ?, number = ? where id = ?", ["", 'CONNECTED', 0, client.info["wid"]["user"], process.env.WHATSAPP_ID], + + function (err, result) { + + if (err) { + console.log("ERROR: " + err) + reject(err) + } + else { + resolve(result) + } + + }) + + }) + + + let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' + + try { + + let response = await axios.post(url, { whatsappId: process.env.WHATSAPP_ID }) + + } catch (error) { + console.log('There was an error on POST THE DATA TO URL: ', url, '\n' + error) + } + + console.log('SEND SEEN: ', sendSeen) + + + await syncUnreadMessages(client) + + if (sendSeen) { + client.sendPresenceAvailable() + } + + // if(asking_qrcode){ + // // backup_session(destroy, 120000, true) + // } + + backup_session(destroy, 120000, false) + + asking_qrcode = false + + console.log('PASSOU............') +}) + + +async function read() { + let chats = await client.getState() + console.log(chats) +} + + + +client.on("message_create", async msg => { + + // if (msg.hasMedia && msg.fromMe) + // return + + if (msg.fromMe && msg.type === "chat" && auxCampaignMessage.trim() == msg?.body?.trim()) { + console.log('IGNORED MESSAGE SENT CAMPAIGN') + return + } + + await handleMessage(msg) + + +}) + +client.on("media_uploaded", async msg => { + + console.log('Entrou no midia upload') + + let msgContact = null + let media = null + + if (msg.fromMe) { + + msgContact = await client.getContactById(msg.to) + + } + else { + + msgContact = await msg.getContact() + + } + + const chat = await msg.getChat() + + msgContact.getProfilePicUrl = await msgContact.getProfilePicUrl() + + let quotedMsg = await msg.getQuotedMessage() + + if (msg.hasMedia) { + media = await msg.downloadMedia() + } + + let data = { + id: process.env.WHATSAPP_ID, + msg: msg, + msgContact: msgContact, + chat: chat, + quotedMsg: quotedMsg ? quotedMsg.id.id : null, + media: media + } + + socketIo.emit("media_uploaded", data) + +}) + +client.on("message_ack", async (msg, ack) => { + + const campaigSend = await db.collection('campaignsends').findOne({ whatsapp_msg_id: msg.id.id }) + + if (campaigSend && ack == 3) { + + const updateResult = await db.collection('campaignsends').updateOne({ _id: ObjectId(campaigSend._id) }, + { + $set: { + read: new Date(new Date() + "UTC"), + ack: ack + } + }) + + // console.log('RESULT ACK UPDATE: ', updateResult) + + const read = await db.collection('campaignsends').countDocuments({ campaignId: ObjectId(campaigSend.campaignId), ack: 3 }) + + const data = { + id: ObjectId(campaigSend.campaignId), + read: read + } + + console.log('DATA ACK READ MESSAGE CAMPAIGN: ', data) + + socketIo.emit("campaign_message_sent", data) + + return + } + else if (campaigSend && (ack == 2 || ack == 1)) { + return + } + + + let data = { + whatsappId: process.env.WHATSAPP_ID, + id: msg.id.id, + ack: ack + } + + socketIo.emit("message_ack", data) + +}) + +socketIo.on('send_message', async data => { + + console.log('#') + console.log('--------------> send_message from number: ', mobileuid) + console.log('--------------> send_message to number: ', data.msg.number) + console.log('--------------> send_message body: ', data.msg.body) + console.log('--------------> send_message quotedMessageId: ', data.msg.quotedMessageId) + console.log('--------------> send_message linkPreview: ', data.msg.linkPreview) + console.log('#') + const sentMessage = await client.sendMessage(data.msg.number, data.msg.body, { quotedMessageId: data.msg.quotedMessageId, linkPreview: data.msg.linkPreview }) + + // console.log('=====================> sentMessage: ', sentMessage) + +}) + + +socketIo.on('send_media', async data => { + + console.log('#') + console.log('--------------> send_message from number: ', mobileuid) + console.log('--------------> send_message to number: ', data.msg.number) + // console.log('--------------> send_message media: ', data.msg.media); + console.log('--------------> send_message sendAudioAsVoice: ', data.msg.sendAudioAsVoice) + console.log('--------------> data.msg.media.mimetype: ', data.msg.media.mimetype) + console.log('--------------> data.msg.media.filename: ', data.msg.media.filename) + console.log('#') + + let media = new MessageMedia(data.msg.media.mimetype, data.msg.media.data, data.msg.media.file) + + if (media && !media.filename) + media.filename = data.msg.media.filename + + const sentMessage = await client.sendMessage(data.msg.number, media, { sendAudioAsVoice: data.msg.sendAudioAsVoice }) + + // const fullFilename = process.cwd() + process.env.MEDIA_DOWNLOAD_IN + data.msg.media.filename; + // console.log('fullFIlename: ', fullFilename) + // fs.writeFileSync(fullFilename, data.msg.media.data, { encoding: 'base64' }); + +}) + + + +client.on("change_state", async newState => { + + let omnihit_url = process.env.CLIENT_URL + '/whatsapp/connection/monitor' + + // logger.info(`Monitor session: ${sessionName}, ${newState}`); + + console.log('>>>>>>>>>>>>>> change_state wbotMonitor.ts MOBILE NUMBER: ', client.info["wid"]["user"]) + + let data = { + action: 'change_state', + whatsappId: process.env.WHATSAPP_ID + } + + await whatsappMonitor(newState, omnihit_url, data) + +}) + +client.on("disconnected", async reason => { + + let omnihit_url = process.env.CLIENT_URL + '/whatsapp/connection/monitor' + + console.log('>>>>>>>>>>>>>> change_state wbotMonitor.ts MOBILE NUMBER: ', client.info["wid"]["user"]) + + let data = { + action: 'disconnected', + whatsappId: process.env.WHATSAPP_ID, + reason: reason + } + + + await removeDir(path.join(__dirname, '.wwebjs_auth', 'session-omnihit_sesssion')) + + setTimeout(() => { + process.exit() + }, 3000) + + await whatsappMonitor('OPENING', omnihit_url, data) + + +}) + +app.get('/', function (req, res) { return res.send('Express + TypeScript Server') }) + +app.post('/start', function (req, res) { + client.initialize() + res.send("OK") +}) + +app.post('/qr', function (req, res) { + res.send(QRCODE) +}) + + +app.post('/api/getWbotMessage', async (req, res) => { + + const { number, messageId, limit } = req.body + + console.log('number: ', number, ' | limit: ', limit) + + const wbotChat = await client.getChatById(number) + + const fetchWbotMessagesGradually = async () => { + + const chatMessages = await wbotChat.fetchMessages({ limit }) + + const msgFound = chatMessages.find(msg => msg.id.id === messageId) + + if (!msgFound && limit < 100) { + limit += 20 + return fetchWbotMessagesGradually() + } + + return msgFound + } + + try { + const msgFound = await fetchWbotMessagesGradually() + + if (!msgFound) { + res.status(404).json({ message: "Cannot found message within 100 last messages" }) + return + } + + res.status(200).json({ message: "ok", data: msgFound }) + + } catch (err) { + + console.log('ERR_FETCH_WAPP_MSG: ', err) + + res.status(404).json({ message: "ERR_FETCH_WAPP_MSG" }) + } + +}) + + +app.post('/api/disconnect', async (req, res) => { + + try { + + console.log('Restaring the session.........') + + await removeDir(path.join(__dirname, '.wwebjs_auth', 'session-omnihit_sesssion')) + + await client.logout() + + setTimeout(() => { + process.exit() + }, 3000) + + + } catch (error) { + console.log('There was an error on try disconnect the whatsapp: ', error) + } + + res.status(200).json({ message: "ok" }) + +}) + + +app.post('/api/DeleteWhatsAppMessage', async (req, res) => { + + const { number, messageId, limit } = req.body + + console.log('number: ', number, ' | messageId: ', messageId, ' | limit: ', limit) + + try { + + const messageToDelete = await getWbotMessage(messageId, number, limit) + + await messageToDelete.delete(true) + + res.status(200).json({ message: "ok", data: messageToDelete }) + return + + } catch (error) { + + console.log('There was an error on try delete the massage: ', error) + + res.status(500).json({ message: "There was an error on trying delete the message" }) + return + } + + +}) + + +app.post('/api/GetProfilePicUrl', async (req, res) => { + + const { number } = req.body + + console.log('THE NUMBER: ', number) + + const profilePicUrl = await client.getProfilePicUrl(`${number}@c.us`) + + res.status(200).json({ message: "ok", data: profilePicUrl }) +}) + + +app.post('/api/restore', async (req, res) => { + + await restore(client) + + res.status(200).json({ message: "ok" }) +}) + +app.post('/api/sendSeen', async (req, res) => { + + let stat + + const { number } = req.body + + try { + + stat = await client.getState() + + // await syncUnreadMessages(client) + + const wbotChat = await client.getChatById(number) + + wbotChat.sendSeen() + + // const chatMessages = await wbotChat.fetchMessages({ limit: 100 }); + + // console.log('=============> wbotChat: ', chatMessages) + + } catch (err) { + + let terr = err.message + + stat = (terr.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + + } + + res.status(200).json({ message: "ok" }) +}) + +app.get('/api/connection/status', async (req, res) => { + + let stat + + try { + + stat = await client.getState() + + } catch (err) { + + let terr = err.message + + stat = (terr.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + + } + + res.status(200).json({ message: "ok", data: stat }) + +}) + + +const syncUnreadMessages = async (wbot) => { + + console.log('ENTROU NO UNREAD MESSAGES +++++++++++++=') + + const chats = await wbot.getChats() + + /* eslint-disable no-restricted-syntax */ + /* eslint-disable no-await-in-loop */ + for (const chat of chats) { + + // console.log('chat: ', chat) + + if (chat.unreadCount > 0) { + + const unreadMessages = await chat.fetchMessages({ + limit: chat.unreadCount + }) + + for (const msg of unreadMessages) { + + // console.log('--BACKEND MSG: ', msg) + + if (!sendSeen) { + return + } + + await handleMessage(msg, wbot) + } + + console.log(':::::::::::::::::::::::::::::PASSOU') + + await chat.sendSeen() + } + + } +} + + +const getWbotMessage = async (messageId, number, limit,) => { + + const wbotChat = await client.getChatById(number) + + const fetchWbotMessagesGradually = async () => { + + const chatMessages = await wbotChat.fetchMessages({ limit }) + + const msgFound = chatMessages.find(msg => msg.id.id === messageId) + + if (!msgFound && limit < 100) { + limit += 20 + return fetchWbotMessagesGradually() + } + + return msgFound + } + + try { + const msgFound = await fetchWbotMessagesGradually() + + if (!msgFound) { + return null + } + + return msgFound + + } catch (err) { + + console.log('ERR_FETCH_WAPP_MSG: ', err) + } + + return null +} + + +async function whatsappMonitor(newState, omnihit_url, data) { + + const whatsapp = await whatsappUpdateStatus(newState) + + if (whatsapp && whatsapp.affectedRows) { + console.log('whatsapp status update affectedRows: ', whatsapp.affectedRows) + } + + // console.log(' whatsappwhatsappwhatsappwhatsapp: ', whatsapp) + + + try { + let response = await axios.post(omnihit_url, data) + } catch (error) { + console.log('There was an error on POST THE DATA TO URL: ', omnihit_url, '\n' + error) + } +} + +async function whatsappUpdateStatus(newState) { + return await new Promise((resolve, reject) => { + + dbcc.query("UPDATE Whatsapps SET status = ? where id = ?", [newState, process.env.WHATSAPP_ID], + function (err, result) { + if (err) { + console.log("ERROR: " + err) + reject(err) + } + else { + resolve(result) + } + }) + + }) +} + +async function handleMessage(msg) { + + console.log('Entrou no message_create') + + let msgContact = null + let media = null + + if (msg.fromMe) { + + msgContact = await client.getContactById(msg.to) + + } + else { + + console.log('################# RECEIVING MESSAGE FROM: ', msg.from, ' to ', msg.to) + + msgContact = await msg.getContact() + + } + + const chat = await msg.getChat() + + msgContact.getProfilePicUrl = await msgContact.getProfilePicUrl() + + let quotedMsg = await msg.getQuotedMessage() + + if (msg.hasMedia) { + media = await msg.downloadMedia() + } + + let data = { + id: process.env.WHATSAPP_ID, + msg: msg, + msgContact: msgContact, + chat: chat, + quotedMsg: quotedMsg ? quotedMsg.id.id : null, + media: media + } + + + socketIo.emit("message_create", data) + +} + +async function getlabels() { + var ret = await client.getContactById('551721379544-1625752306@g.us') + //createGroup('The books', ['551100000000@c.us']); + console.log("-- Chats --------------------------------") + console.log(ret) + return ret +} + +function base64_encode(file) { + // read binary data + var bitmap = fs.readFileSync(file) + // convert binary data to base64 encoded string + return new Buffer(bitmap).toString('base64') +} + +function getBase64(url) { + return axios + .get(url, { + responseType: 'arraybuffer' + }) + .then(response => Buffer.from(response.data, 'binary').toString('base64')) +} + +function downloadMedia(url) { + let base64 = axios.get(url, { response: "arraybuffer" }).toString("base64") + console.log("-- BASE64 -------------------------------") + console.log(base64) + return base64 +} + +async function validate(mobile, cb) { + + // let ret = await client.isRegisteredUser(mobile); + + let ret = await client.isRegisteredUser(`${mobile}@c.us`) + + // ///////////////////////////////////////////////////// 5571992888229 casaes + + let _validNumber = null + + console.log('******** mobile: ', mobile) + + try { + + _validNumber = (await client.getNumberId(`${mobile}@c.us`)).user + + } catch (err) { + console.log(`Error number: ${err}`) + } + + + console.log('_validNumber: ', _validNumber) + + //////////////////////////////////////////////////////////////////// + + cb({ isValid: ret, number: _validNumber }) + + // cb(ret) +} + +app.post('/api/validate', (req, res) => { + + console.log('ENTROU') + + let mobile = req.body['mobile'] + + console.log(new Date() + " >>> Validating Registration Number ::: " + mobile + " on WhatsApp ...") + + validate(mobile, function (e) { + + res.send(e) + + }) +}) + +app.post('/api/chat', (req, res) => { + let mobile = req.body['mobile'] + let message = req.body['message'] + console.log(new Date() + " >>> Send Message ::: " + mobile + " ...") + client.sendMessage(mobile, message) + res.send('OK') +}) + + +app.post('/group', function (req, res) { + //var ret = client.createGroup("Prueba", ["55@c.us"]); + var ret = getlabels() + res.send(ret) +}) + +app.post('/stop', function (req, res) { + client.destroy() + res.send("OK") +}) + + +app.post('/api/status', function (req, res) { + res.send("OK") +}) + +async function monitor() { + let _nextime = 0 + try { + clearInterval(scheduler_monitor) + let stat + + if (mobileuid != undefined) { + try { + stat = await client.getState() + } catch (err) { + let terr = err.message + stat = (terr.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + } + // omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, stat); + + _nextime = 30000 + } else { + _nextime = 10000 + } + + console.log(`WHATSAPP_ID: ${process.env.WHATSAPP_ID} | CLIENT MOBILEUID: ${mobileuid} | NAME: ${process.env.MOBILENAME} | ENV MOBILEUID: ${process.env.MOBILEUID} | STATUS: ${stat} | INTERNET: ${changeInternetConn}`) + + if (stat && stat === 'CONNECTED') { + + + + const result = await whatsappUpdateStatus('CONNECTED') + + if (result) + console.log(`Update status to CONNECTED WHATSAPP_ID: ${process.env.WHATSAPP_ID} => result.affectedRows: ${result.affectedRows}`) + + } + + } catch (error) { + //new Date(new Date() + 'UTC') + // console.log(new Date().toISOString() + " >>> ", error); + console.log(new Date(new Date() + 'UTC') + " >>> ", error) + } finally { + scheduler_monitor = setInterval(monitor, _nextime) + } +} + + +const sendCampaignMessage = async () => { + + try { + clearInterval(scheduler_campaign_monitor) + + + let internetConnection = await checkInternetConnection() + + if (!internetConnection) { + changeInternetConn = false + } else { + changeInternetConn = true + } + + // let stat + // try { + // stat = await client.getState() + // } catch (err) { + // stat = (err?.message?.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + // } + // console.log('===========> stat: ', stat) + + if (mobileuid != undefined && internetConnection) { + + auxCampaignMessage = '' + + const campaign = await db.collection('campaigns').findOne({ 'campaign.whatsapp_sender': process.env.MOBILEUID, 'app.status': 'running' }) + + if (!campaign) + return + + const campaigSend = await db.collection('campaignsends').findOne({ campaignId: ObjectId(campaign._id), ack: 1 }) + + if (!campaigSend) { + + const countCampaignSend = await db.collection('campaignsends').countDocuments() + + if (countCampaignSend && countCampaignSend > 0) { + + await db.collection('campaigns').updateOne({ _id: ObjectId(campaign._id) }, + { + $set: { + 'app.status': 'success' + } + }) + + const data = { + id: ObjectId(campaign._id), + status: 'success' + } + + socketIo.emit("campaign_message_sent", data) + + } + + return + } + + + if (campaign.campaign?.textToSeconds && + campaign.campaign?.message && + campaign.campaign.message.trim().length > 0) { + + let msgMs = Math.round((campaign.campaign.message.trim().length * 0.1) * 1000) + + console.log('AWAITING MESSAGE TEXT LENGTH TO MILISECONDS: ', msgMs) + + await sleep(msgMs) + + } + + if (campaign.campaign?.secondStart && campaign.campaign?.secondEnd) { + + let randomTime = getRandomNumber(campaign.campaign.secondStart, campaign.campaign.secondEnd) * 1000 + + console.log('AWAITING RANDOM TIME TO SEND CAMPAIGN MESSAGE: ', randomTime) + + await sleep(randomTime) + } + + + auxCampaignMessage = campaign.campaign.message + + const sentMessage = await client.sendMessage(`${campaigSend.number}@c.us`, campaign.campaign.message) + + if (sentMessage) { + + const updateResult = await db.collection('campaignsends').updateOne({ _id: ObjectId(campaigSend._id) }, + { + $set: { + whatsapp_msg_id: sentMessage.id.id, + sender: process.env.MOBILEUID, + delivered: new Date(new Date() + "UTC"), + ack: 2 + } + }) + + // console.log('RESULT: ', updateResult) + + const sent = await db.collection('campaignsends').countDocuments({ campaignId: ObjectId(campaign._id), $or: [{ ack: 2 }, { ack: 3 }] }) + + const data = { + id: ObjectId(campaign._id), + sent + } + + socketIo.emit("campaign_message_sent", data) + + } + + + } + + } catch (error) { + console.log('error on sendCampaignMessage: ', error) + } + finally { + scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000) + } +} + +async function monitorCell() { + + try { + clearInterval(scheduler_monitor_cell) + // let _contact_mobile = "5511954803572@c.us"; + let _message = new Date(new Date() + 'UTC') + + if (client.info && client.info["wid"]["user"]) { + client.sendMessage(`${process.env.MONITOR_NUMBER}@c.us`, _message) + } + + } catch (error) { + console.log(`Error on send monitor message to number ${process.env.MONITOR_NUMBER} from monitorCell function: ${error}`) + } finally { + scheduler_monitor_cell = setInterval(monitorCell, 1800000) + } +} + + +async function internetMonitor() { + try { + clearInterval(scheduler_internet_conn) + + if (!changeInternetConn) { + + console.log('INTERNET IS OFFLINE. THE PM2 WILL RESTORE THE PROCESS') + + process.exit() + } + + } catch (error) { + console.log(`Error on try kill the process from internetMonitor function`) + } finally { + scheduler_internet_conn = setInterval(internetMonitor, 60000) + } +} + + +function comercialBuss(until_hour) { + const _hour = new Date().getHours() + + console.log(' _hour: ', _hour) + // const _minute = new Date().getMinutes() + // const _second = new Date().getSeconds() + + if (_hour >= until_hour) { + console.log('Trying send message into comercial buss!') + return + } +} + + +scheduler_monitor = setInterval(monitor, 10000) + +// scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000) + +// scheduler_internet_conn = setInterval(internetMonitor, 60000) + +app.listen(process.env.PORT || 8003, function () { + console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003) +}) + + +process.on('uncaughtException', function (err) { + console.error(' ') + console.error('----- ' + (new Date).toUTCString() + ' ----------------------------------') + console.error('Erro uncaughtException: ', err.message) + console.error(err.stack) + console.error(' ') + return +}); + + + diff --git a/TEST_SERVER1/test/whats/funcs/dbcloud.js b/TEST_SERVER1/test/whats/funcs/dbcloud.js new file mode 100644 index 0000000..bc0b56e --- /dev/null +++ b/TEST_SERVER1/test/whats/funcs/dbcloud.js @@ -0,0 +1,141 @@ +const dotenv = require('dotenv'); +dotenv.config({ path: `${process.cwd()}/.env` }); +const path = require('path') + +const MongoClient = require( 'mongodb' ).MongoClient; + +const url = process.env.DB_URL; + +var _db; + +module.exports = { + + connectToServer: function( callback ) { + + MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) { + + _db = client.db(process.env.DB_NAME); + + return callback( err ); + + } ); + + }, + + getDb: function() { + + return _db; + + } + +}; + + +// // PRODUCTION CONNECTION +// const MongoClient = require( 'mongodb' ).MongoClient; + +// const url = "mongodb://admin:d1nf54012022prod*@172.31.187.8:27017"; + +// var _db; + +// module.exports = { + +// connectToServer: function( callback ) { + +// MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) { + +// _db = client.db('db_omnihit'); + +// return callback( err ); + +// } ); + +// }, + +// getDb: function() { + +// return _db; + +// } + +// }; + + +// LOCA CONNECTION +// const MongoClient = require( 'mongodb' ).MongoClient; + +// const url = 'mongodb://localhost:27017'; + +// var _db; + +// module.exports = { + +// connectToServer: function( callback ) { +// MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) { +// _db = client.db('db_omnihit'); +// return callback( err ); +// } ); +// }, + +// getDb: function() { +// return _db; +// } +// } + + + + + +/* + +const MongoClient = require( 'mongodb' ).MongoClient; + +const url = "mongodb://admin:d1nf54012022*@172.31.187.2:27017"; + +var _db; + +module.exports = { + + connectToServer: function( callback ) { + + MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) { + + _db = client.db('db_omnihit_todoo'); + + return callback( err ); + + } ); + + }, + + getDb: function() { + + return _db; + + } + +}; + +*/ + + + +// const MongoClient = require( 'mongodb' ).MongoClient; + +// const url = "mongodb://admin:d1nf5401@192.168.15.13/admin?retryWrites=true&w=majority"; + +// var _db; + +// module.exports = { + +// connectToServer: function( callback ) { +// MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) { +// _db = client.db('db_omnihit'); +// return callback( err ); +// } ); +// }, + +// getDb: function() { +// return _db; +// } +// }; diff --git a/TEST_SERVER1/test/whats/funcs/mongoConn.js b/TEST_SERVER1/test/whats/funcs/mongoConn.js new file mode 100644 index 0000000..8017bb6 --- /dev/null +++ b/TEST_SERVER1/test/whats/funcs/mongoConn.js @@ -0,0 +1,18 @@ +const { MongoClient } = require('mongodb') + +const uri = process.env.DB_MONGO_URL + +const mongo = new MongoClient(uri) + +async function run() { + try { + await mongo.connect() + console.log('Connectado ao mongo db') + } catch (err) { + console.log(err) + } +} + +run() + +module.exports = mongo \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/funcs/omnihit.js b/TEST_SERVER1/test/whats/funcs/omnihit.js new file mode 100644 index 0000000..60b656d --- /dev/null +++ b/TEST_SERVER1/test/whats/funcs/omnihit.js @@ -0,0 +1,85 @@ +const os = require('os'); +const dbcloud = require('./dbcloud.js'); +const dotenv = require('dotenv'); +const axios = require('axios').default; +dotenv.config({path: '../.env'}); + + +module.exports = { + + // qrcode: async function(mobileuid, mobilename, qr ) { + // payload = {} + // payload['mobileuid'] = mobileuid; + // payload['mobilename'] = mobilename; + // payload['qrcode'] = qr; + // await axios.post(process.env.URL_QRCODE, payload, { timeout: 2000, headers: { 'content-type': 'application/json' } }); + // console.log(new Date().toISOString() + " >>> SEND QR CODE ---------------------------------------"); + // return 200; + // }, + + monitor: async function(mobileuid, mobilename, stat) { + + + let _totalmem = parseInt(os.totalmem()); + let _freemem = parseInt(os.freemem()); + let _memory = 100 - (_freemem / _totalmem * 100); + + + payload = {} + payload['mobileuid'] = mobileuid; + payload['mobilename'] = mobilename; + payload['memory'] = _memory; + + let db = dbcloud.getDb(); + + let mco = await db.collection('tab_counts').find({ "_id": mobileuid }).limit(1).toArray(); + + if ( mco.length == 0 ) { + payload['_id'] = mobileuid; + payload['hour'] = 0; + payload['in'] = 0; + payload['out'] = 0; + payload['lastmessage'] = new Date(new Date() + 'UTC'); + + payload['in_today'] = 0; + payload['out_today'] = 0; + + await db.collection('tab_counts').insertOne(payload); + + }else{ + payload['hour'] = mco[0]['hour']; + payload['in'] = mco[0]['in']; + payload['out'] = mco[0]['out']; + payload['lastmessage'] = mco[0]['lastmessage'] + payload['in_today'] = mco[0]['in_today']; + payload['out_today'] = mco[0]['out_today']; + } + + payload['dt'] = new Date(new Date() + 'UTC'); + payload['status'] = stat; + + + console.log(new Date().toISOString() + " >>> SEND MONITOR ALARM ---------------------------------------"); + console.log(new Date().toISOString() + " >>> payload: ",payload); + + + let monitor = await db.collection('tab_monitor').find({ "_id": mobileuid }).limit(1).toArray(); + + if ( monitor.length == 0 ) { + + payload['_id'] = mobileuid + + await db.collection('tab_monitor').insertOne(payload); + + } + else{ + + await db.collection('tab_monitor').updateOne({ "_id": mobileuid }, { $set: payload }, true); + + } + + + + return 200; + } +} \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/funcs/tools.js b/TEST_SERVER1/test/whats/funcs/tools.js new file mode 100644 index 0000000..fc15a01 --- /dev/null +++ b/TEST_SERVER1/test/whats/funcs/tools.js @@ -0,0 +1,43 @@ +module.exports = function() { + + this.getTimestamp = function() { + var date = new Date(); + var year = date.getFullYear(); + var month = ("0"+(date.getMonth()+1)).substr(-2); + var day = ("0"+date.getDate()).substr(-2); + var hour = ("0"+date.getHours()).substr(-2); + var minutes = ("0"+date.getMinutes()).substr(-2); + var seconds = ("0"+date.getSeconds()).substr(-2); + return year+"-"+month+"-"+day+" "+hour+":"+minutes+":"+seconds; + }; + + this.log = function(desc, message) { + console.log(getTimestamp() + ' >> ' + desc + " :: "); + console.log(message); + }; + + this.getTime = function() { + var date = new Date(); + var hour = ("0"+date.getHours()).substr(-2); + var minutes = ("0"+date.getMinutes()).substr(-2); + var seconds = ("0"+date.getSeconds()).substr(-2); + return hour+":"+minutes+":"+seconds; + }; + + this.forceGC = function() { + if (global.gc) { + console.log(getTimestamp() + " >> Starting Garbage Collector..."); + global.gc(); + } else { + console.warn("Garbage Collector não habilitado! Execute seu programa com node --expose-gc app.js."); + } + }; + + this.isJSON = function(str) { + try { + return (JSON.parse(str) && !!str); + } catch (e) { + return false; + } + } +} diff --git a/TEST_SERVER1/test/whats/helpers/backup_session.js b/TEST_SERVER1/test/whats/helpers/backup_session.js new file mode 100644 index 0000000..a8e1e73 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/backup_session.js @@ -0,0 +1,39 @@ +const removeDir = require('./remove_dir'); +const copyFolder = require('./copyFolder'); +const path = require('path'); +const fs = require('fs'); + + +async function backup_session(destroy, save_session_after, save_first_read_only=false) { + + console.log('process.cwd(): ', process.cwd()) + + + const sessionBackupPath = path.join(process.cwd(), `session_backup`, `session-omnihit_sesssion`) + + if (fs.existsSync(sessionBackupPath) && save_first_read_only) return + + destroy = setTimeout(async () => { + + const sessionPath = path.join(process.cwd(), '.wwebjs_auth', 'session-omnihit_sesssion') + + if (fs.existsSync(path.join(process.cwd(), '.wwebjs_auth'))) { + + await removeDir(sessionBackupPath) + + // copy the good session for backup dir + + copyFolder(sessionPath, sessionBackupPath) + } + else { + console.log('Directory not found to copy backup_session: ', sessionPath) + } + + + }, save_session_after); + + return destroy + +} + +module.exports = backup_session; \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/helpers/checkIntenet.js b/TEST_SERVER1/test/whats/helpers/checkIntenet.js new file mode 100644 index 0000000..090a779 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/checkIntenet.js @@ -0,0 +1,41 @@ +const http = require('http') + +const checkInternetConnection = async () => { + const options = { + hostname: 'www.google.com', + port: 80, + method: 'HEAD' + } + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + if (res.statusCode === 200) { + resolve(true) + } else { + resolve(false) + } + req.abort() + }) + + req.on('error', (err) => { + resolve(false) + }) + + req.end() + }) +}; + +// (async () => { +// try { +// const isConnected = await checkInternetConnection() +// if (isConnected) { +// console.log('Internet connection is available.') +// } else { +// console.log('Internet connection is not available.') +// } +// } catch (error) { +// console.error('Error checking internet connection:', error) +// } +// })() + +module.exports = checkInternetConnection diff --git a/TEST_SERVER1/test/whats/helpers/copyFolder.js b/TEST_SERVER1/test/whats/helpers/copyFolder.js new file mode 100644 index 0000000..5af7bd8 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/copyFolder.js @@ -0,0 +1,17 @@ +const fsPromises = require("fs/promises"); +const fs = require('fs-extra') + +// Delete a directory and its children +function copyFolder(sourcePath, destPath) { + + fs.copySync(sourcePath, destPath, { overwrite: true }, (err) => { + if (err) { + console.error(err); + } else { + console.log("Copy dir success!"); + } + }); + +} + +module.exports = copyFolder; \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/helpers/image-uploader.js b/TEST_SERVER1/test/whats/helpers/image-uploader.js new file mode 100644 index 0000000..c327847 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/image-uploader.js @@ -0,0 +1,36 @@ +const multer = require('multer') +const path = require('path') + + + +//Destination to store the images +const imageStorage = multer.diskStorage({ + destination: function(req, file, cb){ + + // let folder = "" + // if(req.baseUrl.includes("users")){ + // folder = "users" + // }else if(req.baseUrl.includes("pets")){ + // folder = "pets" + // } + + + cb(null, path.join(process.cwd(),'medias', 'out')) + }, + filename: function(req, file, cb) { + cb(null, Date.now() + path.extname(file.originalname)) + } +}) + +const imageUpload = multer({ + storage: imageStorage, + // fileFilter(req, file, cb){ + // if (!file.originalname.match(/\.(jpg|jpeg|png)$/)){ + // return cb(new Error('Por favor, envie apenas jpg ou png!')) + // } + // cb(undefined, true) + // } + +}) + +module.exports = { imageUpload } \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/helpers/mysql_conn.js b/TEST_SERVER1/test/whats/helpers/mysql_conn.js new file mode 100644 index 0000000..0a21ad2 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/mysql_conn.js @@ -0,0 +1,129 @@ +const dotenv = require('dotenv'); +dotenv.config({ path: `${process.cwd()}/.env` }); +const path = require('path') + + +// Ubicua Plataform - MYSQL Module +try{ + var mysql_npm = require('mysql'); +}catch(err){ + console.log("Cannot find `mysql` module. Is it installed ? Try `npm install mysql` or `npm install`."); +} + + +var db_config = { + host : process.env.DB_HOST, + user : process.env.DB_USER, + password : process.env.DB_PASS, + database : process.env.DB, + charset : 'utf8mb4_general_ci', + port : process.env.DB_PORT + }; + +//- +//- Connection configuration +//- +// var db_config = { +// host : 'localhost', +// user : 'whaticket', +// password : '9147teste', +// database : 'db_cdnwork', +// charset : 'utf8mb4_general_ci', +// port : '6603' +// }; + + + + +// var db_config = { +// host : '172.31.187.7', +// user : 'todoo', +// password : '7901228899', +// database : 'db_cdnwork', +// charset : 'utf8mb4_general_ci' +// }; + + + +//- +//- Create the connection variable +//- +var connection = mysql_npm.createPool(db_config); + + +//- +//- Establish a new connection +//- +connection.getConnection(function(err){ + if(err) { + // mysqlErrorHandling(connection, err); + console.log("\n\t *** Cannot establish a connection with the database. ***"); + + connection = reconnect(connection); + }else { + console.log("\n\t *** New connection established with the database. ***") + } +}); + + +//- +//- Reconnection function +//- +function reconnect(connection){ + console.log("\n New connection tentative..."); + + //- Create a new one + connection = mysql_npm.createPool(db_config); + + //- Try to reconnect + connection.getConnection(function(err){ + if(err) { + //- Try to connect every 2 seconds. + setTimeout(reconnect(connection), 2000); + }else { + console.log("\n\t *** New connection established with the database. ***") + return connection; + } + }); +} + + +//- +//- Error listener +//- +connection.on('error', function(err) { + + //- + //- The server close the connection. + //- + if(err.code === "PROTOCOL_CONNECTION_LOST"){ + console.log("/!\\ Cannot establish a connection with the database. /!\\ ("+err.code+")"); + return reconnect(connection); + } + + else if(err.code === "PROTOCOL_ENQUEUE_AFTER_QUIT"){ + console.log("/!\\ Cannot establish a connection with the database. /!\\ ("+err.code+")"); + return reconnect(connection); + } + + else if(err.code === "PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR"){ + console.log("/!\\ Cannot establish a connection with the database. /!\\ ("+err.code+")"); + return reconnect(connection); + } + + else if(err.code === "PROTOCOL_ENQUEUE_HANDSHAKE_TWICE"){ + console.log("/!\\ Cannot establish a connection with the database. /!\\ ("+err.code+")"); + } + + else{ + console.log("/!\\ Cannot establish a connection with the database. /!\\ ("+err.code+")"); + return reconnect(connection); + } + +}); + + +//- +//- Export +//- +module.exports = connection; diff --git a/TEST_SERVER1/test/whats/helpers/remove_dir.js b/TEST_SERVER1/test/whats/helpers/remove_dir.js new file mode 100644 index 0000000..8f48f22 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/remove_dir.js @@ -0,0 +1,28 @@ +const fsPromises = require("fs/promises"); +const fs = require('fs') + +// Delete a directory and its children +const removeDir = async (dirPath) => { + + if (fs.existsSync(dirPath)) { + + try { + await fsPromises.rm(dirPath, { recursive: true, force: true }); + console.log("Directory removed!"); + + return true + } + catch (err) { + console.log('An error occurred while removing the directory: ', err); + } + + } + else { + console.log('Directory not found to remove: ', dirPath) + } + + return false + +} + +module.exports = removeDir ; \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/helpers/restore.js b/TEST_SERVER1/test/whats/helpers/restore.js new file mode 100644 index 0000000..36f8075 --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/restore.js @@ -0,0 +1,39 @@ +const removeDir = require('./remove_dir'); +const copyFolder = require('./copyFolder'); +const path = require('path'); +const fs = require('fs'); + + +async function restore(client) { + + try { + await client.destroy() + } catch (error) { + console.error(`Error on try destroy client: ${error}`) + } + + const sessionBackupPath = path.join(process.cwd(), `session_backup`, `session-omnihit_sesssion`) + + const sessionPath = path.join(process.cwd(), '.wwebjs_auth', 'session-omnihit_sesssion') + + if (fs.existsSync(path.join(process.cwd(), `session_backup`, `session-omnihit_sesssion`))) { + + await removeDir(sessionPath) + + // copy the good session for backup dir + copyFolder(sessionBackupPath, sessionPath) + + } + else { + console.log('Directory not found to copy: ', sessionPath) + } + + + setTimeout(() => { + console.log('process.exit: kkkkkkkkkkkkkkkkkkkkk') + process.exit() + }, 5000) + +} + +module.exports = restore; \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/helpers/socket.js b/TEST_SERVER1/test/whats/helpers/socket.js new file mode 100644 index 0000000..8003fcb --- /dev/null +++ b/TEST_SERVER1/test/whats/helpers/socket.js @@ -0,0 +1,67 @@ + + +var io = require('socket.io-client'); + +var lst = [] + +const _clear_lst = () => { + + if (lst.length <= 199) return + + const chunk = Math.floor((lst.length / 2)) + + lst = lst.slice(chunk, chunk + lst.length); + +} + + +const multisessionIdControll = (msgId) => { + + _clear_lst() + + let index = lst.findIndex((x) => x.id == msgId) + + console.log('INDEX: ', index) + + if (index == -1) { + + lst.push({ id: msgId }) + + } + else { + console.log('IGNORED ID: ', msgId) + + return + } + + // console.log('LIST OF ID MESSAGE lst: ', lst) + + console.log('PASSOU.................................ID: ', msgId) + +} + + + + +const initIO = (url) => { + + const socket = io(url, { reconnect: true }); + + socket.on('connect', async () => { + console.log('Made socket connection2'); + }); + + socket.on('messageId', messageId => { + + console.log('-------> messageId: ', messageId); + + multisessionIdControll(messageId) + + console.log('socket lst: ', lst) + + }); + + return socket; +}; + +module.exports = { initIO, lst } \ No newline at end of file diff --git a/TEST_SERVER1/test/whats/medias/in/test/.gitkeep b/TEST_SERVER1/test/whats/medias/in/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TEST_SERVER1/test/whats/package-lock.json b/TEST_SERVER1/test/whats/package-lock.json new file mode 100644 index 0000000..f3082a6 --- /dev/null +++ b/TEST_SERVER1/test/whats/package-lock.json @@ -0,0 +1,3664 @@ +{ + "name": "omnihit", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "omnihit", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^0.21.4", + "body-parser": "^1.19.0", + "dotenv": "^16.0.0", + "express": "^4.17.1", + "form-data": "^4.0.0", + "fs-extra": "^11.1.0", + "logger": "^0.0.1", + "mime": "^2.4.5", + "mongodb": "^4.1.1", + "mongoose": "^7.4.3", + "multer": "^1.4.4", + "mysql": "^2.18.1", + "node-os-utils": "^1.3.5", + "qr-encode": "^0.3.0", + "qrcode-terminal": "^0.12.0", + "socket.io": "^4.5.4", + "socket.io-client": "^4.5.4" + }, + "devDependencies": { + "nodemon": "^2.0.20" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + }, + "node_modules/@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.2.tgz", + "integrity": "sha512-VeJKHShcu1b/ugl0QiujlVuBepab714X9nNyBdA1kfekuDGecxgpTA2Z6nYbagrWFeiIyzSWIOzju3lhj+RNyQ==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/busboy/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/busboy/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/dicer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz", + "integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/logger": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/logger/-/logger-0.0.1.tgz", + "integrity": "sha1-ywgXH4pvb2dLhJna31C+1L77csQ=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mongodb": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.5.0.tgz", + "integrity": "sha512-A2l8MjEpKojnhbCM0MK3+UOGUSGvTNNSv7AkP1fsT7tkambrkkqN/5F2y+PhzsV0Nbv58u04TETpkaSEdI2zKA==", + "dependencies": { + "bson": "^4.6.2", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.5.2", + "socks": "^2.6.2" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.3.tgz", + "integrity": "sha512-eok0lW6mZJHK2vVSWyJb9tUfPMUuRF3h7YC4pU2K2/YSZBlNDUwvKsHgftMOANbokP2Ry+4ylvzAdW4KjkRFjw==", + "dependencies": { + "bson": "^5.4.0", + "kareem": "2.5.1", + "mongodb": "5.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/bson": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", + "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", + "dependencies": { + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "@mongodb-js/zstd": "^1.1.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/mysql/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/mysql/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/mysql/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-os-utils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/node-os-utils/-/node-os-utils-1.3.6.tgz", + "integrity": "sha512-WympE9ELtdOzNak/rAuuIV5DwvX/PTJtN0LjyWeGyTTR2Kt0sY56ldLoGbVBnfM1dz46VeO3sHcNZI5BZ+EB+w==" + }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qr-encode": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/qr-encode/-/qr-encode-0.3.0.tgz", + "integrity": "sha1-DHg8KXtirLPAIh5fukEjNqG0uso=" + }, + "node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "node_modules/socket.io-client": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.4.tgz", + "integrity": "sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.3", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + } + }, + "dependencies": { + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "bson": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.2.tgz", + "integrity": "sha512-VeJKHShcu1b/ugl0QiujlVuBepab714X9nNyBdA1kfekuDGecxgpTA2Z6nYbagrWFeiIyzSWIOzju3lhj+RNyQ==", + "requires": { + "buffer": "^5.6.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + } + } + }, + "engine.io-client": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz", + "integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + } + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + }, + "raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==" + }, + "logger": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/logger/-/logger-0.0.1.tgz", + "integrity": "sha1-ywgXH4pvb2dLhJna31C+1L77csQ=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "mongodb": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.5.0.tgz", + "integrity": "sha512-A2l8MjEpKojnhbCM0MK3+UOGUSGvTNNSv7AkP1fsT7tkambrkkqN/5F2y+PhzsV0Nbv58u04TETpkaSEdI2zKA==", + "requires": { + "bson": "^4.6.2", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.5.2", + "saslprep": "^1.0.3", + "socks": "^2.6.2" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "mongoose": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.3.tgz", + "integrity": "sha512-eok0lW6mZJHK2vVSWyJb9tUfPMUuRF3h7YC4pU2K2/YSZBlNDUwvKsHgftMOANbokP2Ry+4ylvzAdW4KjkRFjw==", + "requires": { + "bson": "^5.4.0", + "kareem": "2.5.1", + "mongodb": "5.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "dependencies": { + "bson": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", + "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==" + }, + "mongodb": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", + "requires": { + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" + }, + "mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "requires": { + "debug": "4.x" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-os-utils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/node-os-utils/-/node-os-utils-1.3.6.tgz", + "integrity": "sha512-WympE9ELtdOzNak/rAuuIV5DwvX/PTJtN0LjyWeGyTTR2Kt0sY56ldLoGbVBnfM1dz46VeO3sHcNZI5BZ+EB+w==" + }, + "nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qr-encode": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/qr-encode/-/qr-encode-0.3.0.tgz", + "integrity": "sha1-DHg8KXtirLPAIh5fukEjNqG0uso=" + }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "socket.io-client": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.4.tgz", + "integrity": "sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.3", + "socket.io-parser": "~4.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/TEST_SERVER1/test/whats/package.json b/TEST_SERVER1/test/whats/package.json new file mode 100644 index 0000000..6f8bdd7 --- /dev/null +++ b/TEST_SERVER1/test/whats/package.json @@ -0,0 +1,34 @@ +{ + "name": "omnihit", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon ./app.js" + }, + "author": "Edson da Silva", + "license": "ISC", + "dependencies": { + "axios": "^0.21.4", + "body-parser": "^1.19.0", + "dotenv": "^16.0.0", + "express": "^4.17.1", + "form-data": "^4.0.0", + "fs-extra": "^11.1.0", + "logger": "^0.0.1", + "mime": "^2.4.5", + "mongodb": "^4.1.1", + "mongoose": "^7.4.3", + "multer": "^1.4.4", + "mysql": "^2.18.1", + "node-os-utils": "^1.3.5", + "qr-encode": "^0.3.0", + "qrcode-terminal": "^0.12.0", + "socket.io": "^4.5.4", + "socket.io-client": "^4.5.4" + }, + "devDependencies": { + "nodemon": "^2.0.20" + } +} diff --git a/TEST_SERVER1/whats/app.js b/TEST_SERVER1/whats/app.js index 62f316e..82416ed 100644 --- a/TEST_SERVER1/whats/app.js +++ b/TEST_SERVER1/whats/app.js @@ -183,11 +183,17 @@ socketIo.on('connect_error', async function (err) { }) // +const wwebVersion = '2.2402.5'; + //NOVA OPÇÃO MD client = new Client({ authStrategy: new LocalAuth({ clientId: 'omnihit_sesssion' }), puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'], executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable' }, + webVersionCache: { + type: 'remote', + remotePath: `https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/${wwebVersion}.html`, + }, }) client.initialize() @@ -879,11 +885,15 @@ async function handleMessage(msg) { if (msg.fromMe) { + console.log('MSG FROM ME') + msgContact = await client.getContactById(msg.to) } else { + console.log('MSG FROM CLIENT') + console.log('################# RECEIVING MESSAGE FROM: ', msg.from, ' to ', msg.to) msgContact = await msg.getContact() @@ -906,10 +916,10 @@ async function handleMessage(msg) { msgContact: msgContact, chat: chat, quotedMsg: quotedMsg ? quotedMsg.id.id : null, - media: media + media: media, + getContactById: msg.fromMe ? await client.getContactById(msg.to) : await client.getContactById(msg.from) } - socketIo.emit("message_create", data) } diff --git a/backend/.env_original b/backend/.env_original new file mode 100644 index 0000000..eac0b17 --- /dev/null +++ b/backend/.env_original @@ -0,0 +1,44 @@ +NODE_ENV= +BACKEND_URL=http://localhost +FRONTEND_URL=http://localhost:3000 +PROXY_PORT=8080 +PORT=8080 + +DB_DIALECT=mysql +DB_HOST=localhost +DB_USER=whaticket +DB_PASS=strongpassword +DB_NAME=whaticket + +# WHATSAPP OFFICIAL +VERIFY_TOKEN=HAPPY +TOKEN=EAAEPZBB2YqgwBOZBEAvPxYaO2nbPvuzU3ZBaZA6YF6tyWtjKom2yLxPxOm421njhbb1ZC2rOkyQyZCWpZBk9jXZCAaMLNY6SkNOrwPoRNaqO9Fbj31mZC8mxra08jIhBiziX7IZBFDWYbkcfw5cfLdTSys9ilfRlKsIZClOUlTiHnhSDkMvXY6rMFrvWswR2YVvJVH1qPvM7hGuuUqM +VERSION=v17.0 +URL_WHATSAPP_MEDIA=https://ccsm-api.omnihit.app.br/whatsapp/official/media +URL_WHATSAPP_API=https://graph.facebook.com + +JWT_SECRET=3123123213123 +JWT_REFRESH_SECRET=75756756756 + +SENTRY_DSN= + +CACHE= + +WHATS_NUMBER_VALIDATOR_URL=http://localhost:8021 + +TOKEN_REMOTE_TICKET_CREATION=emvd7UfskjstMC99mFqs2tEiNmn05PgsUVK06TZP9LfkyjxDrsKCxlVV5ApYM7hP +TOKEN_IAM_HORACIUS_EL=emvd7UfskjstMC99mFqs2tEiNmn05PgsUVK06TZP9LfkyjxDrsKCxlVV5ApYM7hP + +# omnihit da hit test bot +# APP_NAME=recrutamento_el +APP_NAME=test + +BACKEND_URL_RAW=http://localhost + +PUBLICKEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOvEh4Qc0yeJidiX3YpDdOB/XuDeyNRaypmSyW5FjjIxBFaMiUmNSZ2X2m2LqyyrHJxQgRqwjifHUQ+ivmNBm4YFNSr05iFB/kgi/1Jmbst6h1MnmuD1YFSRnEsmdUXzhhgcj5btyjRfw6L2rGwOnqzMzS54seE1aAy+rtN82DW8wfbYU/IO83MAJiocthCBOev5MDUq6hdkGPPZ/kdFOLcQe+wt/suhmF4KRfq77X4GgLM5nAOMj7N7cJ6b97nB47krfPOMJissNzPDZ879BKeQX4t8TwJGUFNOvLd3UW3xVBTBz8pSS36VlCXjbYm44za8eTuBLDYYbGkUNEFYxwIDAQAB + +REDIS_URI=redis://127.0.0.1:6379 + +URL_DASHBOARD_SESSIONS=http://localhost:6002 +TOKEN_DASHBOARD_SESSIONS=8168dd72adb7bab7e8f54f9d022468ab + diff --git a/backend/package.json b/backend/package.json index 1c8646e..1e5bd13 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "watch": "tsc -w", "start": "nodemon --expose-gc dist/server.js", "dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts", + "dev": "nodemon --exec npm run dev:server", "pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all", "test": "NODE_ENV=test jest", "posttest": "NODE_ENV=test sequelize db:migrate:undo:all" diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index af0ad0a..02843a1 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -20,11 +20,13 @@ import { } from "../helpers/ContactsCache"; import { off } from "process"; -import GetContactService from "../services/ContactServices/GetContactService" +import GetContactService from "../services/ContactServices/GetContactService"; +import ContactQueue from "../models/ContactQueues"; type IndexQuery = { searchParam: string; pageNumber: string; + userId?:string; }; type IndexGetContactQuery = { @@ -41,10 +43,11 @@ interface ContactData { number: string; email?: string; extraInfo?: ExtraInfo[]; + queueIds?: number[]; } export const index = async (req: Request, res: Response): Promise => { - let { searchParam, pageNumber } = req.query as IndexQuery; + let { searchParam, pageNumber, userId } = req.query as IndexQuery; console.log("PAGE NUMBER CONTACT: ", pageNumber); @@ -78,13 +81,12 @@ export const index = async (req: Request, res: Response): Promise => { error ); } - } - - console.log("QUERY CONTACTS FROM DATABASE SEARCH PARAM: ", searchParam); + } const { contacts, count, hasMore } = await ListContactsService({ searchParam, - pageNumber + pageNumber, + userId }); return res.json({ contacts, count, hasMore }); @@ -124,13 +126,13 @@ export const store = async (req: Request, res: Response): Promise => { newContact.number = addStartPhoneNumber(newContact.number); - const validNumber = await CheckIsValidContact(newContact.number); + const validNumber = await CheckIsValidContact(newContact.number); if (!validNumber) { throw new AppError("ERR_WAPP_CHECK_CONTACT"); } - const profilePicUrl = await GetProfilePicUrl(validNumber); + const profilePicUrl = await GetProfilePicUrl(validNumber); let name = newContact.name; let number = validNumber; @@ -142,7 +144,8 @@ export const store = async (req: Request, res: Response): Promise => { number, email, profilePicUrl: profilePicUrl, - extraInfo + extraInfo, + queueIds: newContact?.queueIds }); const io = getIO(); @@ -208,6 +211,8 @@ export const remove = async ( await DeleteContactService(contactId); + await ContactQueue.destroy({ where: { contactId } }); + const io = getIO(); io.emit("contact", { action: "delete", diff --git a/backend/src/controllers/IAMControllerEL.ts b/backend/src/controllers/IAMControllerEL.ts new file mode 100644 index 0000000..b61291c --- /dev/null +++ b/backend/src/controllers/IAMControllerEL.ts @@ -0,0 +1,492 @@ +import { Request, Response } from "express"; +import { getIO } from "../libs/socket"; +import { Op } from "sequelize"; +import CreateUserService from "../services/UserServices/CreateUserService"; +import UpdateUserService from "../services/UserServices/UpdateUserService"; +import DeleteUserService from "../services/UserServices/DeleteUserService"; +import { del, get, set } from "../helpers/RedisClient"; + +import { + startWhoIsOnlineMonitor, + stopWhoIsOnlineMonitor +} from "../helpers/WhoIsOnlineMonitor"; + +import User from "../models/User"; + +export const createUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, [ + "user_id", + "user_tax_id", + "user_first_name" + ]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "createUser")); + } + + const auxUser = await User.findOne({ where: { secondaryId: user_id } }); + + if (auxUser) { + return res + .status(400) + .json( + response("1", `The user ${user_id} already exist`, "0", "createUser") + ); + } + + const user = await CreateUserService({ + email: user_tax_id || user_email, + password: "12345", + name: user_first_name, + profile: "user", + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "createUser")); + } + + if (!user?.error) { + const _user = await User.findByPk(user.id); + _user?.update({ secondaryId: user_id }); + + const { id, name } = user; + await set(`user:${id}`, { id, name }); + + const io = getIO(); + io.emit("user", { + action: "create", + user + }); + + await startWhoIsOnlineMonitor(); + } + + return res + .status(200) + .json(response("1", `User ${user_id} created`, "1", "createUser")); +}; + +export const deleteUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "deleteUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + const user = await DeleteUserService(_user.id, true); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "deleteUser")); + } + + if (!user?.error) { + del(`user:${_user.id}`); + + const io = getIO(); + io.emit("user", { + action: "delete", + userId: _user.id + }); + + await stopWhoIsOnlineMonitor(); + + io.emit("onlineStatus", { + action: "delete", + userOnlineTime: _user.id + }); + + await startWhoIsOnlineMonitor(); + + return res + .status(200) + .json(response("1", `User ${user_id} deleted`, "1", "deleteUser")); + } + } + + return res + .status(500) + .json(response("0", "Internal server error", "0", "deleteUser")); +}; + +export const listAllUsers = async ( + req: Request, + res: Response +): Promise => { + const _users: any = await User.findAll({ + where: { + secondaryId: { + [Op.ne]: "" + } + }, + attributes: ["secondaryId", "name"] + }); + + if (_users) { + const user_list = _users.map((user: any) => { + const { secondaryId, name } = user; + return { user_id: secondaryId, full_name: name }; + }); + + return res + .status(200) + .json(response("1", "Success", user_list, "listAllUsers")); + } + + return res + .status(500) + .json(response("0", "Internal server error", [], "listAllUsers")); +}; + +export const checkUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + return res + .status(200) + .json(response("1", `User ${user_id} exist`, "1", "checkUser")); + } + + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUser")); +}; + +export const updateUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "updateUser")); + + const userData = { + email: user_tax_id || user_email, + name: user_first_name, + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "updateUser")); + } + + if (user) { + const { id, name } = user; + await set(`user:${id}`, { id, name }); + } + + const io = getIO(); + io.emit("user", { + action: "update", + user + }); + + return res + .status(200) + .json(response("1", `User ${user_id} updated`, "1", "updateUser")); +}; + +export const resetPassword = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_password }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_password"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "resetPassword")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) { + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "resetPassword")); + } + + const userData = { + password: user_password, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "resetPassword")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response("1", `User ${user_id} password updated`, "1", "resetPassword") + ); +}; + +export const linkUserAndUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "linkUserAndUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "linkUserAndUserRight" + ) + ); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json( + response("1", `User ${user_id} not exist`, "0", "linkUserAndUserRight") + ); + + const userData = { + profile: user_right_title || user_right_id, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "linkUserAndUserRight")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} associated with ${ + user_right_title || user_right_id + } profile`, + "1", + "linkUserAndUserRight" + ) + ); +}; + +export const checkUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "checkUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "checkUserRight" + ) + ); + } + + const _user: any = await User.findOne({ + where: { + secondaryId: user_id + } + }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUserRight")); + + if ( + (user_right_id && _user.profile != user_right_id) || + (user_right_title && _user.profile != user_right_title) + ) { + return res + .status(403) + .json( + response( + "1", + `User ${user_id} does not have this profile`, + "0", + "checkUserRight" + ) + ); + } + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} has ${user_right_title || user_right_id} profile`, + "1", + "checkUserRight" + ) + ); +}; + +async function logoutUser(userId: any) { + await stopWhoIsOnlineMonitor(); + + let onlineTime = { + userId: `${userId}`, + status: "logout..." + }; + + const io = getIO(); + io.emit("onlineStatus", { + action: "logout", + userOnlineTime: onlineTime + }); + + await startWhoIsOnlineMonitor(); +} + +function response(code: string, msg: string, obj: any, type: string) { + let payload = { return_code: code, return_msg: msg }; + + switch (type) { + case "createUser": + return { ...payload, user_created: obj }; + case "deleteUser": + return { ...payload, user_removed: obj }; + case "listAllUsers": + return { ...payload, user_list: obj }; + case "checkUser": + return { ...payload, user_exists: obj }; + case "updateUser": + return { ...payload, user_updated: obj }; + case "resetPassword": + return { ...payload, password_set: obj }; + case "linkUserAndUserRight": + return { ...payload, user_right_linked: obj }; + case "checkUserRight": + return { ...payload, user_right_exists: obj }; + default: + return payload; + } +} + +function invalidProperties(body: any, pros: any[]) { + for (const field of pros) { + console.log("body[field]: ", body[field], " field: ", field); + if (!body[field]) { + return `${field} is required`; + } + } + return false; +} diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index 2016bca..37259da 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI"; import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; import { getIO } from "../libs/socket"; @@ -21,6 +20,8 @@ import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOffici import Whatsapp from "../models/Whatsapp"; import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs"; import AppError from "../errors/AppError"; +import { get } from "../helpers/RedisClient"; +import createApiClientWhatsOfficial from "../helpers/WhatsappOfficialAPI"; type IndexQuery = { pageNumber: string; @@ -97,7 +98,8 @@ export const store = async (req: Request, res: Response): Promise => { } const name = params.find((p: any) => p?.template_name); - const { language }: any = params?.find((p: any) => p?.language) || 'pt_BR' + const { language }: any = + params?.find((p: any) => p?.language) || "pt_BR"; const { template_name } = name; @@ -121,7 +123,12 @@ export const store = async (req: Request, res: Response): Promise => { } } else { try { - const { wabaId }: any = await Whatsapp.findByPk(whatsappId); + const { wabaId, whatsappOfficialToken }: any = + await Whatsapp.findByPk(whatsappId); + + const whatsappOfficialAPI = createApiClientWhatsOfficial( + whatsappOfficialToken + ); const { data } = await whatsappOfficialAPI.get( `/${process.env.VERSION}/${wabaId}/message_templates?language=pt_BR` diff --git a/backend/src/controllers/PositionController.ts b/backend/src/controllers/PositionController.ts new file mode 100644 index 0000000..a913414 --- /dev/null +++ b/backend/src/controllers/PositionController.ts @@ -0,0 +1,113 @@ +import * as Yup from "yup"; +import { Request, Response } from "express"; +import { getIO } from "../libs/socket"; + +import AppError from "../errors/AppError"; +import ListPositionService from "../services/PositionService/ListPositionService"; +import ShowPositionService from "../services/PositionService/ShowPositionService"; +import CreatePositionService from "../services/PositionService/CreatePositionService"; +import UpdatePositionService from "../services/PositionService/UpdatePositionService"; +import DeletePositionService from "../services/PositionService/DeletePositionService"; + +type IndexQuery = { + searchParam: string; + pageNumber: string; +}; + +interface PositionData { + name: string; +} + +export const index = async (req: Request, res: Response): Promise => { + const { searchParam, pageNumber } = req.query as IndexQuery; + + const { positions, count, hasMore } = await ListPositionService({ + searchParam, + pageNumber + }); + + return res.json({ positions, count, hasMore }); +}; + +export const store = async (req: Request, res: Response): Promise => { + const newPosition: PositionData = req.body; + + const PositionSchema = Yup.object().shape({ + name: Yup.string().required() + }); + + try { + await PositionSchema.validate(newPosition); + } catch (err: any) { + throw new AppError(err.message); + } + + const position = await CreatePositionService({ + ...newPosition + }); + + const io = getIO(); + io.emit("position", { + action: "create", + position + }); + + return res.status(200).json(position); +}; + +export const show = async (req: Request, res: Response): Promise => { + const { positionId } = req.params; + + const position = await ShowPositionService(positionId); + + return res.status(200).json(position); +}; + +export const update = async ( + req: Request, + res: Response +): Promise => { + const positionData: PositionData = req.body; + + const schema = Yup.object().shape({ + name: Yup.string().required() + }); + + try { + await schema.validate(positionData); + } catch (err: any) { + throw new AppError(err.message); + } + + const { positionId } = req.params; + + const position = await UpdatePositionService({ + positionData, + positionId + }); + + const io = getIO(); + io.emit("position", { + action: "update", + position + }); + + return res.status(200).json(position); +}; + +export const remove = async ( + req: Request, + res: Response +): Promise => { + const { positionId } = req.params; + + await DeletePositionService(positionId); + + const io = getIO(); + io.emit("position", { + action: "delete", + positionId + }); + + return res.status(200).json({ message: "Position deleted" }); +}; diff --git a/backend/src/controllers/QueueController.ts b/backend/src/controllers/QueueController.ts index 68b9854..9ee3eaa 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -5,22 +5,83 @@ import DeleteQueueService from "../services/QueueService/DeleteQueueService"; import ListQueuesService from "../services/QueueService/ListQueuesService"; import ShowQueueService from "../services/QueueService/ShowQueueService"; import UpdateQueueService from "../services/QueueService/UpdateQueueService"; -import Queue from "../models/Queue" -import AppError from "../errors/AppError" -import { get, set } from "../helpers/RedisClient"; - - +import Queue from "../models/Queue"; +import AppError from "../errors/AppError"; +import { del, get, set } from "../helpers/RedisClient"; +import { Op } from "sequelize"; +import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService"; +import Whatsapp from "../models/Whatsapp"; +import QuickAnswerQueue from "../models/QuickAnswerQueue"; +import ContactQueue from "../models/ContactQueues"; export const index = async (req: Request, res: Response): Promise => { - const queues = await ListQueuesService(); + const queues = await ListQueuesService(); return res.status(200).json(queues); }; -export const store = async (req: Request, res: Response): Promise => { - const { name, color, greetingMessage } = req.body; +export const listQueues = async ( + req: Request, + res: Response +): Promise => { + const whatsapps = await Whatsapp.findAll({ + where: { + name: { [Op.ne]: "botqueue" }, + number: { [Op.ne]: "" }, + phoneNumberId: false + }, + attributes: ["number"], + include: [ + { + model: Queue, + as: "queues", + attributes: ["id", "name"] + } + ] + }); - const queue = await CreateQueueService({ name, color, greetingMessage }); + const whats = whatsapps + ?.filter((w: any) => w?.queues?.length > 0) + ?.map((w: any) => { + const { number, queues } = w; + return { + number, + queues: queues?.map((q: any) => { + const { id, name } = q; + return { id, name }; + }) + }; + }); + + let _queues: any = []; + + for (const w of whats) { + const { queues } = w; + + for (const q of queues) { + const { id: queueId, name } = q; + + const auxQueue = _queues.findIndex((q: any) => q.queueId == queueId); + + if (auxQueue == -1) { + _queues.push({ queueId, name }); + } + } + } + + return res.status(200).json(_queues); +}; + +export const store = async (req: Request, res: Response): Promise => { + const { name, color, greetingMessage, farewellMessage, cc } = req.body; + + const queue = await CreateQueueService({ + name, + color, + greetingMessage, + cc, + farewellMessage + }); const io = getIO(); io.emit("queue", { @@ -62,43 +123,43 @@ export const customization = async ( if (new_queues && new_queues.length > 0) { const db_queues: any = await Queue.findAll(); - for (const i in new_queues) { - let { queueName: name, color, greetingMessage } = new_queues[i]; + // for (const i in new_queues) { + // let { queueName: name, color, greetingMessage } = new_queues[i]; - name = name?.trim()?.replace(/\s+/g, " "); + // name = name?.trim()?.replace(/\s+/g, " "); - const update = db_queues.find( - (q: any) => q.name?.trim()?.replace(/\s+/g, " ") == name - ); + // const update = db_queues.find( + // (q: any) => q.name?.trim()?.replace(/\s+/g, " ") == name + // ); - if (update) { - const { id } = update; - // UPDATE - // const queue = await UpdateQueueService(id, { - // name, - // color, - // greetingMessage - // }); + // if (update) { + // const { id } = update; + // // UPDATE + // // const queue = await UpdateQueueService(id, { + // // name, + // // color, + // // greetingMessage + // // }); - // const io = getIO(); - // io.emit("queue", { - // action: "update", - // queue - // }); - } else { - // CREATE - // const queue = await CreateQueueService({ - // name, - // color, - // greetingMessage - // }); - // const io = getIO(); - // io.emit("queue", { - // action: "update", - // queue - // }); - } - } + // // const io = getIO(); + // // io.emit("queue", { + // // action: "update", + // // queue + // // }); + // } else { + // // CREATE + // // const queue = await CreateQueueService({ + // // name, + // // color, + // // greetingMessage + // // }); + // // const io = getIO(); + // // io.emit("queue", { + // // action: "update", + // // queue + // // }); + // } + // } let remove_queues = db_queues.filter( (q: any) => @@ -125,7 +186,7 @@ export const customization = async ( await set("ura", ura); - const _ura = await get("ura"); + const _ura = await get({ key: "ura", parse: true }); console.log("_URA: ", _ura); return res.status(200).json({ new_queues }); @@ -164,6 +225,11 @@ export const remove = async ( await DeleteQueueService(queueId); + await QuickAnswerQueue.destroy({ where: { queueId } }); + await ContactQueue.destroy({ where: { queueId } }); + + await del(`queue:${queueId}`); + const io = getIO(); io.emit("queue", { action: "delete", diff --git a/backend/src/controllers/QuickAnswerController.ts b/backend/src/controllers/QuickAnswerController.ts index 60e2702..87dfecd 100644 --- a/backend/src/controllers/QuickAnswerController.ts +++ b/backend/src/controllers/QuickAnswerController.ts @@ -9,23 +9,27 @@ import UpdateQuickAnswerService from "../services/QuickAnswerService/UpdateQuick import DeleteQuickAnswerService from "../services/QuickAnswerService/DeleteQuickAnswerService"; import AppError from "../errors/AppError"; +import QuickAnswerQueue from "../models/QuickAnswerQueue"; type IndexQuery = { searchParam: string; pageNumber: string; + userId?: string; }; interface QuickAnswerData { shortcut: string; message: string; + queueIds?: number[]; } export const index = async (req: Request, res: Response): Promise => { - const { searchParam, pageNumber } = req.query as IndexQuery; + const { searchParam, pageNumber, userId } = req.query as IndexQuery; const { quickAnswers, count, hasMore } = await ListQuickAnswerService({ searchParam, - pageNumber + pageNumber, + userId }); return res.json({ quickAnswers, count, hasMore }); @@ -41,7 +45,7 @@ export const store = async (req: Request, res: Response): Promise => { try { await QuickAnswerSchema.validate(newQuickAnswer); - } catch (err:any) { + } catch (err: any) { throw new AppError(err.message); } @@ -59,9 +63,9 @@ export const store = async (req: Request, res: Response): Promise => { }; export const show = async (req: Request, res: Response): Promise => { - const { quickAnswerId } = req.params; + const { quickAnswerId, userId } = req.params; - const quickAnswer = await ShowQuickAnswerService(quickAnswerId); + const quickAnswer = await ShowQuickAnswerService(quickAnswerId, userId); return res.status(200).json(quickAnswer); }; @@ -103,10 +107,12 @@ export const remove = async ( req: Request, res: Response ): Promise => { - const { quickAnswerId } = req.params; + const { quickAnswerId, queueId } = req.params; await DeleteQuickAnswerService(quickAnswerId); + await QuickAnswerQueue.destroy({ where: { quickAnswerId } }); + const io = getIO(); io.emit("quickAnswer", { action: "delete", diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index 211c9bc..c7e975b 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -19,6 +19,8 @@ import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUser import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser"; import { getIO } from "../libs/socket"; import { Json } from "sequelize/types/lib/utils"; +import ReportByNumberQueueService from "../services/ReportServices/ReportByNumberQueueService"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; type IndexQuery = { userId: string; @@ -28,6 +30,7 @@ type IndexQuery = { queueId: string; pageNumber: string; userQueues: []; + isRemote: string; }; type ReportOnQueue = { @@ -48,17 +51,6 @@ export const reportUserByDateStartDateEnd = async ( } const { - userId, - startDate, - endDate, - pageNumber, - userQueues, - createdOrUpdated, - queueId - } = req.query as IndexQuery; - - console.log( - "userId, startDate, endDate, pageNumber, userQueues, createdOrUpdated, queueId: ", userId, startDate, endDate, @@ -66,9 +58,7 @@ export const reportUserByDateStartDateEnd = async ( userQueues, createdOrUpdated, queueId - ); - - // return res.status(200).json({ tickets:[], count:0, hasMore:false, queues:[] }); + } = req.query as IndexQuery; const { tickets, count, hasMore } = await ShowTicketReport({ userId, @@ -76,7 +66,7 @@ export const reportUserByDateStartDateEnd = async ( endDate, pageNumber, createdOrUpdated, - queueId + queueId, }); const queues = await Queue.findAll({ attributes: ["id", "name"] }); @@ -95,11 +85,12 @@ export const reportUserService = async ( ) { throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery; + const { userId, startDate, endDate, userQueues} = req.query as IndexQuery; // let usersProfile = await ListUserParamiterService({ profile: 'user' }) let usersProfile = await ListUserParamiterService({ profiles: ["user", "supervisor"], + userQueues: userQueues ? userQueues : undefined, raw: true }); @@ -274,8 +265,6 @@ export const reportMessagesUserByDateStartDateEnd = async ( } data_query_messages[i].id = i + 1; - - console.log("data_query_messages: ", data_query_messages[i]); } return res.status(200).json(data_query_messages); @@ -302,3 +291,86 @@ export const reportOnQueue = async ( return res.status(200).json({ message: "ok" }); }; + +export const reportService = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + + const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery; + + console.log( + `startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}` + ); + + console.log("IS REMOTE: ", isRemote); + console.log("isRemote: ", isRemote && isRemote == "true" ? true : false); + + const reportService = await ReportByNumberQueueService({ + startDate, + endDate, + isRemote: isRemote && isRemote == "true" ? true : false + }); + + return res.status(200).json({ reportService }); +}; + +export const reportServiceByQueue = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + + const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery; + + const reportService = await ReportByNumberQueueService({ + startDate, + endDate, + queue: true, + isRemote: isRemote && isRemote == "true" ? true : false + }); + + return res.status(200).json({ reportService }); +}; + +export const reportTicksCountByStatusChatEnds = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + + const { startDate, endDate, userQueues } = req.query as IndexQuery; + + const dateToday = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + + const queueIds = userQueues ? userQueues.map(queue => parseInt(queue)) : []; + + const reportStatusChatEnd = await CountStatusChatEndService( + startDate || dateToday.fullDate, + endDate || dateToday.fullDate, + queueIds + ); + + return res.status(200).json({ reportStatusChatEnd }); +}; diff --git a/backend/src/controllers/SchedulingNotifyController.ts b/backend/src/controllers/SchedulingNotifyController.ts index 11cb315..d8e5fc7 100644 --- a/backend/src/controllers/SchedulingNotifyController.ts +++ b/backend/src/controllers/SchedulingNotifyController.ts @@ -4,9 +4,9 @@ import { getIO } from "../libs/socket"; import DeleteSchedulingNotifyService from "../services/SchedulingNotifyServices/DeleteSchedulingNotifyService"; import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService"; import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService"; -import ShowSchedulingNotifyService from "../services/SchedulingNotifyServices/ShowSchedulingNotifyService"; +import ShowSchedulingNotifyService from "../services/SchedulingNotifyServices/ShowSchedulingNotifyService"; import { deleteScheduleByTicketIdCache } from "../helpers/SchedulingNotifyCache"; - +import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService"; type IndexQuery = { contactNumber: string; @@ -14,58 +14,58 @@ type IndexQuery = { endDate: string; }; +export const reportScheduleNotifyByDateStartDateEnd = async ( + req: Request, + res: Response +): Promise => { + const { contactNumber, startDate, endDate } = req.query as IndexQuery; - -export const reportScheduleNotifyByDateStartDateEnd = async (req: Request, res: Response): Promise => { - - const { contactNumber, startDate, endDate } = req.query as IndexQuery - - const data_query = await ListSchedulingNotifyContactService(contactNumber, startDate, endDate); - - // console.group('DATA QUERY SCHEDULE:\n',data_query) - + const data_query = await ListSchedulingNotifyContactService( + contactNumber, + startDate, + endDate + ); + return res.status(200).json(data_query); - }; - -export const createOrUpdateScheduleNotify = async (req: Request, res: Response): Promise => { - +export const createOrUpdateScheduleNotify = async ( + req: Request, + res: Response +): Promise => { const scheduleData = req.body; + + const statusChatEnd = await ShowStatusChatEndService({ + name: scheduleData.statusChatEndName + }); + const schedulingNotifyCreate = await CreateSchedulingNotifyService({ + schedulingNotifyId: scheduleData.schedulingNotifyId, + ticketId: scheduleData.ticketId, + statusChatEndId: statusChatEnd.id, + schedulingDate: scheduleData.schedulingDate, + schedulingTime: scheduleData.schedulingTime, + message: scheduleData.message + }); - - const schedulingNotifyCreate = await CreateSchedulingNotifyService( - { - schedulingNotifyId: scheduleData.schedulingNotifyId, - ticketId: scheduleData.ticketId, - statusChatEndId: scheduleData.statusChatEndId, - schedulingDate: scheduleData.schedulingDate, - schedulingTime: scheduleData.schedulingTime, - message: scheduleData.message - } - ) - - // console.group(':::::::::::::::::: DATA schedulingNotifyCreate:\n',schedulingNotifyCreate) - // const io = getIO(); - // io.emit("schedulingNotify", {action: "update", schedulingNotifyCreate }); + // console.group(':::::::::::::::::: DATA schedulingNotifyCreate:\n',schedulingNotifyCreate) + // const io = getIO(); + // io.emit("schedulingNotify", {action: "update", schedulingNotifyCreate }); return res.status(200).json(schedulingNotifyCreate); - }; -export const remove = async (req: Request, res: Response): Promise => { - - - +export const remove = async ( + req: Request, + res: Response +): Promise => { const { scheduleId } = req.params; - let schedule: any = await ShowSchedulingNotifyService(scheduleId) + let schedule: any = await ShowSchedulingNotifyService(scheduleId); - await deleteScheduleByTicketIdCache(schedule.ticketId) + await deleteScheduleByTicketIdCache(schedule.ticketId); await DeleteSchedulingNotifyService(scheduleId); return res.status(200).send(); }; - diff --git a/backend/src/controllers/SettingController.ts b/backend/src/controllers/SettingController.ts index d131ceb..6451036 100644 --- a/backend/src/controllers/SettingController.ts +++ b/backend/src/controllers/SettingController.ts @@ -16,18 +16,16 @@ export const index = async (req: Request, res: Response): Promise => { // throw new AppError("ERR_NO_PERMISSION", 403); // } - const settings = await ListSettingsService(); + const settings = await ListSettingsService(); - // const config = await SettingTicket.findAll(); - - return res.status(200).json({ settings, }); + return res.status(200).json({ settings }); }; export const ticketSettings = async ( req: Request, res: Response ): Promise => { - const { number } = req.params; + const { number } = req.params; const config = await SettingTicket.findAll({ where: { number } }); @@ -40,6 +38,7 @@ export const updateTicketSettings = async ( ): Promise => { const { number, + saturdayBusinessTime, outBusinessHours, ticketExpiration, weekend, @@ -47,7 +46,7 @@ export const updateTicketSettings = async ( sunday, holiday } = req.body; - + if (!number) throw new AppError("No number selected", 400); if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { @@ -58,6 +57,14 @@ export const updateTicketSettings = async ( }); } + if (saturdayBusinessTime && Object.keys(saturdayBusinessTime).length > 0) { + await updateSettingTicket({ + ...saturdayBusinessTime, + key: "saturdayBusinessTime", + number + }); + } + if (ticketExpiration && Object.keys(ticketExpiration).length > 0) { await updateSettingTicket({ ...ticketExpiration, @@ -116,11 +123,12 @@ export const update = async ( throw new AppError("ERR_NO_PERMISSION", 403); } const { settingKey: key } = req.params; - const { value } = req.body; + const { value, obj } = req.body; const setting = await UpdateSettingService({ key, - value + value, + obj }); if (key && key == "whatsaAppCloudApi") { @@ -158,7 +166,7 @@ export const update = async ( } loadSettings(); - + const io = getIO(); io.emit("settings", { action: "update", diff --git a/backend/src/controllers/StatusChatEndController.ts b/backend/src/controllers/StatusChatEndController.ts index a7ee259..915d1fb 100644 --- a/backend/src/controllers/StatusChatEndController.ts +++ b/backend/src/controllers/StatusChatEndController.ts @@ -1,12 +1,131 @@ import { Request, Response } from "express"; import AppError from "../errors/AppError"; - -import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService"; +import * as Yup from "yup"; -export const show = async (req: Request, res: Response): Promise => { - - const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({ searchParam: "", pageNumber: "1" }); - - return res.status(200).json(statusChatEnd); +type IndexQuery = { + searchParam: string; + pageNumber: string; +}; + +interface StatusChatEndData { + name: string; + farewellMessage: string; + isDefault: boolean; +} + +import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService"; +import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService"; +import UpdateStatusChatEndService from "../services/StatusChatEndService/UpdateStatusChatEndService"; +import { getIO } from "../libs/socket"; +import StatusChatEnd from "../models/StatusChatEnd"; +import CreateStatusChatEndService from "../services/StatusChatEndService/CreateStatusChatEndService"; +import { del } from "../helpers/RedisClient"; + +// export const show = async (req: Request, res: Response): Promise => { + +// const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({ searchParam: "", pageNumber: "1" }); + +// return res.status(200).json(statusChatEnd); +// }; + +export const index = async (req: Request, res: Response): Promise => { + const { searchParam, pageNumber } = req.query as IndexQuery; + + const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({ + searchParam, + pageNumber + }); + + return res.json({ statusChatEnd, count, hasMore }); +}; + +export const show = async (req: Request, res: Response): Promise => { + const { statusChatEndId } = req.params; + + let statushCatEnd; + + const isNumber = (str: any) => !isNaN(str); + + if (isNumber(statusChatEndId)) + statushCatEnd = await ShowStatusChatEndService({ id: statusChatEndId }); + else statushCatEnd = await ShowStatusChatEndService({ isDefault: true }); + + return res.status(200).json(statushCatEnd); +}; + +export const store = async (req: Request, res: Response): Promise => { + const newStatusChatEnd: StatusChatEndData = req.body; + + const StatusChatEndSchema = Yup.object().shape({ + name: Yup.string().required() + }); + + try { + await StatusChatEndSchema.validate(newStatusChatEnd); + } catch (err: any) { + throw new AppError(err.message); + } + + const statusChatEnd = await CreateStatusChatEndService({ + ...newStatusChatEnd + }); + + const io = getIO(); + io.emit("statusChatEnd", { + action: "create", + statusChatEnd + }); + + return res.status(200).json(statusChatEnd); +}; + +export const update = async ( + req: Request, + res: Response +): Promise => { + const statusChatEndData: StatusChatEndData = req.body; + + const schema = Yup.object().shape({ + name: Yup.string() + }); + + try { + await schema.validate(statusChatEndData); + } catch (err: any) { + throw new AppError(err.message); + } + + const { statusChatEndId } = req.params; + + const statusChatEnd = await UpdateStatusChatEndService({ + statusChatEndData, + statusChatEndId + }); + + const io = getIO(); + io.emit("statusChatEnd", { + action: "update", + statusChatEnd + }); + + return res.status(200).json(statusChatEnd); +}; + +export const remove = async ( + req: Request, + res: Response +): Promise => { + const { statusChatEndId } = req.params; + + await StatusChatEnd.destroy({ where: { id: statusChatEndId } }); + + await del(`statusChatEnd:${statusChatEndId}`); + + const io = getIO(); + io.emit("statusChatEnd", { + action: "delete", + statusChatEndId + }); + + return res.status(200).json({ message: "Status chat deleted" }); }; - \ No newline at end of file diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 13773b3..c9d914c 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -22,7 +22,7 @@ import format from "date-fns/format"; import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; import { searchTicketCache, loadTicketsCache } from "../helpers/TicketCache"; -import { Op } from "sequelize"; +import { Op, where } from "sequelize"; type IndexQuery = { searchParam: string; @@ -75,6 +75,11 @@ import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; import CreateContactService from "../services/ContactServices/CreateContactService"; import { botSendMessage } from "../services/WbotServices/wbotMessageListener"; import WhatsappQueue from "../models/WhatsappQueue"; +import { del, get, set } from "../helpers/RedisClient"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; +import Queue from "../models/Queue"; +import StatusChatEnd from "../models/StatusChatEnd"; +import controllByNumber from "../helpers/controllByNumber"; export const index = async (req: Request, res: Response): Promise => { const { @@ -97,30 +102,42 @@ export const index = async (req: Request, res: Response): Promise => { queueIds = JSON.parse(queueIdsStringified); } - const { tickets, count, hasMore } = await ListTicketsService({ - searchParam, - pageNumber, - status, - date, - showAll, - userId, - queueIds, - withUnreadMessages, - unlimited, - searchParamContent - }); + const { tickets, count, hasMore, remoteTicketsControll } = + await ListTicketsService({ + searchParam, + pageNumber, + status, + date, + showAll, + userId, + queueIds, + withUnreadMessages, + unlimited, + searchParamContent + }); - return res.status(200).json({ tickets, count, hasMore }); + return res + .status(200) + .json({ tickets, count, hasMore, remoteTicketsControll }); }; export const remoteTicketCreation = async ( req: Request, res: Response ): Promise => { - const { contact_from, contact_to, msg, contact_name }: any = req.body; + let { queueId, contact_from, cc, contact_to, msg, contact_name }: any = + req.body; - const validate = ["contact_from", "contact_to", "msg"]; - const validateOnlyNumber = ["contact_from", "contact_to"]; + let whatsappId: any; + + if (!queueId && !contact_from && !cc) { + return res.status(400).json({ + error: `Property 'queueId' or 'contact_from' or 'cc' is required.` + }); + } + + const validate = ["contact_to", "msg"]; + const validateOnlyNumber = ["queueId", "contact_to", "contact_from"]; for (let prop of validate) { if (!req.body[prop]) @@ -137,109 +154,188 @@ export const remoteTicketCreation = async ( } } - const whatsapp = await Whatsapp.findOne({ - where: { number: contact_from, status: "CONNECTED" } - }); + if (queueId) { + const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED"); - if (whatsapp) { - const { id: whatsappId, number, status } = whatsapp; + if (!whatsapps || whatsapps?.length == 0) { + return res.status(500).json({ + msg: `queueId ${queueId} does not have a WhatsApp number associated with it or the number's session is disconnected.` + }); + } - const queue: any = await WhatsappQueue.findOne({ - where: { whatsappId }, - attributes: ["queueId"] + const { id } = whatsapps[0]; + + whatsappId = id; + } else if (contact_from) { + const whatsapp = await Whatsapp.findOne({ + where: { number: contact_from, status: "CONNECTED" } }); - const { queueId } = queue; - - // const validNumber = await CheckIsValidContact(contact_to, true); - const validNumber = contact_to; - - if (validNumber) { - let contact = await Contact.findOne({ where: { number: validNumber } }); - - if (!contact) { - // const profilePicUrl = await GetProfilePicUrl(validNumber); - - contact = await CreateContactService({ - name: contact_name ? contact_name : contact_to, - number: validNumber - // profilePicUrl - }); - - const io = getIO(); - io.emit("contact", { - action: "create", - contact - }); - } - - const { id: contactId } = contact; - - const botInfo = await BotIsOnQueue("botqueue"); - - let ticket = await Ticket.findOne({ - where: { - [Op.or]: [ - { contactId, status: "queueChoice" }, - { contactId, status: "open", userId: botInfo.userIdBot } - ] - } + if (!whatsapp) { + return res.status(404).json({ + msg: `Whatsapp number ${contact_from} not found or disconnected!` }); + } - if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { - if (ticket) { - await UpdateTicketService({ - ticketData: { status: "closed" }, - ticketId: ticket.id - }); - ticket = null; - } - } else { - if (ticket) { - await UpdateTicketService({ - ticketData: { status: "closed" }, - ticketId: ticket.id - }); - } - } + const { id } = whatsapp; - if (!ticket) { - ticket = await FindOrCreateTicketService( - contact, - whatsappId, - 0, - undefined, - queueId - ); - botSendMessage(ticket, msg); - } + const { queues } = await ShowWhatsAppService(id); + + if (!queues || queues.length == 0) { + return res.status(500).json({ + msg: `The WhatsApp number ${contact_from} is not associated with any queue! ` + }); + } + + queueId = queues[0].id; + whatsappId = id; + } else if (cc) { + const queue = await Queue.findOne({ where: { cc } }); + if (!queue) { + return res.status(404).json({ + msg: `Queue with cc ${cc} not found! ` + }); + } + + queueId = queue.id; + + const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED"); + + if (whatsapps.length === 0) { + return res.status(500).json({ + msg: `No WhatsApp found for this cc ${cc} or the WhatsApp number is disconnected! ` + }); + } + + whatsappId = whatsapps[0].id; + } + + // const validNumber = await CheckIsValidContact(contact_to, true); + const validNumber = contact_to; + + if (validNumber) { + let contact = await Contact.findOne({ where: { number: validNumber } }); + + if (!contact) { + // const profilePicUrl = await GetProfilePicUrl(validNumber); + + contact = await CreateContactService({ + name: contact_name ? contact_name : contact_to, + number: validNumber + // profilePicUrl + }); const io = getIO(); - io.to(ticket.status).emit("ticket", { - action: "update", - ticket + io.emit("contact", { + action: "create", + contact }); + } + const { id: contactId } = contact; + + const botInfo = await BotIsOnQueue("botqueue"); + + // ticket from queueChoice or bot + let ticket: any = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: "queueChoice" }, + { contactId, status: "open", userId: botInfo.userIdBot } + ] + } + }); + + if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + ticket = null; + } + } else { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + } + } + + ticket = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: "pending" }, + { contactId, status: "open" } + ] + } + }); + + if (ticket) { console.log( - `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success` + `THE CAMPAIGN TICKET WAS NOT CREATED BECAUSE THE TICKET IS PENDING OR OPEN` ); - return res.status(200).json({ msg: "success" }); + + return res.status(422).json({ + msg: `The campaign ticket was not created because the number ${contact_to} already has a ticket open or pending` + }); + } + + ticket = await FindOrCreateTicketService( + contact, + whatsappId, + 0, + undefined, + queueId, + true + ); + + // botSendMessage(ticket, `${msg}`); + + await ticket.update({ + lastMessage: msg + }); + + await set( + `remote:ticketId:${ticket.id}`, + JSON.stringify({ + id: ticket.id, + createdAt: ticket.createdAt, + updatedAt: ticket.updatedAt, + whatsappId: ticket.whatsappId, + status: ticket.status + }) + ); + + const io = getIO(); + io.to(ticket.status).emit("ticket", { + action: "update", + ticket + }); + + const obj = await controllByNumber(); + + if (obj?.tickets) { + io.emit("remoteTickesControll", { + action: "update", + tickets: obj.ticketIds + }); } console.log( - `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: The number ${contact_to} does not exist on WhatsApp` + `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success` ); - return res - .status(500) - .json({ msg: `The number ${contact_to} does not exist on WhatsApp` }); + + return res.status(200).json({ msg: "success" }); } console.log( - `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit` + `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: The number ${contact_to} does not exist on WhatsApp` ); - return res.status(500).json({ - msg: `Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit` - }); + return res + .status(500) + .json({ msg: `The number ${contact_to} does not exist on WhatsApp` }); }; export const store = async (req: Request, res: Response): Promise => { @@ -314,9 +410,8 @@ export const show = async (req: Request, res: Response): Promise => { export const count = async (req: Request, res: Response): Promise => { // type indexQ = { status: string; date?: string; }; - const { status, date } = req.query as IndexQuery; - - const ticketCount = await CountTicketService(status, date); + const { status, date, queueIds } = req.query as IndexQuery; + const ticketCount = await CountTicketService(status, date, queueIds); return res.status(200).json(ticketCount); }; @@ -339,34 +434,75 @@ export const update = async ( // lembrete const scheduleData = JSON.parse(schedulingNotifyData); - const statusChatEndName = await ShowStatusChatEndService( - scheduleData.statusChatEndId - ); + console.log("scheduleData: ", scheduleData); + + const statusChatEnd = await ShowStatusChatEndService({ + name: scheduleData.statusChatEndName + }); const { ticket } = await UpdateTicketService({ ticketData: { status: status, userId: userId, - statusChatEnd: statusChatEndName.name + statusChatEnd: statusChatEnd.name, + statusChatEndId: statusChatEnd.id }, ticketId }); - if (scheduleData.farewellMessage) { + let _farewellMessage; + + if (getSettingValue("farewellMessageByStatusChatEnd")?.value == "enabled") { + const statusChatEndData = await get({ + key: `statusChatEnd:${statusChatEnd.id}`, + parse: true + }); + + if ( + statusChatEndData && + statusChatEndData?.farewellMessage && + statusChatEndData?.farewellMessage?.trim()?.length > 0 + ) { + const { farewellMessage } = statusChatEndData; + + _farewellMessage = farewellMessage; + } + } else if (getSettingValue("farewellMessageByQueue")?.value == "enabled") { + const queueData = await get({ + key: `queue:${ticket.queueId}`, + parse: true + }); + + if ( + queueData && + queueData?.farewellMessage && + queueData?.farewellMessage?.trim()?.length > 0 + ) { + const { farewellMessage } = queueData; + + _farewellMessage = farewellMessage; + } + } else if (scheduleData.farewellMessage) { const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const { farewellMessage } = whatsapp; if (farewellMessage) { - await SendWhatsAppMessage({ body: farewellMessage, ticket }); + _farewellMessage = farewellMessage; } } - // lembrete // agendamento + if (_farewellMessage) { + await SendWhatsAppMessage({ body: `\u200e${_farewellMessage}`, ticket }); + } + + console.log("tatusChatEnd.name: ", statusChatEnd.name); + if ( - scheduleData.statusChatEndId === "2" || - scheduleData.statusChatEndId === "3" + statusChatEnd.name === "LEMBRETE" || + statusChatEnd.name === "AGENDAMENTO À CONFIRMAR" ) { + // lembrete // agendamento if ( isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime) ) { @@ -377,7 +513,7 @@ export const update = async ( const schedulingNotifyCreate = await CreateSchedulingNotifyService({ ticketId: scheduleData.ticketId, - statusChatEndId: scheduleData.statusChatEndId, + statusChatEndId: `${statusChatEnd.id}`, schedulingDate: scheduleData.schedulingDate, schedulingTime: scheduleData.schedulingTime, message: scheduleData.message @@ -405,7 +541,6 @@ export const update = async ( for (const w of whatsappsByqueue) { let whats = await ListWhatsAppsNumber(w.id); - console.log("-------> WHATS: ", JSON.stringify(whats, null, 6)); const ticket = await Ticket.findOne({ where: { [Op.and]: [ @@ -490,6 +625,26 @@ export const update = async ( await setMessageAsRead(ticket); } + if (ticketData.status == "open" || ticketData.status == "pending") { + let ticketRemote = await get({ + key: `remote:ticketId:${ticketId}` + }); + + if (ticketRemote) { + ticketRemote = JSON.parse(ticketRemote); + + ticketRemote = { + ...ticketRemote, + ...{ + status: ticketData.status == "open" ? "open" : "pending", + updatedAt: ticket.updatedAt + } + }; + + set(`remote:ticketId:${ticketId}`, JSON.stringify(ticketRemote)); + } + } + if (ticketData.userId) { const dateToday = splitDateTime( new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) @@ -560,5 +715,7 @@ export const remove = async ( ticketId: +ticketId }); + await del(`remote:ticketId:${ticketId}`); + return res.status(200).json({ message: "ticket deleted" }); }; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 013eeb6..c435c8e 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -100,7 +100,7 @@ export const index = async (req: Request, res: Response): Promise => { // }; export const all = async (req: Request, res: Response): Promise => { - let { userId, profile }: any = req.query as IndexQuery; + let { userId, profile, transferToOtherQueues }: any = req.query as IndexQuery; console.log( "userId: ", @@ -111,7 +111,7 @@ export const all = async (req: Request, res: Response): Promise => { getSettingValue("queueTransferByWhatsappScope")?.value ); - if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { + if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled" && !transferToOtherQueues) { if (!userId) return res.json({ users: [], queues: [] }); const obj = await ListUserByWhatsappQueuesService( @@ -138,8 +138,15 @@ export const all = async (req: Request, res: Response): Promise => { }; export const store = async (req: Request, res: Response): Promise => { - const { email, password, name, profile, positionCompany, queueIds } = - req.body; + const { + email, + password, + name, + profile, + positionId, + queueIds, + transferToOtherQueues + } = req.body; console.log("===========> req.url: ", req.url); @@ -161,10 +168,11 @@ export const store = async (req: Request, res: Response): Promise => { const user = await CreateUserService({ email, password, - name, - positionCompany, + name, + positionId, profile, - queueIds + queueIds, + transferToOtherQueues }); if (user) { diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 3588ea0..1a82021 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -35,8 +35,7 @@ import ShowUserService from "../services/UserServices/ShowUserService"; import fs from "fs"; import receiveWhatsAppMediaOfficialAPI from "../helpers/ReceiveWhatsAppMediaOfficialAPI"; - -import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI"; + import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo"; import { getSettingValue } from "../helpers/WhaticketSettings"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; @@ -230,7 +229,10 @@ export const weebhook = async ( req.body.entry[0].changes[0].value.metadata.display_phone_number; let type = message.type; - const contact_to_exist = await get("whatsapp:*", `${contact_to}`); + const contact_to_exist = await get({ + key: "whatsapp:*", + value: `${contact_to}` + }); if (contact_to_exist == null) { console.log( @@ -285,7 +287,8 @@ export const weebhook = async ( let filename = await receiveWhatsAppMediaOfficialAPI( mediaId, - whatsapp.phoneNumberId + whatsapp.phoneNumberId, + whatsapp.whatsappOfficialToken ); if (!filename) throw new AppError("There was an error"); @@ -374,9 +377,11 @@ export const store = async (req: Request, res: Response): Promise => { } else if (!isOfficial) { phoneNumberId = ""; wabaId = ""; - number = ""; + //number = ""; + } + if(!number){ + number = getNumberFromName(name) } - let invalidPhoneName = validatePhoneName(name); if (invalidPhoneName) { @@ -408,7 +413,15 @@ export const store = async (req: Request, res: Response): Promise => { client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` }); } else { - await set(`whatsapp:${whatsapp.id}`, `${number}`); + await set( + `whatsapp:${whatsapp.id}`, + JSON.stringify({ + number: whatsapp?.number, + id: whatsapp?.id, + greetingMessage: whatsapp?.greetingMessage, + phoneNumberId: whatsapp?.phoneNumberId + }) + ); } const io = getIO(); @@ -468,9 +481,11 @@ export const update = async ( } else if (!isOfficial) { whatsappData.phoneNumberId = ""; whatsappData.wabaId = ""; - whatsappData.number = ""; + //whatsappData.number = ""; + } + if(!whatsappData?.number){ + whatsappData.number = getNumberFromName(whatsappData.name) } - const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({ whatsappData, whatsappId @@ -484,7 +499,15 @@ export const update = async ( client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` }); } else { - await set(`whatsapp:${whatsapp.id}`, `${number}`); + await set( + `whatsapp:${whatsapp.id}`, + JSON.stringify({ + number: whatsapp?.number, + id: whatsapp?.id, + greetingMessage: whatsapp?.greetingMessage, + phoneNumberId: whatsapp?.phoneNumberId + }) + ); } const io = getIO(); diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts index f5bf45d..cef07fa 100644 --- a/backend/src/database/index.ts +++ b/backend/src/database/index.ts @@ -13,8 +13,11 @@ import QuickAnswer from "../models/QuickAnswer"; import SchedulingNotify from "../models/SchedulingNotify"; import StatusChatEnd from "../models/StatusChatEnd"; -import UserOnlineTime from "../models/UserOnlineTime"; +import UserOnlineTime from "../models/UserOnlineTime"; import SettingTicket from "../models/SettingTicket"; +import QuickAnswerQueue from "../models/QuickAnswerQueue"; +import Position from "../models/Position" +import ContactQueue from "../models/ContactQueues" // eslint-disable-next-line const dbConfig = require("../config/database"); // import dbConfig from "../config/database"; @@ -33,11 +36,13 @@ const models = [ WhatsappQueue, UserQueue, QuickAnswer, - + QuickAnswerQueue, SchedulingNotify, StatusChatEnd, UserOnlineTime, - SettingTicket + SettingTicket, + Position, + ContactQueue ]; sequelize.addModels(models); diff --git a/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts b/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts new file mode 100644 index 0000000..39f1158 --- /dev/null +++ b/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Messages", "fromAgent", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Messages", "fromAgent"); + } +}; diff --git a/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts new file mode 100644 index 0000000..a2c590e --- /dev/null +++ b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "secondaryId", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "secondaryId"); + } +}; diff --git a/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts new file mode 100644 index 0000000..9043cea --- /dev/null +++ b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts @@ -0,0 +1,17 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "statusChatEndId", { + type: DataTypes.INTEGER, + references: { model: "StatusChatEnds", key: "id" }, + onUpdate: "CASCADE", + onDelete: "SET NULL", + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "statusChatEndId"); + } +}; diff --git a/backend/src/database/migrations/20240403202639-add-cc-to-Queues.ts b/backend/src/database/migrations/20240403202639-add-cc-to-Queues.ts new file mode 100644 index 0000000..60cf4ff --- /dev/null +++ b/backend/src/database/migrations/20240403202639-add-cc-to-Queues.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Queues", "cc", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Queues", "cc"); + } +}; diff --git a/backend/src/database/migrations/20240404204835-associate-quick-answer-queue.ts b/backend/src/database/migrations/20240404204835-associate-quick-answer-queue.ts new file mode 100644 index 0000000..274989a --- /dev/null +++ b/backend/src/database/migrations/20240404204835-associate-quick-answer-queue.ts @@ -0,0 +1,28 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("QuickAnswerQueues", { + quickAnswerId: { + type: DataTypes.INTEGER, + primaryKey: true + }, + queueId: { + type: DataTypes.INTEGER, + primaryKey: true + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("QuickAnswerQueues"); + } +}; diff --git a/backend/src/database/migrations/20240409124838-add-farewellMessage-to-queue.ts b/backend/src/database/migrations/20240409124838-add-farewellMessage-to-queue.ts new file mode 100644 index 0000000..cff0b32 --- /dev/null +++ b/backend/src/database/migrations/20240409124838-add-farewellMessage-to-queue.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Queues", "farewellMessage", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Queues", "farewellMessage"); + } +}; diff --git a/backend/src/database/migrations/20240409173900-add-farewellMessage-to-statusChatEnd.ts b/backend/src/database/migrations/20240409173900-add-farewellMessage-to-statusChatEnd.ts new file mode 100644 index 0000000..ec72a04 --- /dev/null +++ b/backend/src/database/migrations/20240409173900-add-farewellMessage-to-statusChatEnd.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("StatusChatEnds", "farewellMessage", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("StatusChatEnds", "farewellMessage"); + } +}; diff --git a/backend/src/database/migrations/20240410142126-add-isDefault-column-to-statusChatEnd.ts b/backend/src/database/migrations/20240410142126-add-isDefault-column-to-statusChatEnd.ts new file mode 100644 index 0000000..5c099b7 --- /dev/null +++ b/backend/src/database/migrations/20240410142126-add-isDefault-column-to-statusChatEnd.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("StatusChatEnds", "isDefault", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("StatusChatEnds", "isDefault"); + } +}; diff --git a/backend/src/database/migrations/20240411115322-create-position.ts b/backend/src/database/migrations/20240411115322-create-position.ts new file mode 100644 index 0000000..90ad357 --- /dev/null +++ b/backend/src/database/migrations/20240411115322-create-position.ts @@ -0,0 +1,30 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("Positions", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + name: { + type: DataTypes.TEXT, + allowNull: false + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("Positions"); + } +}; diff --git a/backend/src/database/migrations/20240411122250-add-positionId-to-users.ts b/backend/src/database/migrations/20240411122250-add-positionId-to-users.ts new file mode 100644 index 0000000..abafd2c --- /dev/null +++ b/backend/src/database/migrations/20240411122250-add-positionId-to-users.ts @@ -0,0 +1,19 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "positionId", { + type: DataTypes.INTEGER, + references: { model: "Positions", key: "id" }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "positionId"); + } +}; + + + \ No newline at end of file diff --git a/backend/src/database/migrations/20240411193930-associate-contact-queue.ts b/backend/src/database/migrations/20240411193930-associate-contact-queue.ts new file mode 100644 index 0000000..b757449 --- /dev/null +++ b/backend/src/database/migrations/20240411193930-associate-contact-queue.ts @@ -0,0 +1,28 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("ContactQueues", { + contactId: { + type: DataTypes.INTEGER, + primaryKey: true + }, + queueId: { + type: DataTypes.INTEGER, + primaryKey: true + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("ContactQueues"); + } +}; diff --git a/backend/src/database/migrations/20240414173559-add-column-isRemote-to-tickets.ts b/backend/src/database/migrations/20240414173559-add-column-isRemote-to-tickets.ts new file mode 100644 index 0000000..5cbac7e --- /dev/null +++ b/backend/src/database/migrations/20240414173559-add-column-isRemote-to-tickets.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "isRemote", { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "isRemote"); + } +}; diff --git a/backend/src/database/migrations/20240420115601-add-column-remoteDone-to-tickets.ts b/backend/src/database/migrations/20240420115601-add-column-remoteDone-to-tickets.ts new file mode 100644 index 0000000..ad14fff --- /dev/null +++ b/backend/src/database/migrations/20240420115601-add-column-remoteDone-to-tickets.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "remoteDone", { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "remoteDone"); + } +}; diff --git a/backend/src/database/migrations/20240423193637-add-column-obj-to-settings.ts b/backend/src/database/migrations/20240423193637-add-column-obj-to-settings.ts new file mode 100644 index 0000000..b9734cf --- /dev/null +++ b/backend/src/database/migrations/20240423193637-add-column-obj-to-settings.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Settings", "obj", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Settings", "obj"); + } +}; diff --git a/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts b/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts new file mode 100644 index 0000000..8a653a8 --- /dev/null +++ b/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "transferToOtherQueues", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "transferToOtherQueues"); + } +}; \ No newline at end of file diff --git a/backend/src/database/migrations/20240503175233-remove-column-positionCompany-from-users.ts b/backend/src/database/migrations/20240503175233-remove-column-positionCompany-from-users.ts new file mode 100644 index 0000000..38e711b --- /dev/null +++ b/backend/src/database/migrations/20240503175233-remove-column-positionCompany-from-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "positionCompany"); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "positionCompany", { + type: DataTypes.STRING, + allowNull: true + }); + } +}; diff --git a/backend/src/database/migrations/20240507184134-add-identifier-to-users.ts b/backend/src/database/migrations/20240507184134-add-identifier-to-users.ts new file mode 100644 index 0000000..7a783dc --- /dev/null +++ b/backend/src/database/migrations/20240507184134-add-identifier-to-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "identifier", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "identifier"); + } +}; \ No newline at end of file diff --git a/backend/src/database/migrations/20240610183159-add-column-whatsappOfficialToken-to-whatsapp.ts b/backend/src/database/migrations/20240610183159-add-column-whatsappOfficialToken-to-whatsapp.ts new file mode 100644 index 0000000..bf5a724 --- /dev/null +++ b/backend/src/database/migrations/20240610183159-add-column-whatsappOfficialToken-to-whatsapp.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Whatsapps", "whatsappOfficialToken", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Whatsapps", "whatsappOfficialToken"); + } +}; diff --git a/backend/src/database/seeders/20240201174343-has-campaign.ts.js b/backend/src/database/seeders/20240201174343-has-campaign.ts.js deleted file mode 100644 index 989f222..0000000 --- a/backend/src/database/seeders/20240201174343-has-campaign.ts.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - /* - Add altering commands here. - Return a promise to correctly handle asynchronicity. - - Example: - return queryInterface.bulkInsert('People', [{ - name: 'John Doe', - isBetaMember: false - }], {}); - */ - }, - - down: (queryInterface, Sequelize) => { - /* - Add reverting commands here. - Return a promise to correctly handle asynchronicity. - - Example: - return queryInterface.bulkDelete('People', null, {}); - */ - } -}; diff --git a/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts b/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts new file mode 100644 index 0000000..60c411d --- /dev/null +++ b/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts @@ -0,0 +1,25 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "SettingTickets", + [ + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "saturdayBusinessTime", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}); + } +}; diff --git a/backend/src/database/seeds/20240404150015-add-enable-quick-answer-by-queue-settings.ts b/backend/src/database/seeds/20240404150015-add-enable-quick-answer-by-queue-settings.ts new file mode 100644 index 0000000..0bd3ec8 --- /dev/null +++ b/backend/src/database/seeds/20240404150015-add-enable-quick-answer-by-queue-settings.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "quickAnswerByQueue", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240409144933-add-enable-farewellMessage-by-queue-settings.ts b/backend/src/database/seeds/20240409144933-add-enable-farewellMessage-by-queue-settings.ts new file mode 100644 index 0000000..278a218 --- /dev/null +++ b/backend/src/database/seeds/20240409144933-add-enable-farewellMessage-by-queue-settings.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "farewellMessageByQueue", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240409204650-add-enable-farewellMessage-by-statusChatEnd-settings.ts b/backend/src/database/seeds/20240409204650-add-enable-farewellMessage-by-statusChatEnd-settings.ts new file mode 100644 index 0000000..cdb3126 --- /dev/null +++ b/backend/src/database/seeds/20240409204650-add-enable-farewellMessage-by-statusChatEnd-settings.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "farewellMessageByStatusChatEnd", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240412151450-add-enable-contacts-by-queue.ts b/backend/src/database/seeds/20240412151450-add-enable-contacts-by-queue.ts new file mode 100644 index 0000000..3a0f7cc --- /dev/null +++ b/backend/src/database/seeds/20240412151450-add-enable-contacts-by-queue.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize" + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "contactByqueues", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ) + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}) + } +} diff --git a/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts b/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts new file mode 100644 index 0000000..511de98 --- /dev/null +++ b/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "blockAudioVideoMedia", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts b/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts new file mode 100644 index 0000000..8bb6828 --- /dev/null +++ b/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "waitingTimeTickets", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts b/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts new file mode 100644 index 0000000..00c4069 --- /dev/null +++ b/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "notificationTransferQueue", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/database/seeds/20240423173230-add-enable-remote-tickets-send-controll.ts b/backend/src/database/seeds/20240423173230-add-enable-remote-tickets-send-controll.ts new file mode 100644 index 0000000..36d3a18 --- /dev/null +++ b/backend/src/database/seeds/20240423173230-add-enable-remote-tickets-send-controll.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "remoteTicketSendControll", + value: "true", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/errors/AppError.ts b/backend/src/errors/AppError.ts index a8b1209..eed88bd 100644 --- a/backend/src/errors/AppError.ts +++ b/backend/src/errors/AppError.ts @@ -1,5 +1,5 @@ class AppError { - public readonly message: string; + public message: string; public readonly statusCode: number; diff --git a/backend/src/helpers/AutoRemoteTickets.ts b/backend/src/helpers/AutoRemoteTickets.ts new file mode 100644 index 0000000..8d3d1e4 --- /dev/null +++ b/backend/src/helpers/AutoRemoteTickets.ts @@ -0,0 +1,137 @@ +import { intervalToDuration } from "date-fns"; +import { del, get, set } from "./RedisClient"; +import { getIO } from "../libs/socket"; +import controllByNumber from "./controllByNumber"; +import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; + +let timer: any; + +const AutoRemoteTickets = async () => { + try { + let obj: any = await controllByNumber(); + + if (!obj?.tickets) return; + + // console.log("remote tickets: ", obj?.tickets); + + for (const ticket of obj.tickets) { + if (!ticket.includes("open") || ticket.includes("messageDateTime")) + continue; + + const regex = /"updatedAt":"([^"]+)"/; + let match = ticket.match(regex); + + const updatedAt = match ? match[1] : null; + + console.log("updatedAt: ", updatedAt); + + let timeDiff: any = intervalToDuration({ + start: new Date(updatedAt), + end: new Date() + }); + + console.log("----------> idle time timeDiff: ", timeDiff); + + if (timeDiff.minutes > 2) { + match = ticket.match(/"id":(\d+)/); + let ticketId = match ? match[1] : null; + console.log("ticketId: ", ticketId); + + ticketId = JSON.parse(ticketId); + + let ticketRemote = await get({ + key: `remote:ticketId:${ticketId}` + }); + + if (ticketRemote) { + ticketRemote = JSON.parse(ticketRemote); + + ticketRemote = { + ...ticketRemote, + ...{ status: "pending" } + }; + + set(`remote:ticketId:${ticketId}`, JSON.stringify(ticketRemote)); + + await UpdateTicketService({ + ticketData: { + status: "pending", + userId: null + }, + ticketId + }); + + const io = getIO(); + io.emit("remoteTickesControllIdleOpen", { + action: "update", + ticketId + }); + } + } + } + + for (const ticket of obj.tickets) { + if (!ticket.includes("messageDateTime")) continue; + + let match = ticket.match(/"messageDateTime":("[^"]*")/); + let messageDateTime = match ? match[1] : null; + console.log("messageDateTime: ", messageDateTime); + + match = ticket.match(/"whatsappId":(\d+)/); + let whatsappId = match ? match[1] : null; + console.log("whatsappId: ", whatsappId); + + const whatsapp = await get({ + key: `whatsapp:${whatsappId}` + }); + + match = whatsapp.match(/"number":"(\d+)"/); + let number = match ? match[1] : null; + console.log("number: ", number); + + match = ticket.match(/"id":(\d+)/); + let ticketId = match ? match[1] : null; + console.log("ticketId: ", ticketId); + + number = JSON.parse(number); + ticketId = JSON.parse(ticketId); + + let timeDiff: any = intervalToDuration({ + start: new Date(JSON.parse(messageDateTime)), + end: new Date() + }); + + console.log("______timeDiff: ", timeDiff); + + if (timeDiff.seconds > 50) { + del(`remote:ticketId:${ticketId}`); + + obj = await controllByNumber(); + + const io = getIO(); + io.emit("remoteTickesControll", { + action: "update", + tickets: obj.ticketIds + }); + } + } + } catch (error) { + console.log("There was an error on auto remote tickets service: ", error); + } +}; + +const schedule = async () => { + try { + clearInterval(timer); + + await AutoRemoteTickets(); + } catch (error) { + console.log("error on schedule: ", error); + } finally { + timer = setInterval(schedule, 5000); + } +}; + +timer = setInterval(schedule, 5000); + +export default schedule; diff --git a/backend/src/helpers/BotIsOnQueue.ts b/backend/src/helpers/BotIsOnQueue.ts index 5955fb7..c534bb4 100644 --- a/backend/src/helpers/BotIsOnQueue.ts +++ b/backend/src/helpers/BotIsOnQueue.ts @@ -4,9 +4,8 @@ const fs = require("fs"); import ListUsersService from "../services/UserServices/ListUsersService"; import { get } from "./RedisClient"; -const _botIsOnQueue = async (botName: string) => { - - const botInfo = await get("botInfo"); +const _botIsOnQueue = async (botName: string) => { + const botInfo = await get({ key: "botInfo", parse: true }); if ( botInfo && @@ -19,8 +18,8 @@ const _botIsOnQueue = async (botName: string) => { botQueueId: botInfo.queueId, isOnQueue: botInfo.botIsOnQueue }; - } - return { userIdBot: null, botQueueId: null, isOnQueue: false }; + } + return { userIdBot: null, botQueueId: null, isOnQueue: false }; }; export default _botIsOnQueue; diff --git a/backend/src/helpers/CheckContactOpenTickets.ts b/backend/src/helpers/CheckContactOpenTickets.ts index 303f4a7..0b4a52e 100644 --- a/backend/src/helpers/CheckContactOpenTickets.ts +++ b/backend/src/helpers/CheckContactOpenTickets.ts @@ -1,6 +1,7 @@ import { Op } from "sequelize"; import AppError from "../errors/AppError"; import Ticket from "../models/Ticket"; +import User from "../models/User"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import { getSettingValue } from "./WhaticketSettings"; import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsAppsForQueueService"; @@ -38,8 +39,13 @@ const CheckContactOpenTickets = async ( if (ticket) { if (handle) return true; - - throw new AppError("ERR_OTHER_OPEN_TICKET"); + const userName = await User.findOne({ + where:{ id: ticket.userId } + }); + const error = new AppError("ERR_OTHER_OPEN_TICKET"); + error.message = `Erro: já existe um ticket criado com esse contato. Responsável: ${userName? userName.name.toUpperCase() : 'Aguardando'}`; + + throw error; } diff --git a/backend/src/helpers/GetDefaultWhatsApp.ts b/backend/src/helpers/GetDefaultWhatsApp.ts index c19757a..6adc34d 100644 --- a/backend/src/helpers/GetDefaultWhatsApp.ts +++ b/backend/src/helpers/GetDefaultWhatsApp.ts @@ -25,7 +25,7 @@ const GetDefaultWhatsApp = async ({ where: { isDefault: true } }); - if (!defaultWhatsapp && !ignoreNoWhatsappFound) { + if (!defaultWhatsapp) { if (userId) { let whatsapps = await wbotByUserQueue({ userId, queueId }); @@ -53,13 +53,13 @@ const GetDefaultWhatsApp = async ({ where: { status: "CONNECTED" } }); } - } else { + } else { defaultWhatsapp = await Whatsapp.findOne({ where: { status: "CONNECTED", isOfficial: false } }); } - } - + } + if (!defaultWhatsapp && !ignoreNoWhatsappFound) { throw new AppError("ERR_NO_DEF_WAPP_FOUND"); } diff --git a/backend/src/helpers/MonthsAgo.ts b/backend/src/helpers/MonthsAgo.ts new file mode 100644 index 0000000..51203d8 --- /dev/null +++ b/backend/src/helpers/MonthsAgo.ts @@ -0,0 +1,23 @@ +const monthsAgo = (months: number) => { + const { subMonths, startOfMonth, format } = require("date-fns"); + + // Get the current date + const currentDate = new Date(); + + // Subtract 4 months from the current date + const monthsAgo = subMonths(currentDate, months); + + // Get the start of the month for the current date and four months ago + // const startOfCurrentMonth = startOfMonth(currentDate); + const startMonthsAgo = startOfMonth(monthsAgo); + + // Format the dates in YYYY-MM-DD format + // const formattedCurrentMonth = format(startOfCurrentMonth, "yyyy-MM-dd"); + const formattedMonthsAgo = format(startMonthsAgo, "yyyy-MM-dd"); + + // console.log("Current Month:", formattedCurrentMonth); + // console.log("Months Ago:", formattedMonthsAgo); + return formattedMonthsAgo; +}; + +export default monthsAgo; diff --git a/backend/src/helpers/QuickAnswearByqueueFiltered.ts b/backend/src/helpers/QuickAnswearByqueueFiltered.ts new file mode 100644 index 0000000..5ac0961 --- /dev/null +++ b/backend/src/helpers/QuickAnswearByqueueFiltered.ts @@ -0,0 +1,29 @@ +import QuickAnswer from "../models/QuickAnswer"; + +export default function quickAnswearByQueueFiltered( + queueIds: any[], + quickAnswers: QuickAnswer[] +) { + let auxQuickAnswear = []; + let repet: any[] = []; + const userQueues = queueIds.map((uq: any) => uq.queueId); + + for (const quickAnswer of quickAnswers) { + const { queues, id } = quickAnswer; + + if (queues.length == 0) { + auxQuickAnswear.push(quickAnswer); + continue; + } + + for (const q of queues) { + if (userQueues.includes(q.id)) { + if (repet.includes(id)) continue; + repet.push(id); + + auxQuickAnswear.push(quickAnswer); + } + } + } + return auxQuickAnswear; +} diff --git a/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts b/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts index fec1aa9..5cf3fc3 100644 --- a/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts +++ b/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts @@ -10,7 +10,6 @@ import { import { writeFile } from "fs"; -import whatsappOfficialAPI from "./WhatsappOfficialAPI"; import path, { join } from "path"; import { promisify } from "util"; @@ -18,14 +17,20 @@ import mime from "mime"; import fs from "fs"; import { response } from "express"; +import createApiClientWhatsOfficial from "./WhatsappOfficialAPI"; const writeFileAsync = promisify(writeFile); async function receiveWhatsAppMediaOfficialAPI( mediaId: string, - phoneNumberId: string + phoneNumberId: string, + whatsappOfficialToken: string ) { try { + const whatsappOfficialAPI = createApiClientWhatsOfficial( + whatsappOfficialToken + ); + const { data } = await whatsappOfficialAPI.get( `/${process.env.VERSION}/${mediaId}?phone_number_id=${phoneNumberId}` ); @@ -33,7 +38,7 @@ async function receiveWhatsAppMediaOfficialAPI( if (data && data?.url) { const config: any = { headers: { - Authorization: `Bearer ${process.env.TOKEN}` + Authorization: `Bearer ${whatsappOfficialToken}` }, responseType: "arraybuffer" }; @@ -44,7 +49,7 @@ async function receiveWhatsAppMediaOfficialAPI( const ext = response.headers["content-type"].split("/")[1]; - const filename_ext = `${filename}.${ext}`; + const filename_ext = `${filename}.${ext}`; const destPath = path.join( __dirname, diff --git a/backend/src/helpers/RedisClient.ts b/backend/src/helpers/RedisClient.ts index e218e85..2520588 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -6,6 +6,13 @@ type WhatsappData = { contactId: string; identifier: string; value?: string; + history?: string; +}; + +type getData = { + key: string; + value?: string; + parse?: boolean; }; export async function set(key: string, value: string | object) { @@ -15,21 +22,40 @@ export async function set(key: string, value: string | object) { } } -export async function get(key: string, value?: string) { +export async function getSimple(key: string) { + const value: any = await redis.get(key); + return value; +} + +export async function get({ key, value, parse }: getData) { if (key.includes("*")) { const keys = await redis.keys(key); - - // If there are keys, delete them if (keys.length > 0) { - for (const key of keys) { - const val = await redis.get(key); - if (value == val) return value; + if (value) { + for (const key of keys) { + const val = await redis.get(key); + if (val.includes(value)) { + if (parse) return JSON.parse(val); + return val; + } + } + } else { + let res: any[] = []; + for (const key of keys) { + const val = await redis.get(key); + if (parse) res.push(JSON.parse(val)); + res.push(val); + } + return res; } } return null; } else { const value: any = await redis.get(key); - return JSON.parse(value); + + if (parse) return JSON.parse(value); + + return value; } } @@ -44,7 +70,7 @@ export async function clearAllKeys(...keys: string[]) { // If there are keys, delete them if (del_keys.length > 0) { - console.log("del_keys: ", del_keys); + // console.log("del_keys: ", del_keys); await redis.del(...del_keys); } } @@ -83,7 +109,8 @@ export async function createObject({ whatsappId, contactId, identifier, - value + value, + history = "" }: WhatsappData) { const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`; const result = await redis.hmset( @@ -95,7 +122,9 @@ export async function createObject({ "identifier", identifier, "value", - value + value, + "history", + history ); await redis.expire(key, 300); @@ -123,7 +152,8 @@ export async function findObject( "whatsappId", "contactId", "identifier", - "value" + "value", + "history" ); return result; } @@ -190,3 +220,5 @@ export async function getHashesWithPattern( return hashes; } + + diff --git a/backend/src/helpers/SchedulingNotifySendMessage.ts b/backend/src/helpers/SchedulingNotifySendMessage.ts index 01f1c0b..d75a903 100644 --- a/backend/src/helpers/SchedulingNotifySendMessage.ts +++ b/backend/src/helpers/SchedulingNotifySendMessage.ts @@ -104,16 +104,16 @@ const monitor = async () => { stdout = stdout[1].trim().split(/\s+/); // DISK SPACE MONITORING - const io = getIO(); - io.emit("diskSpaceMonit", { - action: "update", - diskSpace: { - size: stdout[1], - used: stdout[2], - available: stdout[3], - use: stdout[4] - } - }); + // const io = getIO(); + // io.emit("diskSpaceMonit", { + // action: "update", + // diskSpace: { + // size: stdout[1], + // used: stdout[2], + // available: stdout[3], + // use: stdout[4] + // } + // }); let data: any = {}; diff --git a/backend/src/helpers/SerializeUser.ts b/backend/src/helpers/SerializeUser.ts index 8f7250b..cbe7c20 100644 --- a/backend/src/helpers/SerializeUser.ts +++ b/backend/src/helpers/SerializeUser.ts @@ -3,8 +3,9 @@ import User from "../models/User"; interface SerializedUser { id: number; - name: string; - positionCompany: string; + name: string; + positionId: string | number; + position: object; email: string; profile: string; queues: Queue[]; @@ -13,10 +14,12 @@ interface SerializedUser { export const SerializeUser = (user: User): SerializedUser => { return { id: user.id, - name: user.name, - positionCompany: user.positionCompany, + name: user.name, + positionId: user.positionId, + position: user.position, email: user.email, profile: user.profile, - queues: user.queues + queues: user.queues, + }; }; diff --git a/backend/src/helpers/SetMessageAsRead.ts b/backend/src/helpers/SetMessageAsRead.ts index d29d297..e7aef63 100644 --- a/backend/src/helpers/SetMessageAsRead.ts +++ b/backend/src/helpers/SetMessageAsRead.ts @@ -3,9 +3,7 @@ import { getWbot } from "../libs/wbot"; import Message from "../models/Message" import Ticket from "../models/Ticket"; import Whatsapp from "../models/Whatsapp"; -import endPointQuery from "./old_EndPointQuery"; - -import whatsappOfficialAPI from "./WhatsappOfficialAPI"; +import endPointQuery from "./old_EndPointQuery"; export async function setMessageAsRead(ticket: Ticket) { if (ticket?.phoneNumberId) { diff --git a/backend/src/helpers/TicketConfig.ts b/backend/src/helpers/TicketConfig.ts index 0f7416e..fe29a61 100644 --- a/backend/src/helpers/TicketConfig.ts +++ b/backend/src/helpers/TicketConfig.ts @@ -39,7 +39,7 @@ const isHoliday = async (number: string | number) => { locale: ptBR }) ) - ); + ); if (currentDate.fullDate == startTime.fullDate) { obj.set = true; @@ -62,21 +62,8 @@ const isWeekend = async (number: string | number) => { weekend.value == "enabled" && weekend.message?.trim()?.length > 0 ) { - // Specify your desired timezone - const brazilTimeZone = "America/Sao_Paulo"; - - const currentDateUtc = new Date(); - - // Convert UTC date to Brazil time zone - const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone); - - // Format the date using the desired format - const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX"); - - const parsedDate = parseISO(formattedDate); - // Convert parsed date to Brazil time zone - const localDate = utcToZonedTime(parsedDate, brazilTimeZone); + const localDate = localDateConvert(); // Check if it's Saturday or Sunday if (isSaturday(localDate)) { @@ -173,8 +160,104 @@ async function isOutBusinessTime(number: string | number) { return obj; } -export { - isWeekend, - isHoliday, - isOutBusinessTime -}; +async function isOutBusinessTimeSaturday(number: string | number) { + let obj = { set: false, msg: "" }; + + // Convert parsed date to Brazil time zone + const localDate = localDateConvert(); + + // Check if it's Saturday or Sunday + if (!isSaturday(localDate)) { + return obj; + } + + const outBusinessHoursSaturday = await SettingTicket.findOne({ + where: { key: "saturdayBusinessTime", number } + }); + + let isWithinRange = false; + + if ( + outBusinessHoursSaturday && + outBusinessHoursSaturday.value == "enabled" && + outBusinessHoursSaturday?.message?.trim()?.length > 0 + ) { + const ticketDateTimeUpdate = splitDateTime( + new Date( + _format(new Date(), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const startTime = splitDateTime( + new Date( + _format( + new Date(outBusinessHoursSaturday.startTime), + "yyyy-MM-dd HH:mm:ss", + { + locale: ptBR + } + ) + ) + ); + + const endTime = splitDateTime( + new Date( + _format( + new Date(outBusinessHoursSaturday.endTime), + "yyyy-MM-dd HH:mm:ss", + { + locale: ptBR + } + ) + ) + ); + + const format = "HH:mm:ss"; + const parsedStartTime = parse( + ticketDateTimeUpdate.fullTime, + format, + new Date() + ); + const parsedEndTime = parse(startTime.fullTime, format, new Date()); + const parsedTimeToCheck = parse(endTime.fullTime, format, new Date()); + const timeInterval = { start: parsedStartTime, end: parsedEndTime }; + + // If the time range spans across different days, handle the date part + if (parsedEndTime < parsedStartTime) { + const nextDay = new Date(parsedStartTime); + nextDay.setDate(nextDay.getDate() + 1); + timeInterval.end = nextDay; + } + + isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); + + if (!isWithinRange) { + obj.set = true; + obj.msg = outBusinessHoursSaturday.message; + } + } + + return obj; +} + +function localDateConvert() { + const brazilTimeZone = "America/Sao_Paulo"; + + const currentDateUtc = new Date(); + + // Convert UTC date to Brazil time zone + const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone); + + // Format the date using the desired format + const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX"); + + const parsedDate = parseISO(formattedDate); + + // Convert parsed date to Brazil time zone + const localDate = utcToZonedTime(parsedDate, brazilTimeZone); + return localDate; +} + +export { isWeekend, isHoliday, isOutBusinessTime, isOutBusinessTimeSaturday }; diff --git a/backend/src/helpers/WhatsappOfficialAPI.ts b/backend/src/helpers/WhatsappOfficialAPI.ts index 2bc4703..3412751 100644 --- a/backend/src/helpers/WhatsappOfficialAPI.ts +++ b/backend/src/helpers/WhatsappOfficialAPI.ts @@ -1,11 +1,30 @@ import axios from "axios"; +import https from "https" +import http from "http" -const api = axios.create({ - baseURL: process.env.URL_WHATSAPP_API, - headers: { - Accept: "application/json", - Authorization: `Bearer ${process.env.TOKEN}` - } -}); -export default api; +// const api = axios.create({ +// baseURL: process.env.URL_WHATSAPP_API, +// headers: { +// Accept: "application/json", +// Authorization: `Bearer ${process.env.TOKEN}` +// }, +// httpAgent: new http.Agent({ keepAlive: true }), +// httpsAgent: new https.Agent({ keepAlive: true }) +// }); + +// export default api; + +const createApiClientWhatsOfficial = (token: string) => { + return axios.create({ + baseURL: process.env.URL_WHATSAPP_API, + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}` + }, + httpAgent: new http.Agent({ keepAlive: true }), + httpsAgent: new https.Agent({ keepAlive: true }) + }); +}; + +export default createApiClientWhatsOfficial; diff --git a/backend/src/helpers/WhatsappOfficialNumberInfo.ts b/backend/src/helpers/WhatsappOfficialNumberInfo.ts index 77ae2b3..9e08ea2 100644 --- a/backend/src/helpers/WhatsappOfficialNumberInfo.ts +++ b/backend/src/helpers/WhatsappOfficialNumberInfo.ts @@ -1,7 +1,15 @@ -import whatsappOfficialAPI from "./WhatsappOfficialAPI"; +import Whatsapp from "../models/Whatsapp"; +import createApiClientWhatsOfficial from "./WhatsappOfficialAPI"; async function whatsappOfficialNumberInfo(wabaId: string) { try { + const { whatsappOfficialToken }: any = await Whatsapp.findOne({ + where: { wabaId } + }); + + const whatsappOfficialAPI = createApiClientWhatsOfficial( + whatsappOfficialToken + ); const { data } = await whatsappOfficialAPI.get( `/${process.env.VERSION}/${wabaId}/phone_numbers` ); diff --git a/backend/src/helpers/controllByNumber.ts b/backend/src/helpers/controllByNumber.ts new file mode 100644 index 0000000..6ffad64 --- /dev/null +++ b/backend/src/helpers/controllByNumber.ts @@ -0,0 +1,62 @@ +import { values } from "sequelize/types/lib/operators"; +import Ticket from "../models/Ticket"; +import { get, set } from "./RedisClient"; +import { getIO } from "../libs/socket"; +import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; +import { v4 as uuidv4 } from "uuid"; + +async function controllByNumber() { + let tickets = await get({ key: "remote:ticketId*", parse: false }); + + if (!tickets) return { ticketIds: [], tickets: null }; + + let controll: any[] = []; + + for (const ticket of tickets) { + let match = ticket?.match(/"whatsappId":(\d+)/); + let whatsappId = match ? match[1] : null; + + const whatsapp = await get({ + key: `whatsapp:${whatsappId}` + }); + + match = whatsapp?.match(/"number":"(\d+)"/); + let number = match ? match[1] : null; + + match = ticket?.match(/"id":(\d+)/); + let ticketId = match ? match[1] : null; + + number = JSON.parse(number); + ticketId = JSON.parse(ticketId); + + const index = controll?.findIndex((c: any) => c.number == number); + + if (index == -1) { + controll?.push({ ticketId, number }); + } + } + + const ticketIds = controll?.map((c: any) => c.ticketId); + + //console.log("=======> ticketIds: ", ticketIds); + + for (const ticketId of ticketIds) { + const ticket: any = await Ticket.findByPk(ticketId); + if(ticket){ + const { status } = ticket; + + if (status && status == "pending") { + await UpdateTicketService({ + ticketData: { statusChatEnd: uuidv4() }, + ticketId: ticket.id + }); + } + } + } + + set(`remote:controll`, JSON.stringify(ticketIds)); + + return { ticketIds, tickets }; +} + +export default controllByNumber; diff --git a/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts index 84d1db9..91d9967 100644 --- a/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts +++ b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts @@ -1,3 +1,4 @@ +import { where } from "sequelize"; import { getIO } from "../libs/socket"; import Contact from "../models/Contact"; import Ticket from "../models/Ticket"; @@ -5,8 +6,9 @@ import { isValidMsg, verifyMessage } from "../services/WbotServices/wbotMessageListener"; - -import whatsappOfficialAPI from "./WhatsappOfficialAPI"; +import { get } from "./RedisClient"; +import createApiClientWhatsOfficial from "./WhatsappOfficialAPI"; +import Whatsapp from "../models/Whatsapp"; async function sendWhatsAppMessageOfficialAPI( ticket: Ticket, @@ -52,7 +54,14 @@ async function sendWhatsAppMessageOfficialAPI( return; } - console.log("SEND MESSAGE: ", JSON.stringify(data, null,2)); + const { whatsappOfficialToken }: any = await Whatsapp.findOne({ + where: { phoneNumberId } + }); + + console.log("SEND MESSAGE: ", JSON.stringify(data, null, 2)); + const whatsappOfficialAPI = createApiClientWhatsOfficial( + whatsappOfficialToken + ); whatsappOfficialAPI .post(`/${process.env.VERSION}/${phoneNumberId}/messages`, data) diff --git a/backend/src/helpers/sendWhatsMediaOfficialAPI.ts b/backend/src/helpers/sendWhatsMediaOfficialAPI.ts index d2c6390..dff553e 100644 --- a/backend/src/helpers/sendWhatsMediaOfficialAPI.ts +++ b/backend/src/helpers/sendWhatsMediaOfficialAPI.ts @@ -12,13 +12,14 @@ import { import ffmpeg from "fluent-ffmpeg"; import fs from "fs"; -import whatsappOfficialAPI from "./WhatsappOfficialAPI"; import path from "path"; import { convertAudioToOgg } from "../helpers/ConvertAudio"; import { bytesToMB } from "./BytesToMB"; import isThisHour from "date-fns/esm/isThisHour/index"; import AppError from "../errors/AppError"; +import createApiClientWhatsOfficial from "./WhatsappOfficialAPI"; +import Whatsapp from "../models/Whatsapp"; async function sendWhatsMediaOfficialAPI( ticket: Ticket, @@ -79,6 +80,14 @@ async function sendWhatsMediaOfficialAPI( return; } + const { whatsappOfficialToken }: any = await Whatsapp.findOne({ + where: { phoneNumberId } + }); + + const whatsappOfficialAPI = createApiClientWhatsOfficial( + whatsappOfficialToken + ); + whatsappOfficialAPI .post(`/${process.env.VERSION}/${phoneNumberId}/messages`, data) .then(response => { diff --git a/backend/src/middleware/isAuth.ts b/backend/src/middleware/isAuth.ts index b19552e..91061fc 100644 --- a/backend/src/middleware/isAuth.ts +++ b/backend/src/middleware/isAuth.ts @@ -19,10 +19,11 @@ const isAuth = (req: Request, res: Response, next: NextFunction): void => { throw new AppError("ERR_SESSION_EXPIRED", 401); } - const [, token] = authHeader.split(" "); + const [, token] = authHeader.split(" "); if ( - req.originalUrl == "/tickets/remote/create" && + (req.originalUrl == "/queue/remote/list" || + req.originalUrl == "/tickets/remote/create") && token === process.env.TOKEN_REMOTE_TICKET_CREATION ) { return next(); diff --git a/backend/src/middleware/verifyAPIKey.ts b/backend/src/middleware/verifyAPIKey.ts new file mode 100644 index 0000000..61cc6f7 --- /dev/null +++ b/backend/src/middleware/verifyAPIKey.ts @@ -0,0 +1,23 @@ +import { Request, Response, NextFunction } from "express"; +import AppError from "../errors/AppError"; +const verifyAPIKey = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + throw new AppError("ERR_SESSION_EXPIRED", 401); + } + + const [, token] = authHeader.split(" "); + + const apiKeyIsValid = token === process.env.TOKEN_IAM_HORACIUS_EL + if (!apiKeyIsValid) { + throw new AppError( + "Invalid token", + 401 + ); + } + + return next(); +}; + +export default verifyAPIKey; diff --git a/backend/src/models/Contact.ts b/backend/src/models/Contact.ts index d7c4c93..8a2aeb7 100644 --- a/backend/src/models/Contact.ts +++ b/backend/src/models/Contact.ts @@ -9,10 +9,13 @@ import { AllowNull, Unique, Default, - HasMany + HasMany, + BelongsToMany } from "sequelize-typescript"; import ContactCustomField from "./ContactCustomField"; import Ticket from "./Ticket"; +import Queue from "./Queue" +import ContactQueue from "./ContactQueues" @Table class Contact extends Model { @@ -52,6 +55,12 @@ class Contact extends Model { @HasMany(() => ContactCustomField) extraInfo: ContactCustomField[]; + + @BelongsToMany(() => Queue, () => ContactQueue) + queues: Array; + + @HasMany(() => ContactQueue) + contactQueue: ContactQueue[]; } export default Contact; diff --git a/backend/src/models/ContactQueues.ts b/backend/src/models/ContactQueues.ts new file mode 100644 index 0000000..ad371e0 --- /dev/null +++ b/backend/src/models/ContactQueues.ts @@ -0,0 +1,33 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + ForeignKey, + BelongsTo +} from "sequelize-typescript"; +import Queue from "./Queue"; +import Contact from "./Contact" + +@Table +class ContactQueue extends Model { + @ForeignKey(() => Contact) + @Column + contactId: number; + + @ForeignKey(() => Queue) + @Column + queueId: number; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @BelongsTo(() => Queue) + queue: Queue; +} + +export default ContactQueue; diff --git a/backend/src/models/Message.ts b/backend/src/models/Message.ts index 40fe9f8..7455195 100644 --- a/backend/src/models/Message.ts +++ b/backend/src/models/Message.ts @@ -31,6 +31,10 @@ class Message extends Model { @Column fromMe: boolean; + @Default(false) + @Column + fromAgent: boolean; + @Column(DataType.TEXT) body: string; diff --git a/backend/src/models/Position.ts b/backend/src/models/Position.ts new file mode 100644 index 0000000..c4814ba --- /dev/null +++ b/backend/src/models/Position.ts @@ -0,0 +1,48 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + ForeignKey, + BelongsTo, + HasMany, + HasOne, + AutoIncrement, + Default, + DataType, + Unique +} from "sequelize-typescript"; + +import Contact from "./Contact"; +import Message from "./Message"; +import Queue from "./Queue"; +import User from "./User"; +import Whatsapp from "./Whatsapp"; + +import SchedulingNotify from "./SchedulingNotify"; +import StatusChatEnd from "./StatusChatEnd"; + +@Table +class Position extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Unique + @Column(DataType.TEXT) + name: string; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @HasMany(() => User) + users: User[]; +} + +export default Position; diff --git a/backend/src/models/Queue.ts b/backend/src/models/Queue.ts index c5c06d9..5b7c3f2 100644 --- a/backend/src/models/Queue.ts +++ b/backend/src/models/Queue.ts @@ -36,6 +36,12 @@ class Queue extends Model { @Column greetingMessage: string; + @Column + farewellMessage: string; + + @Column + cc: string; + @CreatedAt createdAt: Date; diff --git a/backend/src/models/QuickAnswer.ts b/backend/src/models/QuickAnswer.ts index 3549734..b6fbde0 100644 --- a/backend/src/models/QuickAnswer.ts +++ b/backend/src/models/QuickAnswer.ts @@ -6,8 +6,12 @@ import { UpdatedAt, Model, PrimaryKey, - AutoIncrement + AutoIncrement, + BelongsToMany, + HasMany } from "sequelize-typescript"; +import Queue from "./Queue"; +import QuickAnswerQueue from "./QuickAnswerQueue"; @Table class QuickAnswer extends Model { @@ -27,6 +31,12 @@ class QuickAnswer extends Model { @UpdatedAt updatedAt: Date; + + @BelongsToMany(() => Queue, () => QuickAnswerQueue) + queues: Array; + + @HasMany(() => QuickAnswerQueue) + quickAnswerQueue: QuickAnswerQueue[]; } export default QuickAnswer; diff --git a/backend/src/models/QuickAnswerQueue.ts b/backend/src/models/QuickAnswerQueue.ts new file mode 100644 index 0000000..c34c9c2 --- /dev/null +++ b/backend/src/models/QuickAnswerQueue.ts @@ -0,0 +1,34 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + ForeignKey, + BelongsTo +} from "sequelize-typescript"; +import Queue from "./Queue"; +import Whatsapp from "./Whatsapp"; +import QuickAnswer from "./QuickAnswer" + +@Table +class QuickAnswerQueue extends Model { + @ForeignKey(() => QuickAnswer) + @Column + quickAnswerId: number; + + @ForeignKey(() => Queue) + @Column + queueId: number; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @BelongsTo(() => Queue) + queue: Queue; +} + +export default QuickAnswerQueue; diff --git a/backend/src/models/Setting.ts b/backend/src/models/Setting.ts index b58e57a..dec71a9 100644 --- a/backend/src/models/Setting.ts +++ b/backend/src/models/Setting.ts @@ -16,6 +16,9 @@ class Setting extends Model { @Column value: string; + @Column + obj: string; + @CreatedAt createdAt: Date; diff --git a/backend/src/models/StatusChatEnd.ts b/backend/src/models/StatusChatEnd.ts index dde5775..0f89388 100644 --- a/backend/src/models/StatusChatEnd.ts +++ b/backend/src/models/StatusChatEnd.ts @@ -1,35 +1,44 @@ import { - Table, - AutoIncrement, - Column, - CreatedAt, - UpdatedAt, - Model, - PrimaryKey, - HasMany - } from "sequelize-typescript"; + Table, + AutoIncrement, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + HasMany +} from "sequelize-typescript"; - import SchedulingNotify from "./SchedulingNotify"; +import SchedulingNotify from "./SchedulingNotify"; +import Ticket from "./Ticket"; - @Table - class StatusChatEnd extends Model { - @PrimaryKey - @AutoIncrement - @Column - id: number; - - @Column - name: string; - - @CreatedAt - createdAt: Date; - - @UpdatedAt - updatedAt: Date; +@Table +class StatusChatEnd extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; - @HasMany(() => SchedulingNotify) - SchedulingNotifies: SchedulingNotify[]; - } - - export default StatusChatEnd; - \ No newline at end of file + @Column + name: string; + + @Column + farewellMessage: string; + + @Column + isDefault: boolean; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @HasMany(() => SchedulingNotify) + SchedulingNotifies: SchedulingNotify[]; + + @HasMany(() => Ticket) + tickets: Ticket[]; +} + +export default StatusChatEnd; diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index d3b95c9..3f8b218 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -21,6 +21,7 @@ import User from "./User"; import Whatsapp from "./Whatsapp"; import SchedulingNotify from "./SchedulingNotify"; +import StatusChatEnd from "./StatusChatEnd"; @Table class Ticket extends Model { @@ -42,6 +43,18 @@ class Ticket extends Model { @Column isGroup: boolean; + @Default(false) + @Column + isRemote: boolean; + + @Default(false) + @Column + remoteDone: boolean; + + @ForeignKey(() => StatusChatEnd) + @Column + statusChatEndId: number; + @Column statusChatEnd: string; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index fb2109e..1be891d 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -11,13 +11,16 @@ import { AutoIncrement, Default, HasMany, - BelongsToMany, + BelongsToMany, + BelongsTo, + ForeignKey } from "sequelize-typescript"; import { hash, compare } from "bcryptjs"; import Ticket from "./Ticket"; import Queue from "./Queue"; import UserQueue from "./UserQueue"; import UserOnlineTime from "./UserOnlineTime"; +import Position from "./Position" @Table class User extends Model { @@ -40,10 +43,16 @@ class User extends Model { @Default(0) @Column - tokenVersion: number; - + tokenVersion: number; + @Column - positionCompany: string; + secondaryId: string; + + @Column + transferToOtherQueues: boolean; + + @Column + identifier: string; @Default("admin") @Column @@ -56,13 +65,20 @@ class User extends Model { updatedAt: Date; @HasMany(() => Ticket) tickets: Ticket[]; - + @HasMany(() => UserOnlineTime) - UserOnlineTime: UserOnlineTime[]; + UserOnlineTime: UserOnlineTime[]; @BelongsToMany(() => Queue, () => UserQueue) queues: Queue[]; + @ForeignKey(() => Position) + @Column + positionId: number; + + @BelongsTo(() => Position) + position: Position; + @BeforeUpdate @BeforeCreate static hashPassword = async (instance: User): Promise => { diff --git a/backend/src/models/Whatsapp.ts b/backend/src/models/Whatsapp.ts index 28cae6a..60ff078 100644 --- a/backend/src/models/Whatsapp.ts +++ b/backend/src/models/Whatsapp.ts @@ -87,6 +87,9 @@ class Whatsapp extends Model { @UpdatedAt updatedAt: Date; + @Column + whatsappOfficialToken: string; + @HasMany(() => Ticket) tickets: Ticket[]; diff --git a/backend/src/routes/PositionRoutes.ts b/backend/src/routes/PositionRoutes.ts new file mode 100644 index 0000000..9e1b293 --- /dev/null +++ b/backend/src/routes/PositionRoutes.ts @@ -0,0 +1,30 @@ +import express from "express"; +import isAuth from "../middleware/isAuth"; + +import * as PositionController from "../controllers/PositionController"; + +const positionRoutes = express.Router(); + +positionRoutes.get("/positions", isAuth, PositionController.index); + +positionRoutes.get( + "/positions/:positionId", + isAuth, + PositionController.show +); + +positionRoutes.post("/positions", isAuth, PositionController.store); + +positionRoutes.put( + "/positions/:positionId", + isAuth, + PositionController.update +); + +positionRoutes.delete( + "/positions/:positionId", + isAuth, + PositionController.remove +); + +export default positionRoutes; diff --git a/backend/src/routes/iamRoutesEL.ts b/backend/src/routes/iamRoutesEL.ts new file mode 100644 index 0000000..2181547 --- /dev/null +++ b/backend/src/routes/iamRoutesEL.ts @@ -0,0 +1,56 @@ +import { Router } from "express"; + +import * as IAMControllerEL from "../controllers/IAMControllerEL"; +import verifyAPIKey from "../middleware/verifyAPIKey"; + +const iamRoutesEL = Router(); + +iamRoutesEL.post( + "/iam/horacius/createUser", + verifyAPIKey, + IAMControllerEL.createUser +); + +iamRoutesEL.put( + "/iam/horacius/updateUser", + verifyAPIKey, + IAMControllerEL.updateUser +); + +iamRoutesEL.delete( + "/iam/horacius/deleteUser", + verifyAPIKey, + IAMControllerEL.deleteUser +); + +iamRoutesEL.get( + "/iam/horacius/listAllUsers", + verifyAPIKey, + IAMControllerEL.listAllUsers +); + +iamRoutesEL.get( + "/iam/horacius/checkUser", + verifyAPIKey, + IAMControllerEL.checkUser +); + +iamRoutesEL.patch( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.linkUserAndUserRight +); + +iamRoutesEL.post( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.checkUserRight +); + +iamRoutesEL.patch( + "/iam/horacius/resetPassword", + verifyAPIKey, + IAMControllerEL.resetPassword +); + +export default iamRoutesEL; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 297e954..4f041f5 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -14,10 +14,12 @@ import reportRoutes from "./reportRoutes"; import schedulingNotifiyRoutes from "./SchedulingNotifyRoutes"; import statusChatEndRoutes from "./statusChatEndRoutes"; import wbotMonitorRoutes from "./wbotMonitorRoutes"; - +import iamRoutesEL from "./iamRoutesEL"; +import positionRoutes from "./PositionRoutes" const routes = Router(); +routes.use(iamRoutesEL); routes.use(userRoutes); routes.use("/auth", authRoutes); routes.use(settingRoutes); @@ -33,5 +35,6 @@ routes.use(schedulingNotifiyRoutes); routes.use(reportRoutes); routes.use(statusChatEndRoutes); routes.use(wbotMonitorRoutes); +routes.use(positionRoutes); export default routes; diff --git a/backend/src/routes/queueRoutes.ts b/backend/src/routes/queueRoutes.ts index 6de13d9..96bb509 100644 --- a/backend/src/routes/queueRoutes.ts +++ b/backend/src/routes/queueRoutes.ts @@ -11,6 +11,8 @@ queueRoutes.post("/queue", isAuth, QueueController.store); queueRoutes.post("/queue/customization", QueueController.customization); +queueRoutes.get("/queue/remote/list", isAuth, QueueController.listQueues); + queueRoutes.get("/queue/:queueId", isAuth, QueueController.show); queueRoutes.put("/queue/:queueId", isAuth, QueueController.update); diff --git a/backend/src/routes/reportRoutes.ts b/backend/src/routes/reportRoutes.ts index 0c84aba..ecaf41a 100644 --- a/backend/src/routes/reportRoutes.ts +++ b/backend/src/routes/reportRoutes.ts @@ -1,18 +1,48 @@ //relatorio -import express from "express"; +import express from "express"; -import isAuth from "../middleware/isAuth"; - -import * as ReportController from "../controllers/ReportController"; +import isAuth from "../middleware/isAuth"; -const reportRoutes = express.Router(); +import * as ReportController from "../controllers/ReportController"; -reportRoutes.get("/reports", isAuth, ReportController.reportUserByDateStartDateEnd); +const reportRoutes = express.Router(); -reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); +reportRoutes.get( + "/reports", + isAuth, + ReportController.reportUserByDateStartDateEnd +); -reportRoutes.get("/reports/user/services", isAuth, ReportController.reportUserService); +reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); -reportRoutes.get("/reports/messages", isAuth, ReportController.reportMessagesUserByDateStartDateEnd); +reportRoutes.get( + "/reports/user/services", + isAuth, + ReportController.reportUserService +); + +reportRoutes.get( + "/reports/services/numbers", + isAuth, + ReportController.reportService +); + +reportRoutes.get( + "/reports/services/queues", + isAuth, + ReportController.reportServiceByQueue +); + +reportRoutes.get( + "/reports/messages", + isAuth, + ReportController.reportMessagesUserByDateStartDateEnd +); + +reportRoutes.get( + "/reports/count/statusChatEnd", + isAuth, + ReportController.reportTicksCountByStatusChatEnds +); export default reportRoutes; diff --git a/backend/src/routes/settingRoutes.ts b/backend/src/routes/settingRoutes.ts index dd9f6e7..280fb7c 100644 --- a/backend/src/routes/settingRoutes.ts +++ b/backend/src/routes/settingRoutes.ts @@ -9,7 +9,7 @@ settingRoutes.get("/settings", SettingController.index); settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings); -// routes.get("/settings/:settingKey", isAuth, SettingsController.show); +// settingRoutes.get("/settings/:settingKey", isAuth, SettingsController.show); settingRoutes.put( "/settings/ticket", diff --git a/backend/src/routes/statusChatEndRoutes.ts b/backend/src/routes/statusChatEndRoutes.ts index 9eff396..9dd0920 100644 --- a/backend/src/routes/statusChatEndRoutes.ts +++ b/backend/src/routes/statusChatEndRoutes.ts @@ -3,8 +3,29 @@ import isAuth from "../middleware/isAuth"; import * as StatusChatEnd from "../controllers/StatusChatEndController"; -const statusChatEndRoutes = Router(); +const statusChatEndRoutes = Router(); -statusChatEndRoutes.get("/statusChatEnd", isAuth, StatusChatEnd.show); +statusChatEndRoutes.post("/statusChatEnd", isAuth, StatusChatEnd.store); -export default statusChatEndRoutes; \ No newline at end of file +// statusChatEndRoutes.get("/statusChatEnd", isAuth, StatusChatEnd.show); +statusChatEndRoutes.get("/statusChatEnd", isAuth, StatusChatEnd.index); + +statusChatEndRoutes.get( + "/statusChatEnd/:statusChatEndId", + isAuth, + StatusChatEnd.show +); + +statusChatEndRoutes.put( + "/statusChatEnd/:statusChatEndId", + isAuth, + StatusChatEnd.update +); + +statusChatEndRoutes.delete( + "/statusChatEnd/:statusChatEndId", + isAuth, + StatusChatEnd.remove +); + +export default statusChatEndRoutes; diff --git a/backend/src/server.ts b/backend/src/server.ts index 5381d13..2a34717 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -6,13 +6,18 @@ import { StartAllWhatsAppsSessions } from "./services/WbotServices/StartAllWhats import User from "./models/User"; import Whatsapp from "./models/Whatsapp"; import endPointQuery from "./helpers/EndPointQuery"; -import { cacheSize, flushCache, loadTicketsCache } from "./helpers/TicketCache"; +import { + cacheSize, + flushCache, + loadTicketsCache, +} from "./helpers/TicketCache"; import "./helpers/CloseBotTickets"; import { loadContactsCache } from "./helpers/ContactsCache"; import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache"; import { delRestoreControllFile } from "./helpers/RestoreControll"; import "./helpers/AutoCloseTickets"; +import "./helpers/AutoRemoteTickets"; import "./helpers/SchedulingNotifySendMessage"; import axios from "axios"; @@ -27,6 +32,9 @@ import { clearAllKeys, get, set } from "./helpers/RedisClient"; import ShowUserService from "./services/UserServices/ShowUserService"; import { json } from "sequelize"; import { setBotInfo } from "./helpers/SetBotInfo"; +import Queue from "./models/Queue"; +import StatusChatEnd from "./models/StatusChatEnd"; +import Message from "./models/Message"; const server = app.listen(process.env.PORT, () => { logger.info(`Server started on port: ${process.env.PORT}`); @@ -47,7 +55,28 @@ gracefulShutdown(server); (async () => { console.log("os.tmpdir(): ", os.tmpdir()); - await clearAllKeys("user:*", "whatsapp:*"); + await clearAllKeys( + "user:*", + "whatsapp:*", + "queue:*", + "statusChatEnd:*", + ); + + // let messages: any = await Message.findAll({ + // attributes: ["id", "ticketId", "body"] + // }); + + // for (const message of messages) { + // const { id, body, ticketId } = message; + // await set(`message:ticketId:${ticketId}:id:${id}`, body); + // } + + const statusChatEnds = await StatusChatEnd.findAll(); + + for (const statusChatEnd of statusChatEnds) { + const { id, name, farewellMessage } = statusChatEnd; + await set(`statusChatEnd:${id}`, { id, name, farewellMessage }); + } const users = await User.findAll(); @@ -62,23 +91,47 @@ gracefulShutdown(server); await set(`user:${id}`, { id, name }); } + const queues = await Queue.findAll(); + + for (const queue of queues) { + const { id, greetingMessage, name, farewellMessage } = queue; + await set(`queue:${id}`, { id, name, greetingMessage, farewellMessage }); + } + loadSettings(); let whatsapps: any = await Whatsapp.findAll({ - attributes: ["id", "url", "phoneNumberId", "number"] + attributes: ["id", "url", "phoneNumberId", "number", "greetingMessage"] }); if (whatsapps && whatsapps.length > 0) { for (let i = 0; i < whatsapps.length; i++) { try { - const { phoneNumberId } = whatsapps[i]; + const { phoneNumberId, id, greetingMessage } = whatsapps[i]; + + // if (phoneNumberId) { + // await set( + // `whatsapp:${whatsapps[i].dataValues.id}`, + // JSON.stringify({ + // number: whatsapps[i].dataValues.number, + // id, + // greetingMessage, + // phoneNumberId + // }) + // ); + // } + + await set( + `whatsapp:${whatsapps[i].dataValues.id}`, + JSON.stringify({ + number: whatsapps[i].dataValues.number, + id, + greetingMessage, + phoneNumberId + }) + ); if (phoneNumberId) { - await set( - `whatsapp:${whatsapps[i].dataValues.id}`, - `${whatsapps[i].dataValues.number}` - ); - continue; } diff --git a/backend/src/services/ContactServices/AssociateContatctQueue.ts b/backend/src/services/ContactServices/AssociateContatctQueue.ts new file mode 100644 index 0000000..f4acdab --- /dev/null +++ b/backend/src/services/ContactServices/AssociateContatctQueue.ts @@ -0,0 +1,12 @@ +import Contact from "../../models/Contact"; + +const AssociateContatctQueue = async ( + contact: Contact, + queueIds: number[] +): Promise => { + await contact.$set("queues", queueIds); + + await contact.reload(); +}; + +export default AssociateContatctQueue; diff --git a/backend/src/services/ContactServices/CreateContactService.ts b/backend/src/services/ContactServices/CreateContactService.ts index 692be06..51d5a86 100644 --- a/backend/src/services/ContactServices/CreateContactService.ts +++ b/backend/src/services/ContactServices/CreateContactService.ts @@ -1,8 +1,9 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; -import { createOrUpdateContactCache } from '../../helpers/ContactsCache' +import { createOrUpdateContactCache } from "../../helpers/ContactsCache"; import GetProfilePicUrl from "../WbotServices/GetProfilePicUrl"; +import AssociateContatctQueue from "./AssociateContatctQueue"; interface ExtraInfo { name: string; @@ -15,26 +16,26 @@ interface Request { email?: string; profilePicUrl?: string; extraInfo?: ExtraInfo[]; + queueIds?: number[]; } const CreateContactService = async ({ name, number, email = "", - profilePicUrl='', - extraInfo = [] -}: Request): Promise => { - + profilePicUrl = "", + extraInfo = [], + queueIds = [] +}: Request): Promise => { try { - const numberExists = await Contact.findOne({ where: { number } }); - + if (numberExists) { throw new AppError("ERR_DUPLICATED_CONTACT"); } - + const contact = await Contact.create( { name, @@ -47,22 +48,26 @@ const CreateContactService = async ({ include: ["extraInfo"] } ); - - - + + await AssociateContatctQueue(contact, queueIds); + // TEST DEL - await createOrUpdateContactCache(`contact:${contact.id}`, {id: contact.id, name, number, profilePicUrl, isGroup:'false', extraInfo, email }) + await createOrUpdateContactCache(`contact:${contact.id}`, { + id: contact.id, + name, + number, + profilePicUrl, + isGroup: "false", + extraInfo, + email + }); // - - + return contact; - } catch (error: any) { - console.error('===> Error on CreateContactService.ts file: \n', error) + console.error("===> Error on CreateContactService.ts file: \n", error); throw new AppError(error.message); } - - }; export default CreateContactService; diff --git a/backend/src/services/ContactServices/ListContactsService.ts b/backend/src/services/ContactServices/ListContactsService.ts index f624308..b1b442d 100644 --- a/backend/src/services/ContactServices/ListContactsService.ts +++ b/backend/src/services/ContactServices/ListContactsService.ts @@ -1,9 +1,16 @@ import { Sequelize, Op } from "sequelize"; import Contact from "../../models/Contact"; +import Queue from "../../models/Queue"; +import { getSettingValue } from "../../helpers/WhaticketSettings"; +// import ListContactsService from "../services/ContactServices/ListContactsService"; +import ListWhatsappQueueByUserQueue from "../WhatsappService/ListWhatsAppsForQueueService"; +import QueuesByUser from "../UserServices/ShowQueuesByUser"; +import { number } from "yup"; interface Request { searchParam?: string; pageNumber?: string; + userId?: string; } interface Response { @@ -14,13 +21,14 @@ interface Response { const ListContactsService = async ({ searchParam = "", - pageNumber = "1" + pageNumber = "1", + userId }: Request): Promise => { const whereCondition = { [Op.or]: [ { name: Sequelize.where( - Sequelize.fn("LOWER", Sequelize.col("name")), + Sequelize.fn("LOWER", Sequelize.col("Contact.name")), "LIKE", `%${searchParam.toLowerCase().trim()}%` ) @@ -29,22 +37,61 @@ const ListContactsService = async ({ ] }; const limit = 20; - const offset = limit * (+pageNumber - 1); + const offset = limit * (+pageNumber - 1); - const { count, rows: contacts } = await Contact.findAndCountAll({ + let { count, rows: contacts } = await Contact.findAndCountAll({ where: whereCondition, limit, + include: [ + { + model: Queue, + as: "queues", + // where: whereConditionQueue, + attributes: ["id", "name", "color", "greetingMessage"] + } + ], offset, order: [["name", "ASC"]] - }); + }); const hasMore = count > offset + contacts.length; + if (getSettingValue("contactByqueues")?.value == "enabled") { + const queueIds = await QueuesByUser({ userId }); + + contacts = contactQueueFilter(queueIds, contacts); + } + return { contacts, count, hasMore }; }; - export default ListContactsService; + +function contactQueueFilter(queueIds: any[], contacts: Contact[]) { + let auxContact: any[] = []; + let repet: any[] = []; + const userQueues = queueIds.map((uq: any) => uq.queueId); + + for (const contact of contacts) { + const { queues, id } = contact; + + if (queues.length == 0) { + auxContact.push(contact); + continue; + } + + for (const q of queues) { + if (userQueues.includes(q.id)) { + if (repet.includes(id)) continue; + repet.push(id); + + auxContact.push(contact); + } + } + } + + return auxContact; +} diff --git a/backend/src/services/ContactServices/ShowContactService.ts b/backend/src/services/ContactServices/ShowContactService.ts index 4b215c4..7a856ec 100644 --- a/backend/src/services/ContactServices/ShowContactService.ts +++ b/backend/src/services/ContactServices/ShowContactService.ts @@ -1,8 +1,20 @@ import Contact from "../../models/Contact"; import AppError from "../../errors/AppError"; +import { getSettingValue } from "../../helpers/WhaticketSettings"; +import Queue from "../../models/Queue"; const ShowContactService = async (id: string | number): Promise => { - const contact = await Contact.findByPk(id, { include: ["extraInfo"] }); + let includeQueue: any[] = []; + + if (getSettingValue("contactByqueues")?.value == "enabled") { + includeQueue = [ + { model: Queue, as: "queues", attributes: ["id", "name", "color"] } + ]; + } + + const contact = await Contact.findByPk(id, { + include: ["extraInfo", ...includeQueue] + }); if (!contact) { throw new AppError("ERR_NO_CONTACT_FOUND", 404); diff --git a/backend/src/services/ContactServices/UpdateContactService.ts b/backend/src/services/ContactServices/UpdateContactService.ts index 3478743..396d52e 100644 --- a/backend/src/services/ContactServices/UpdateContactService.ts +++ b/backend/src/services/ContactServices/UpdateContactService.ts @@ -2,9 +2,10 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; import ContactCustomField from "../../models/ContactCustomField"; -import { updateTicketsByContactsCache } from '../../helpers/TicketCache' -import { updateContactCacheById } from '../../helpers/ContactsCache' +import { updateTicketsByContactsCache } from "../../helpers/TicketCache"; +import { updateContactCacheById } from "../../helpers/ContactsCache"; import { tr } from "date-fns/locale"; +import AssociateContatctQueue from "./AssociateContatctQueue"; interface ExtraInfo { id?: number; @@ -16,6 +17,7 @@ interface ContactData { number?: string; name?: string; extraInfo?: ExtraInfo[]; + queueIds?: number[]; } interface Request { @@ -23,15 +25,12 @@ interface Request { contactId: string; } - const UpdateContactService = async ({ contactData, contactId }: Request): Promise => { - try { - - const { email, name, number, extraInfo } = contactData; + const { email, name, number, extraInfo, queueIds } = contactData; // console.log('email, name, number, extraInfo: ', email, name, number, extraInfo) @@ -54,7 +53,9 @@ const UpdateContactService = async ({ await Promise.all( contact.extraInfo.map(async oldInfo => { - const stillExists = extraInfo.findIndex(info => info.id === oldInfo.id); + const stillExists = extraInfo.findIndex( + info => info.id === oldInfo.id + ); if (stillExists === -1) { await ContactCustomField.destroy({ where: { id: oldInfo.id } }); @@ -63,11 +64,10 @@ const UpdateContactService = async ({ ); } - const oldNumber = contact.number - + const oldNumber = contact.number; //Solução para o erro tcp_wrap.cc - console.log('----------> oldNumber: ', oldNumber) + console.log("----------> oldNumber: ", oldNumber); if (number) { const numberExists = await Contact.findOne({ where: { number } @@ -78,37 +78,35 @@ const UpdateContactService = async ({ } } - await contact.update({ name, number, email }); - + + if (queueIds) await AssociateContatctQueue(contact, queueIds); //TEST DEL - await updateTicketsByContactsCache(oldNumber, contact.name, contact.number) + await updateTicketsByContactsCache(oldNumber, contact.name, contact.number); // - await contact.reload({ attributes: ["id", "name", "number", "email", "profilePicUrl"], include: ["extraInfo"] }); - - // console.log('contactcontactcontactcontact: ',flatten(JSON.parse(JSON.stringify(contact)))) - await updateContactCacheById(contact.id, JSON.parse(JSON.stringify(contact))) + await updateContactCacheById( + contact.id, + JSON.parse(JSON.stringify(contact)) + ); return contact; - } catch (error: any) { - console.error('===> Error on UpdateContactService.ts file: \n', error) + console.error("===> Error on UpdateContactService.ts file: \n", error); throw new AppError(error.message); } - }; export default UpdateContactService; diff --git a/backend/src/services/MessageServices/CreateMessageService.ts b/backend/src/services/MessageServices/CreateMessageService.ts index 95af0e3..6262728 100644 --- a/backend/src/services/MessageServices/CreateMessageService.ts +++ b/backend/src/services/MessageServices/CreateMessageService.ts @@ -1,4 +1,5 @@ import AppError from "../../errors/AppError"; +import { get, set } from "../../helpers/RedisClient"; import { updateTicketCacheByTicketId } from "../../helpers/TicketCache"; import { getIO } from "../../libs/socket"; import Message from "../../models/Message"; @@ -13,6 +14,7 @@ interface MessageData { read?: boolean; mediaType?: string; mediaUrl?: string; + fromAgent?: boolean; } interface Request { messageData: MessageData; @@ -20,10 +22,8 @@ interface Request { const CreateMessageService = async ({ messageData -}: Request): Promise => { - +}: Request): Promise => { try { - await Message.upsert(messageData); const message = await Message.findByPk(messageData.id, { @@ -46,13 +46,35 @@ const CreateMessageService = async ({ throw new Error("ERR_CREATING_MESSAGE"); } - if (message.ticket.status != "queueChoice") { + //////////////////// SETTINGS /////////////////////////// + let ticketRemote = await get({ + key: `remote:ticketId:${message.ticket.id}` + }); + + if (ticketRemote && !ticketRemote.includes("messageDateTime")) { + ticketRemote = JSON.parse(ticketRemote); + + ticketRemote = { + ...ticketRemote, + ...{ messageDateTime: message.createdAt } + }; + + const ticket = await Ticket.findByPk(message.ticket.id); + ticket?.update({ remoteDone: true }); + + console.log('MESSAGE SERVICE: XXXXXXXXXXXXXXXXXXXXXXX') + + set(`remote:ticketId:${message.ticket.id}`, JSON.stringify(ticketRemote)); + } + ////////////////////////////////////////////////////////// + + if (message.ticket.status != "queueChoice") { await updateTicketCacheByTicketId(message.ticket.id, { lastMessage: message.body, updatedAt: new Date(message.ticket.updatedAt).toISOString(), "contact.profilePicUrl": message.ticket.contact.profilePicUrl, unreadMessages: message.ticket.unreadMessages - }); + }); const io = getIO(); io.to(message.ticketId.toString()) diff --git a/backend/src/services/PositionService/CreatePositionService.ts b/backend/src/services/PositionService/CreatePositionService.ts new file mode 100644 index 0000000..7f8271b --- /dev/null +++ b/backend/src/services/PositionService/CreatePositionService.ts @@ -0,0 +1,23 @@ +import AppError from "../../errors/AppError"; +import Position from "../../models/Position"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; + +interface Request { + name: string; +} + +const CreatePositionService = async ({ name }: Request): Promise => { + const nameExists = await Position.findOne({ + where: { name } + }); + + if (nameExists) { + throw new AppError("ERR_POSITION_DUPLICATED"); + } + + const position = await Position.create({ name }); + + return position; +}; + +export default CreatePositionService; diff --git a/backend/src/services/PositionService/DeletePositionService.ts b/backend/src/services/PositionService/DeletePositionService.ts new file mode 100644 index 0000000..6fa5d99 --- /dev/null +++ b/backend/src/services/PositionService/DeletePositionService.ts @@ -0,0 +1,16 @@ +import Position from "../../models/Position"; +import AppError from "../../errors/AppError"; + +const DeletePositionService = async (id: string): Promise => { + const position = await Position.findOne({ + where: { id } + }); + + if (!position) { + throw new AppError("ERR_NO_POSITION_FOUND", 404); + } + + await position.destroy(); +}; + +export default DeletePositionService; diff --git a/backend/src/services/PositionService/ListPositionService.ts b/backend/src/services/PositionService/ListPositionService.ts new file mode 100644 index 0000000..8300f6f --- /dev/null +++ b/backend/src/services/PositionService/ListPositionService.ts @@ -0,0 +1,48 @@ +import { Op, Sequelize } from "sequelize"; +import Position from "../../models/Position"; + +interface Request { + searchParam?: string; + pageNumber?: string; +} + +interface Response { + positions: Position[]; + count: number; + hasMore: boolean; +} + +const ListPositionService = async ({ + searchParam = "", + pageNumber = "1" +}: Request): Promise => { + console.log("searchParam: ", searchParam); + + const whereCondition = { + message: Sequelize.where( + Sequelize.fn("LOWER", Sequelize.col("name")), + "LIKE", + `%${searchParam.toLowerCase().trim()}%` + ) + }; + + const limit = 20; + const offset = limit * (+pageNumber - 1); + + let { count, rows: positions } = await Position.findAndCountAll({ + where: whereCondition, + limit, + offset, + order: [["name", "ASC"]] + }); + + const hasMore = count > offset + positions.length; + + return { + positions, + count, + hasMore + }; +}; + +export default ListPositionService; diff --git a/backend/src/services/PositionService/ShowPositionService.ts b/backend/src/services/PositionService/ShowPositionService.ts new file mode 100644 index 0000000..652cf06 --- /dev/null +++ b/backend/src/services/PositionService/ShowPositionService.ts @@ -0,0 +1,16 @@ +import AppError from "../../errors/AppError"; +import Position from "../../models/Position"; + +const ShowPositionService = async ( + id: string, +): Promise => { + const position = await Position.findByPk(id); + + if (!position) { + throw new AppError("ERR_NO_POSITION_FOUND", 404); + } + + return position; +}; + +export default ShowPositionService; diff --git a/backend/src/services/PositionService/UpdatePositionService.ts b/backend/src/services/PositionService/UpdatePositionService.ts new file mode 100644 index 0000000..076a668 --- /dev/null +++ b/backend/src/services/PositionService/UpdatePositionService.ts @@ -0,0 +1,38 @@ +import Position from "../../models/Position"; +import AppError from "../../errors/AppError"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; + +interface PositionData { + name: string; +} + +interface Request { + positionData: PositionData; + positionId: string; +} + +const UpdatePositionService = async ({ + positionData, + positionId +}: Request): Promise => { + const { name } = positionData; + + const position = await Position.findOne({ + where: { id: positionId } + }); + + if (!position) { + throw new AppError("ERR_NO_POSITION_FOUND", 404); + } + await position.update({ + name + }); + + await position.reload({ + attributes: ["id", "name"] + }); + + return position; +}; + +export default UpdatePositionService; diff --git a/backend/src/services/QueueService/AssociateQuickAnswerQueue.ts b/backend/src/services/QueueService/AssociateQuickAnswerQueue.ts new file mode 100644 index 0000000..73586ca --- /dev/null +++ b/backend/src/services/QueueService/AssociateQuickAnswerQueue.ts @@ -0,0 +1,12 @@ +import QuickAnswer from "../../models/QuickAnswer"; + +const AssociateQuickAnswerQueue = async ( + QuickAnswer: QuickAnswer, + queueIds: number[] +): Promise => { + await QuickAnswer.$set("queues", queueIds); + + await QuickAnswer.reload(); +}; + +export default AssociateQuickAnswerQueue; diff --git a/backend/src/services/QueueService/CreateQueueService.ts b/backend/src/services/QueueService/CreateQueueService.ts index b783da8..a8acdf4 100644 --- a/backend/src/services/QueueService/CreateQueueService.ts +++ b/backend/src/services/QueueService/CreateQueueService.ts @@ -1,76 +1,83 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; +import { set } from "../../helpers/RedisClient"; interface QueueData { name: string; color: string; greetingMessage?: string; + farewellMessage?: string; + cc?: string; } const CreateQueueService = async (queueData: QueueData): Promise => { - try { - const { color, name } = queueData; - const queueSchema = Yup.object().shape({ - name: Yup.string() - .min(2, "ERR_QUEUE_INVALID_NAME") - .required("ERR_QUEUE_INVALID_NAME") - .test( - "Check-unique-name", - "ERR_QUEUE_NAME_ALREADY_EXISTS", - async value => { - if (value) { - const queueWithSameName = await Queue.findOne({ - where: { name: value } - }); + const queueSchema = Yup.object().shape({ + name: Yup.string() + .min(2, "ERR_QUEUE_INVALID_NAME") + .required("ERR_QUEUE_INVALID_NAME") + .test( + "Check-unique-name", + "ERR_QUEUE_NAME_ALREADY_EXISTS", + async value => { + if (value) { + const queueWithSameName = await Queue.findOne({ + where: { name: value } + }); - return !queueWithSameName; + return !queueWithSameName; + } + return false; + } + ), + color: Yup.string() + .required("ERR_QUEUE_INVALID_COLOR") + .test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => { + if (value) { + const colorTestRegex = /^#[0-9a-f]{3,6}$/i; + return colorTestRegex.test(value); } return false; - } - ), - color: Yup.string() - .required("ERR_QUEUE_INVALID_COLOR") - .test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => { - if (value) { - const colorTestRegex = /^#[0-9a-f]{3,6}$/i; - return colorTestRegex.test(value); - } - return false; - }) - .test( - "Check-color-exists", - "ERR_QUEUE_COLOR_ALREADY_EXISTS", - async value => { - if (value) { - const queueWithSameColor = await Queue.findOne({ - where: { color: value } - }); - return !queueWithSameColor; + }) + .test( + "Check-color-exists", + "ERR_QUEUE_COLOR_ALREADY_EXISTS", + async value => { + if (value) { + const queueWithSameColor = await Queue.findOne({ + where: { color: value } + }); + return !queueWithSameColor; + } + return false; } - return false; - } - ) - }); + ) + }); - try { - await queueSchema.validate({ color, name }); - } catch (err: any) { - throw new AppError(err.message); - } + try { + await queueSchema.validate({ color, name }); + } catch (err: any) { + throw new AppError(err.message); + } - const queue = await Queue.create(queueData); + const queue = await Queue.create(queueData); - return queue; + const { id, greetingMessage, farewellMessage } = queue; + await set(`queue:${id}`, { + id, + name, + greetingMessage, + farewellMessage + }); + return queue; } catch (error: any) { - console.error('===> Error on CreateQueueService.ts file: \n', error) + console.error("===> Error on CreateQueueService.ts file: \n", error); throw new AppError(error.message); } - }; export default CreateQueueService; diff --git a/backend/src/services/QueueService/DeleteQueueService.ts b/backend/src/services/QueueService/DeleteQueueService.ts index 59201d7..47c4772 100644 --- a/backend/src/services/QueueService/DeleteQueueService.ts +++ b/backend/src/services/QueueService/DeleteQueueService.ts @@ -4,28 +4,28 @@ import UserQueue from "../../models/UserQueue"; import ListTicketsServiceCache from "../TicketServices/ListTicketServiceCache"; -import { deleteTicketsFieldsCache } from '../../helpers/TicketCache' +import { deleteTicketsFieldsCache } from "../../helpers/TicketCache"; +import { del } from "../../helpers/RedisClient"; const DeleteQueueService = async (queueId: number | string): Promise => { - const queue = await ShowQueueService(queueId); if (queue.id) { + const tickets = await ListTicketsServiceCache({ queueId }); - const tickets = await ListTicketsServiceCache({ queueId }) - - await deleteTicketsFieldsCache(tickets, ['queue.id', 'queue.name', 'queue.color']) - + await deleteTicketsFieldsCache(tickets, [ + "queue.id", + "queue.name", + "queue.color" + ]); } - - try { + try { await UserQueue.destroy({ where: { queueId: queueId } }); + del(`queue:${queueId}`); } catch (error) { - - console.log('Error on delete UserQueue by queueId: ', queueId) - + console.log("Error on delete UserQueue by queueId: ", queueId); } await queue.destroy(); diff --git a/backend/src/services/QueueService/UpdateQueueService.ts b/backend/src/services/QueueService/UpdateQueueService.ts index d52da18..c118950 100644 --- a/backend/src/services/QueueService/UpdateQueueService.ts +++ b/backend/src/services/QueueService/UpdateQueueService.ts @@ -3,20 +3,21 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; import ShowQueueService from "./ShowQueueService"; +import { set } from "../../helpers/RedisClient"; interface QueueData { name?: string; color?: string; greetingMessage?: string; + farewellMessage?: string; + cc?: string; } const UpdateQueueService = async ( queueId: number | string, queueData: QueueData ): Promise => { - try { - const { color, name } = queueData; const queueSchema = Yup.object().shape({ @@ -30,7 +31,7 @@ const UpdateQueueService = async ( const queueWithSameName = await Queue.findOne({ where: { name: value, id: { [Op.not]: queueId } } }); - + return !queueWithSameName; } return true; @@ -59,24 +60,30 @@ const UpdateQueueService = async ( } ) }); - + try { await queueSchema.validate({ color, name }); } catch (err: any) { throw new AppError(err.message); } - + const queue = await ShowQueueService(queueId); - + await queue.update(queueData); - + + const { greetingMessage, farewellMessage } = queue; + await set(`queue:${queueId}`, { + id: queueId, + name, + greetingMessage, + farewellMessage + }); + return queue; - } catch (error: any) { - console.error('===> Error on UpdateQueueService.ts file: \n', error) + console.error("===> Error on UpdateQueueService.ts file: \n", error); throw new AppError(error.message); - } - + } }; export default UpdateQueueService; diff --git a/backend/src/services/QuickAnswerService/CreateQuickAnswerService.ts b/backend/src/services/QuickAnswerService/CreateQuickAnswerService.ts index 80668e1..6d0d694 100644 --- a/backend/src/services/QuickAnswerService/CreateQuickAnswerService.ts +++ b/backend/src/services/QuickAnswerService/CreateQuickAnswerService.ts @@ -1,14 +1,17 @@ import AppError from "../../errors/AppError"; import QuickAnswer from "../../models/QuickAnswer"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; interface Request { shortcut: string; message: string; + queueIds?: number[]; } const CreateQuickAnswerService = async ({ shortcut, - message + message, + queueIds = [] }: Request): Promise => { const nameExists = await QuickAnswer.findOne({ where: { shortcut } @@ -20,6 +23,8 @@ const CreateQuickAnswerService = async ({ const quickAnswer = await QuickAnswer.create({ shortcut, message }); + await AssociateQuickAnswerQueue(quickAnswer, queueIds); + return quickAnswer; }; diff --git a/backend/src/services/QuickAnswerService/ListQuickAnswerService.ts b/backend/src/services/QuickAnswerService/ListQuickAnswerService.ts index 0ddcbcc..093cfaa 100644 --- a/backend/src/services/QuickAnswerService/ListQuickAnswerService.ts +++ b/backend/src/services/QuickAnswerService/ListQuickAnswerService.ts @@ -1,9 +1,14 @@ -import { Sequelize } from "sequelize"; +import { Op, Sequelize } from "sequelize"; import QuickAnswer from "../../models/QuickAnswer"; +import Queue from "../../models/Queue"; +import QueuesByUser from "../UserServices/ShowQueuesByUser"; +import quickAnswearByQueueFiltered from "../../helpers/QuickAnswearByqueueFiltered"; +import { getSettingValue } from "../../helpers/WhaticketSettings"; interface Request { searchParam?: string; pageNumber?: string; + userId?: string | number; } interface Response { @@ -14,8 +19,11 @@ interface Response { const ListQuickAnswerService = async ({ searchParam = "", - pageNumber = "1" + pageNumber = "1", + userId }: Request): Promise => { + console.log("searchParam: ", searchParam); + const whereCondition = { message: Sequelize.where( Sequelize.fn("LOWER", Sequelize.col("message")), @@ -23,11 +31,21 @@ const ListQuickAnswerService = async ({ `%${searchParam.toLowerCase().trim()}%` ) }; + const limit = 20; const offset = limit * (+pageNumber - 1); - const { count, rows: quickAnswers } = await QuickAnswer.findAndCountAll({ + let { count, rows: quickAnswers } = await QuickAnswer.findAndCountAll({ where: whereCondition, + + include: [ + { + model: Queue, + as: "queues", + attributes: ["id", "name", "color", "greetingMessage"] + } + ], + limit, offset, order: [["message", "ASC"]] @@ -35,6 +53,11 @@ const ListQuickAnswerService = async ({ const hasMore = count > offset + quickAnswers.length; + if (getSettingValue("quickAnswerByQueue")?.value == "enabled") { + const queueIds = await QueuesByUser({ userId }); + quickAnswers = quickAnswearByQueueFiltered(queueIds, quickAnswers); + } + return { quickAnswers, count, diff --git a/backend/src/services/QuickAnswerService/ShowQuickAnswerService.ts b/backend/src/services/QuickAnswerService/ShowQuickAnswerService.ts index 1ed3d2e..03b9c0c 100644 --- a/backend/src/services/QuickAnswerService/ShowQuickAnswerService.ts +++ b/backend/src/services/QuickAnswerService/ShowQuickAnswerService.ts @@ -1,12 +1,28 @@ import QuickAnswer from "../../models/QuickAnswer"; import AppError from "../../errors/AppError"; +import Queue from "../../models/Queue"; +import QueuesByUser from "../UserServices/ShowQueuesByUser"; +import quickAnswearByQueueFiltered from "../../helpers/QuickAnswearByqueueFiltered"; -const ShowQuickAnswerService = async (id: string): Promise => { - const quickAnswer = await QuickAnswer.findByPk(id); +const ShowQuickAnswerService = async ( + id: string, + userId?: string +): Promise => { + + const quickAnswer = await QuickAnswer.findByPk(id, { + include: [ + { + model: Queue, + as: "queues", + attributes: ["id", "name", "color", "greetingMessage"] + } + ], + order: [["queues", "id", "ASC"]] + }); if (!quickAnswer) { throw new AppError("ERR_NO_QUICK_ANSWERS_FOUND", 404); - } + } return quickAnswer; }; diff --git a/backend/src/services/QuickAnswerService/UpdateQuickAnswerService.ts b/backend/src/services/QuickAnswerService/UpdateQuickAnswerService.ts index e50351b..1fd326f 100644 --- a/backend/src/services/QuickAnswerService/UpdateQuickAnswerService.ts +++ b/backend/src/services/QuickAnswerService/UpdateQuickAnswerService.ts @@ -1,9 +1,11 @@ import QuickAnswer from "../../models/QuickAnswer"; import AppError from "../../errors/AppError"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; interface QuickAnswerData { shortcut?: string; message?: string; + queueIds?: number[]; } interface Request { @@ -15,7 +17,7 @@ const UpdateQuickAnswerService = async ({ quickAnswerData, quickAnswerId }: Request): Promise => { - const { shortcut, message } = quickAnswerData; + const { shortcut, message, queueIds } = quickAnswerData; const quickAnswer = await QuickAnswer.findOne({ where: { id: quickAnswerId }, @@ -30,6 +32,8 @@ const UpdateQuickAnswerService = async ({ message }); + if (queueIds) await AssociateQuickAnswerQueue(quickAnswer, queueIds); + await quickAnswer.reload({ attributes: ["id", "shortcut", "message"] }); diff --git a/backend/src/services/ReportServices/ReportByNumberQueueService.ts b/backend/src/services/ReportServices/ReportByNumberQueueService.ts new file mode 100644 index 0000000..64fd234 --- /dev/null +++ b/backend/src/services/ReportServices/ReportByNumberQueueService.ts @@ -0,0 +1,442 @@ +import { Sequelize } from "sequelize"; + +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require("sequelize"); + +import { splitDateTime } from "../../helpers/SplitDateTime"; +import format from "date-fns/format"; +import ptBR from "date-fns/locale/pt-BR"; +import Whatsapp from "../../models/Whatsapp"; +import { number } from "yup"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; + +interface Request { + startDate: string | number; + endDate: string; + queue?: boolean; + isRemote?: boolean; +} + +const ReportByNumberQueueService = async ({ + startDate, + endDate, + queue = false, + isRemote = false +}: Request): Promise => { + let reportServiceData: any[] = []; + const includeIsRemote = isRemote ? "t.isRemote = true AND" : ""; + + const whatsapps = await Whatsapp.findAll(); + + if (!queue) { + for (const whatsapp of whatsapps) { + const { id, name, number } = whatsapp; + + let startedByClient: any; + let avgChatWaitingTime: any; + let pendingChat: any; + let closedChat: any; + + if ( + !number || + reportServiceData.findIndex((w: any) => w?.number == number) != -1 + ) + continue; + + console.log("NUMBER: ", number); + + // CHAT STARTED BY AGENT + const startedByAgent: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromAgent = 1 + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + + if (!isRemote) { + // CHAT STARTED BY CLIENT + startedByClient = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + } else { + // CHAT RESPONSE BY CLIENT + startedByClient = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MAX(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT CLOSED + closedChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + } else { + // CHAT CLOSED + closedChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT WAINTING TIME + avgChatWaitingTime = await sequelize.query( + ` + SELECT TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + -- AND q.id = 2 + AND w.number = ${number} + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT PENDING + pendingChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND w.number = ${number};`, + + { type: QueryTypes.SELECT } + ); + } else { + // CHAT PENDING REMOTE + pendingChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND w.number = ${number};`, + + { type: QueryTypes.SELECT } + ); + } + + reportServiceData.push({ + id, + name, + number, + startedByAgent: startedByAgent[0]?.ticket_count, + startedByClient: startedByClient ? startedByClient[0]?.ticket_count : 0, + closedChat: closedChat[0]?.ticket_count, + avgChatWaitingTime: avgChatWaitingTime ? avg(avgChatWaitingTime) : 0, + pendingChat: pendingChat[0]?.ticket_count + }); + } + } else { + for (const whatsapp of whatsapps) { + const { id, name, number } = whatsapp; + let startedByClient: any; + let avgChatWaitingTime: any; + let pendingChat: any; + let closedChat: any; + + if ( + !number || + reportServiceData.findIndex((w: any) => w?.number == number) != -1 + ) + continue; + + const data = await ShowWhatsAppService(id); + + const queues: any = data.queues.map((q: any) => { + const { id, name, color } = q; + return { id, name, color }; + }); + + console.log("NUMBER 2: ", number); + + for (const q of queues) { + // CHAT STARTED BY AGENT + const startedByAgent: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromAgent = 1 + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + + if (!isRemote) { + // CHAT STARTED BY CLIENT + startedByClient = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + } else { + // CHAT RESPONSE BY CLIENT + startedByClient = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MAX(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT CLOSED + closedChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + } + else{ + // CHAT CLOSED REMOTE + closedChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT WAINTING TIME + avgChatWaitingTime = await sequelize.query( + `SELECT TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND q.id = ${q.id} + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + } + + if (!isRemote) { + // CHAT PENDING + pendingChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND q.id = ${q.id};`, + + { type: QueryTypes.SELECT } + ); + } else { + // CHAT PENDING REMOTE + pendingChat = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND q.id = ${q.id};`, + + { type: QueryTypes.SELECT } + ); + } + + reportServiceData.push({ + id, + name, + number, + queueName: q.name, + queueColor: q.color, + startedByAgent: startedByAgent[0]?.ticket_count, + startedByClient: startedByClient + ? startedByClient[0]?.ticket_count + : 0, + closedChat: closedChat[0]?.ticket_count, + avgChatWaitingTime: avgChatWaitingTime ? avg(avgChatWaitingTime) : 0, + pendingChat: pendingChat[0]?.ticket_count + }); + } + } + } + + return reportServiceData; +}; + +export default ReportByNumberQueueService; + +function avg(avgChatWaitingTime: any) { + let waitingAVG: any = avgChatWaitingTime + .filter((t: any) => t?.WAITING_TIME) + .map((t: any) => t.WAITING_TIME); + + if (waitingAVG.length > 0) { + let midIndex = Math.floor((0 + waitingAVG.length) / 2); + + if (waitingAVG.length % 2 == 1) { + waitingAVG = waitingAVG[midIndex]; + } else { + waitingAVG = calculateAverageTime( + waitingAVG[midIndex - 1], + waitingAVG[midIndex] + ); + } + } else { + waitingAVG = 0; + } + return waitingAVG; +} + +function calculateAverageTime(time1: string, time2: string) { + // Function to parse time string to seconds + function timeStringToSeconds(timeString: string) { + const [hours, minutes, seconds] = timeString.split(":").map(Number); + return hours * 3600 + minutes * 60 + seconds; + } + + // Function to convert seconds to time string + function secondsToTimeString(seconds: number) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + return `${hours.toString().padStart(2, "0")}:${minutes + .toString() + .padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`; + } + + // Convert time strings to seconds + const time1Seconds = timeStringToSeconds(time1); + const time2Seconds = timeStringToSeconds(time2); + + // Calculate average seconds + const averageSeconds = Math.round((time1Seconds + time2Seconds) / 2); + + // Convert average seconds back to time string + const averageTime = secondsToTimeString(averageSeconds); + + return averageTime; +} diff --git a/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts index c6f31ff..08f00c1 100644 --- a/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts +++ b/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts @@ -6,7 +6,7 @@ import SchedulingNotify from "../../models/SchedulingNotify"; interface Request { schedulingNotifyId?: string, ticketId: string, - statusChatEndId: string, + statusChatEndId: string | number, schedulingDate: string, schedulingTime: string, message: string diff --git a/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts index 046cba1..7d9d2f1 100644 --- a/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts +++ b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts @@ -3,15 +3,17 @@ import Contact from "../../models/Contact"; import SchedulingNotify from "../../models/SchedulingNotify"; import { Op, where, Sequelize } from "sequelize"; import AppError from "../../errors/AppError"; +import StatusChatEnd from "../../models/StatusChatEnd" -const ListSchedulingNotifyContactService = async (contactNumber: string = '', startDate: string='', endDate: string=''): Promise => { - - - let where_clause = {} - let where_clause_notify = {} +const ListSchedulingNotifyContactService = async ( + contactNumber: string = "", + startDate: string = "", + endDate: string = "" +): Promise => { + let where_clause = {}; + let where_clause_notify = {}; let nameNumber = { - [Op.or]: [ { name: Sequelize.where( @@ -22,126 +24,144 @@ const ListSchedulingNotifyContactService = async (contactNumber: string = '', st }, { number: { [Op.like]: `%${contactNumber.toLowerCase().trim()}%` } } ] - - } + }; - + if ( + contactNumber.trim().length > 0 && + startDate.trim().length > 0 && + endDate.trim().length > 0 + ) { + where_clause = nameNumber; - - if (contactNumber.trim().length>0 && startDate.trim().length>0 && endDate.trim().length>0){ - - where_clause = nameNumber - - where_clause_notify = { + where_clause_notify = { schedulingTime: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if ( + contactNumber.trim().length == 0 && + startDate.trim().length > 0 && + endDate.trim().length > 0 + ) { + where_clause = {}; - } - else if (contactNumber.trim().length==0 && startDate.trim().length>0 && endDate.trim().length>0){ - - where_clause = {} - - where_clause_notify = { + where_clause_notify = { schedulingDate: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if ( + contactNumber.trim().length == 0 && + startDate.trim().length > 0 && + endDate.trim().length == 0 + ) { + where_clause = {}; - } - else if (contactNumber.trim().length==0 && startDate.trim().length>0 && endDate.trim().length==0){ - - where_clause = {} - - where_clause_notify = { + where_clause_notify = { schedulingDate: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: startDate +' 23:59:59.999999' - }, - } + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: startDate + " 23:59:59.999999" + } + }; + } else if ( + contactNumber.trim().length == 0 && + startDate.trim().length == 0 && + endDate.trim().length > 0 + ) { + where_clause = {}; - } - else if (contactNumber.trim().length==0 && startDate.trim().length==0 && endDate.trim().length>0){ - - where_clause = {} - - where_clause_notify = { + where_clause_notify = { schedulingDate: { - [Op.gte]: endDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } + [Op.gte]: endDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if ( + contactNumber.trim().length > 0 && + startDate.trim().length > 0 && + endDate.trim().length == 0 + ) { + where_clause = nameNumber; - } - else if (contactNumber.trim().length>0 && startDate.trim().length>0 && endDate.trim().length==0){ - - where_clause = nameNumber - - where_clause_notify = { + where_clause_notify = { schedulingDate: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: startDate +' 23:59:59.999999' - }, - } + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: startDate + " 23:59:59.999999" + } + }; + } else if ( + contactNumber.trim().length > 0 && + startDate.trim().length == 0 && + endDate.trim().length > 0 + ) { + where_clause = nameNumber; - } - else if (contactNumber.trim().length>0 && startDate.trim().length==0 && endDate.trim().length>0){ - - where_clause = nameNumber - - where_clause_notify = { + where_clause_notify = { schedulingDate: { - [Op.gte]: endDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } - - } - else if(contactNumber.trim().length>0){ - - where_clause = nameNumber - + [Op.gte]: endDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if (contactNumber.trim().length > 0) { + where_clause = nameNumber; } - - - const ticket = await SchedulingNotify.findAll({ - - raw: true, - where: where_clause_notify, - attributes:['id', 'ticketId','statusChatEndId', [Sequelize.fn("DATE_FORMAT",Sequelize.col("schedulingDate"),"%d/%m/%Y %H:%i:%s"),"schedulingDate"], - [Sequelize.fn("DATE_FORMAT",Sequelize.col("schedulingTime"),"%d/%m/%Y %H:%i:%s"),"schedulingTime"], 'message'], + const ticket = await SchedulingNotify.findAll({ + raw: true, + where: where_clause_notify, - include: [ - { - model: Ticket, - required:true, - attributes: [], - include: [ - { - model: Contact, - where: where_clause, - attributes: ['name', 'number', 'profilePicUrl'] - }, - ] - }, - - ], + attributes: [ + "id", + "ticketId", + "statusChatEndId", + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("schedulingDate"), + "%d/%m/%Y %H:%i:%s" + ), + "schedulingDate" + ], + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("schedulingTime"), + "%d/%m/%Y %H:%i:%s" + ), + "schedulingTime" + ], + "message" + ], - order: [["id", "DESC"]] - - }); - - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - return ticket; - }; - - export default ListSchedulingNotifyContactService; - \ No newline at end of file + include: [ + { + model: Ticket, + required: true, + attributes: [], + include: [ + { + model: Contact, + where: where_clause, + attributes: ["name", "number", "profilePicUrl"] + } + ] + }, + { + model: StatusChatEnd, + required: true, + } + ], + + order: [["id", "DESC"]] + }); + + if (!ticket) { + throw new AppError("ERR_NO_TICKET_FOUND", 404); + } + + return ticket; +}; + +export default ListSchedulingNotifyContactService; diff --git a/backend/src/services/SettingServices/UpdateSettingService.ts b/backend/src/services/SettingServices/UpdateSettingService.ts index 59bf6b2..90fc3cd 100644 --- a/backend/src/services/SettingServices/UpdateSettingService.ts +++ b/backend/src/services/SettingServices/UpdateSettingService.ts @@ -4,12 +4,15 @@ import Setting from "../../models/Setting"; interface Request { key: string; value: string; + obj?: string; } const UpdateSettingService = async ({ key, - value + value, + obj }: Request): Promise => { + console.log("key: ", key, " | value: ", value, " | obj: ", obj); try { const setting = await Setting.findOne({ @@ -20,12 +23,16 @@ const UpdateSettingService = async ({ throw new AppError("ERR_NO_SETTING_FOUND", 404); } - await setting.update({ value }); + if (obj) { + obj = JSON.stringify(obj); + } + await setting.update({ value, obj }); + + await setting.reload(); return setting; - } catch (error: any) { - console.error('===> Error on UpdateSettingService.ts file: \n', error) + console.error("===> Error on UpdateSettingService.ts file: \n", error); throw new AppError(error.message); } }; diff --git a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts new file mode 100644 index 0000000..d6b28db --- /dev/null +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -0,0 +1,41 @@ +import StatusChatEnd from "../../models/StatusChatEnd"; +import AppError from "../../errors/AppError"; + +import { Sequelize } from "sequelize"; +import { splitDateTime } from "../../helpers/SplitDateTime" +import ptBR from "date-fns/locale/pt-BR"; +import { format } from "date-fns" +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require("sequelize"); + +const CountStatusChatEndService = async ( + startDate: string, + endDate: string, + queueIds?: number[] +) => { + let countStatusChatEnd: any + + if(queueIds && queueIds.length > 0){ + countStatusChatEnd = await sequelize.query( + `select t.id, s.name, count(t.id) as count from Tickets t join StatusChatEnds s on + t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.queueId IN (${queueIds}) + group by s.id;`, + { type: QueryTypes.SELECT } + ); + } + else{ + countStatusChatEnd = await sequelize.query( + `select t.id, s.name, count(t.id) as count from Tickets t join StatusChatEnds s on + t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + group by s.id;`, + { type: QueryTypes.SELECT } + ); + } + + + return countStatusChatEnd; +}; + +export default CountStatusChatEndService; diff --git a/backend/src/services/StatusChatEndService/CreateStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CreateStatusChatEndService.ts new file mode 100644 index 0000000..4995f2c --- /dev/null +++ b/backend/src/services/StatusChatEndService/CreateStatusChatEndService.ts @@ -0,0 +1,33 @@ +import AppError from "../../errors/AppError"; +import { set } from "../../helpers/RedisClient"; +import StatusChatEnd from "../../models/StatusChatEnd"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; + +interface Request { + name: string; + farewellMessage: string; + isDefault: boolean; +} + +const CreateStatusChatEndService = async ({ + name, + farewellMessage, + isDefault = false +}: Request): Promise => { + const nameExists = await StatusChatEnd.findOne({ + where: { name } + }); + + if (nameExists) { + throw new AppError("ERR_STATUS_CHAT_END_DUPLICATED"); + } + + const statusChatEnd = await StatusChatEnd.create({ name, farewellMessage, isDefault }); + + const { id } = statusChatEnd; + await set(`statusChatEnd:${id}`, { id, name, farewellMessage, isDefault }); + + return statusChatEnd; +}; + +export default CreateStatusChatEndService; diff --git a/backend/src/services/StatusChatEndService/ListStatusChatEndService.ts b/backend/src/services/StatusChatEndService/ListStatusChatEndService.ts index a995f82..b7422a8 100644 --- a/backend/src/services/StatusChatEndService/ListStatusChatEndService.ts +++ b/backend/src/services/StatusChatEndService/ListStatusChatEndService.ts @@ -25,10 +25,10 @@ interface Request { const { count, rows: statusChatEnd } = await StatusChatEnd.findAndCountAll({ where: whereCondition, - attributes: ['id', 'name'], + attributes: ["id", "name", "farewellMessage", "isDefault"], limit, offset, - order: [["id", "ASC"]] + order: [["name", "ASC"]] }); const hasMore = count > offset + statusChatEnd.length; diff --git a/backend/src/services/StatusChatEndService/ShowStatusChatEndService.ts b/backend/src/services/StatusChatEndService/ShowStatusChatEndService.ts index 085e591..94be78d 100644 --- a/backend/src/services/StatusChatEndService/ShowStatusChatEndService.ts +++ b/backend/src/services/StatusChatEndService/ShowStatusChatEndService.ts @@ -1,20 +1,45 @@ import StatusChatEnd from "../../models/StatusChatEnd"; import AppError from "../../errors/AppError"; +import { Op } from "sequelize"; -const ShowStatusChatEndService = async ( - id: string | number -): Promise => { - const status = await StatusChatEnd.findByPk(id, { - attributes: ["id", "name"] - }); +interface Request { + id?: string | number; + name?: string; + isDefault?: boolean; +} - console.log(`---------------> statusChatEnd id: ${id}`); +const ShowStatusChatEndService = async ({ + id, + name, + isDefault +}: Request): Promise => { + let statusChatEnd: any; - if (!status) { + if (id) { + statusChatEnd = await StatusChatEnd.findByPk(id, { + attributes: ["id", "name", "farewellMessage", "isDefault"] + }); + } else if (name) { + statusChatEnd = await StatusChatEnd.findOne({ + where: { name }, + attributes: ["id", "name", "farewellMessage", "isDefault"] + }); + } else if (isDefault) { + statusChatEnd = await StatusChatEnd.findOne({ + where: { isDefault }, + attributes: ["id", "name", "farewellMessage", "isDefault"] + }); + + if (!statusChatEnd) { + statusChatEnd = await StatusChatEnd.findOne(); + } + } + + if (!statusChatEnd) { throw new AppError("ERR_NO_STATUS_FOUND", 404); } - return status; + return statusChatEnd; }; export default ShowStatusChatEndService; diff --git a/backend/src/services/StatusChatEndService/UpdateStatusChatEndService.ts b/backend/src/services/StatusChatEndService/UpdateStatusChatEndService.ts new file mode 100644 index 0000000..99a2618 --- /dev/null +++ b/backend/src/services/StatusChatEndService/UpdateStatusChatEndService.ts @@ -0,0 +1,57 @@ +import QuickAnswer from "../../models/QuickAnswer"; +import AppError from "../../errors/AppError"; +import AssociateQuickAnswerQueue from "../QueueService/AssociateQuickAnswerQueue"; +import StatusChatEnd from "../../models/StatusChatEnd"; +import { set } from "../../helpers/RedisClient"; +import { update } from "../../controllers/UserController"; +import { Op, where } from "sequelize"; + +interface StatusChatEndData { + name?: string; + farewellMessage?: string; + isDefault?: boolean; +} + +interface Request { + statusChatEndData: StatusChatEndData; + statusChatEndId: string; +} + +const UpdateStatusChatEndService = async ({ + statusChatEndData, + statusChatEndId +}: Request): Promise => { + const { name, farewellMessage, isDefault } = statusChatEndData; + + if (isDefault) { + StatusChatEnd.update( + { isDefault: false }, + { where: { id: { [Op.gte]: 0 } } } + ); + } + + const statusChatEnd = await StatusChatEnd.findOne({ + where: { id: statusChatEndId }, + attributes: ["id", "name", "farewellMessage", "isDefault"] + }); + + if (!statusChatEnd) { + throw new AppError("ERR_NO_STATUS_CHAT_END_FIND", 404); + } + await statusChatEnd.update({ + name, + farewellMessage, + isDefault + }); + + await statusChatEnd.reload({ + attributes: ["id", "name", "farewellMessage", "isDefault"] + }); + + const { id } = statusChatEnd; + await set(`statusChatEnd:${id}`, { id, name, farewellMessage, isDefault }); + + return statusChatEnd; +}; + +export default UpdateStatusChatEndService; diff --git a/backend/src/services/TicketServices/CountTicketService.ts b/backend/src/services/TicketServices/CountTicketService.ts index 2cdde93..08f9de3 100644 --- a/backend/src/services/TicketServices/CountTicketService.ts +++ b/backend/src/services/TicketServices/CountTicketService.ts @@ -9,7 +9,7 @@ import ptBR from 'date-fns/locale/pt-BR'; import { splitDateTime } from "../../helpers/SplitDateTime"; const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) -const CountTicketService = async (status: string, date?: string): Promise => { +const CountTicketService = async (status: string, date?: string, queueIds?: string): Promise => { let where_clause = {} @@ -30,8 +30,8 @@ const CountTicketService = async (status: string, date?: string): Promise = // } } - - where_clause = { ...where_clause, status: status } + if(queueIds) where_clause = { ...where_clause, status: status, queueId: { [Op.or]: [queueIds, null] } }; + else where_clause = { ...where_clause, status: status}; const ticket = await Ticket.findAll({ where: where_clause, diff --git a/backend/src/services/TicketServices/FindOrCreateTicketService.ts b/backend/src/services/TicketServices/FindOrCreateTicketService.ts index 29fe820..9ab8cdd 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketService.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketService.ts @@ -14,7 +14,8 @@ const FindOrCreateTicketService = async ( whatsappId: number, unreadMessages: number, groupContact?: Contact, - queueId?: number | string + queueId?: number | string, + isRemote?: boolean ): Promise => { try { let ticket; @@ -59,10 +60,11 @@ const FindOrCreateTicketService = async ( order: [["updatedAt", "DESC"]] }); - if (ticket) { + if (ticket) { await ticket.update({ status: "pending", userId: null, + queueId: null, unreadMessages }); } @@ -95,7 +97,12 @@ const FindOrCreateTicketService = async ( if (!ticket) { let status = "pending"; - if (queues.length > 1 && !botInfo.isOnQueue && !queueId) { + if ( + queues.length > 1 && + !botInfo.isOnQueue && + !queueId && + !groupContact + ) { status = "queueChoice"; } @@ -106,7 +113,8 @@ const FindOrCreateTicketService = async ( queueId, unreadMessages, whatsappId, - phoneNumberId + phoneNumberId, + isRemote }); } diff --git a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts index 38d7f6b..42d7d53 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts @@ -6,144 +6,162 @@ import Ticket from "../../models/Ticket"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowTicketService from "./ShowTicketService"; import AppError from "../../errors/AppError"; -import { userInfo } from "os"; +import { userInfo } from "os"; import ShowQueueService from "../QueueService/ShowQueueService"; import UpdateTicketService from "./UpdateTicketService"; - +import { getSettingValue } from "../../helpers/WhaticketSettings"; +import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; const FindOrCreateTicketServiceBot = async ( - contact: Contact, - whatsappId: number, - unreadMessages: number, - groupContact?: Contact + contact: Contact, + whatsappId: number, + unreadMessages: number, + groupContact?: Contact ): Promise => { + try { + // let ticket = await Ticket.findOne({ + // where: { + // status: { + // [Op.or]: ["open", "pending", "queueChoice"] + // }, + // contactId: groupContact ? groupContact.id : contact.id + // } + // }); - try { + let ticket; - let ticket = await Ticket.findOne({ - where: { - status: { - [Op.or]: ["open", "pending", "queueChoice"] - }, - contactId: groupContact ? groupContact.id : contact.id - } + if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { + let whats = await ListWhatsAppsNumber(whatsappId); + + ticket = await Ticket.findOne({ + where: { + status: { + [Op.or]: ["open", "pending", "queueChoice"] + }, + contactId: groupContact ? groupContact.id : contact.id, + whatsappId: { [Op.in]: whats.whatsapps.map((w: any) => w.id) } + } + }); + } else { + ticket = await Ticket.findOne({ + where: { + status: { + [Op.or]: ["open", "pending", "queueChoice"] + }, + contactId: groupContact ? groupContact.id : contact.id + } + }); + } + + const { queues, greetingMessage, phoneNumberId } = + await ShowWhatsAppService(whatsappId); + + //Habilitar esse caso queira usar o bot + const botInfo = await BotIsOnQueue("botqueue"); + // const botInfo = { isOnQueue: false } + + if (ticket) { + await ticket.update({ unreadMessages }); + } + + // if (!ticket && groupContact) { + // ticket = await Ticket.findOne({ + // where: { + // contactId: groupContact.id + // }, + // order: [["updatedAt", "DESC"]] + // }); + + // if (ticket) { + + // await ticket.update({ + // status: "pending", + // userId: null, + // unreadMessages + // }); + // } + // } + + if (!ticket && !groupContact) { + console.log("BOT CREATING OR REOPENING THE TICKET"); + + ticket = await Ticket.findOne({ + where: { + contactId: contact.id, + userId: botInfo.userIdBot, + whatsappId: whatsappId + }, + order: [["updatedAt", "DESC"]] + }); + + if (ticket) { + await ticket.update({ + status: "open", + userId: botInfo.userIdBot, + unreadMessages }); - const { queues, greetingMessage, phoneNumberId } = - await ShowWhatsAppService(whatsappId); + console.log("lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - - //Habilitar esse caso queira usar o bot - const botInfo = await BotIsOnQueue('botqueue') - // const botInfo = { isOnQueue: false } - - - - if (ticket) { - await ticket.update({ unreadMessages }); - } - - // if (!ticket && groupContact) { - // ticket = await Ticket.findOne({ - // where: { - // contactId: groupContact.id - // }, - // order: [["updatedAt", "DESC"]] - // }); - - - - // if (ticket) { - - // await ticket.update({ - // status: "pending", - // userId: null, - // unreadMessages - // }); - // } - // } - - if (!ticket && !groupContact) { - - console.log('BOT CREATING OR REOPENING THE TICKET') - - ticket = await Ticket.findOne({ - where: { - contactId: contact.id, - userId: botInfo.userIdBot - }, - order: [["updatedAt", "DESC"]] - }); - - if (ticket) { - - await ticket.update({ - status: "open", - userId: botInfo.userIdBot, - unreadMessages - }); - - console.log('lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') - - await dialogFlowStartContext(contact, ticket, botInfo); - - } - } - - let created = false - - if (!ticket) { - - created = true - - let status = "open" - - if (queues.length > 1 && !botInfo.isOnQueue) { - status = "queueChoice" - } - - ticket = await Ticket.create({ - contactId: groupContact ? groupContact.id : contact.id, - status: status, - userId: botInfo.userIdBot, - isGroup: !!groupContact, - unreadMessages, - whatsappId, - phoneNumberId - }); - - console.log('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') - - await dialogFlowStartContext(contact, ticket, botInfo); - - } - - ticket = await ShowTicketService(ticket.id); - - return { ticket, created }; - - } catch (error: any) { - console.error('===> Error on FindOrCreateTicketServiceBot.ts file: \n', error) - throw new AppError(error.message); + await dialogFlowStartContext(contact, ticket, botInfo); + } } + + let created = false; + + if (!ticket) { + created = true; + + let status = "open"; + + if (queues.length > 1 && !botInfo.isOnQueue) { + status = "queueChoice"; + } + + ticket = await Ticket.create({ + contactId: groupContact ? groupContact.id : contact.id, + status: status, + userId: botInfo.userIdBot, + isGroup: !!groupContact, + unreadMessages, + whatsappId, + phoneNumberId + }); + + console.log("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); + + await dialogFlowStartContext(contact, ticket, botInfo); + } + + ticket = await ShowTicketService(ticket.id); + + return { ticket, created }; + } catch (error: any) { + console.error( + "===> Error on FindOrCreateTicketServiceBot.ts file: \n", + error + ); + throw new AppError(error.message); + } }; export default FindOrCreateTicketServiceBot; -async function dialogFlowStartContext(contact: Contact, ticket: Ticket, botInfo: any) { +async function dialogFlowStartContext( + contact: Contact, + ticket: Ticket, + botInfo: any +) { + let msg: any = { type: "chat", from: `${contact.number}@c.us`, body: "0" }; - let msg: any = { type: 'chat', from: `${contact.number}@c.us`, body: '0' }; + let queue = await ShowQueueService(botInfo.botQueueId); - let queue = await ShowQueueService(botInfo.botQueueId); + await UpdateTicketService({ + ticketData: { queueId: queue.id }, + ticketId: ticket.id + }); - await UpdateTicketService({ - ticketData: { queueId: queue.id }, - ticketId: ticket.id - }); + ticket = await ShowTicketService(ticket.id); - ticket = await ShowTicketService(ticket.id); - - // await sendDialogflowAnswer(ticket.whatsappId, ticket, msg, contact, false); - + // await sendDialogflowAnswer(ticket.whatsappId, ticket, msg, contact, false); } - diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 1ea2cc6..4a04a80 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -1,6 +1,6 @@ import { Op, fn, where, col, Filterable, Includeable } from "sequelize"; import { startOfDay, endOfDay, parseISO, format } from "date-fns"; -import ptBR from 'date-fns/locale/pt-BR'; +import ptBR from "date-fns/locale/pt-BR"; import Ticket from "../../models/Ticket"; import Contact from "../../models/Contact"; @@ -8,19 +8,17 @@ import Message from "../../models/Message"; import Queue from "../../models/Queue"; import ShowUserService from "../UserServices/ShowUserService"; -const unflatten = require('flat').unflatten +const unflatten = require("flat").unflatten; import { splitDateTime } from "../../helpers/SplitDateTime"; -const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) +const dateToday = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) +); -import ListTicketServiceCache from "./ListTicketServiceCache" - -import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache' import { getWbot } from "../../libs/wbot"; import User from "../../models/User"; - - - +import { get } from "../../helpers/RedisClient"; +import monthsAgo from "../../helpers/MonthsAgo"; interface Request { searchParam?: string; @@ -39,9 +37,9 @@ interface Response { tickets: Ticket[]; count: number; hasMore: boolean; + remoteTicketsControll?: object[]; } - const ListTicketsService = async ({ searchParam = "", pageNumber = "1", @@ -51,64 +49,70 @@ const ListTicketsService = async ({ showAll, userId, withUnreadMessages, - unlimited = 'false', + unlimited = "false", searchParamContent = "" }: Request): Promise => { + console.log( + "----------> searchParamContent: ", + searchParamContent, + " | searchParam: ", + searchParam + ); - console.log('----------> searchParamContent: ', searchParamContent) + let whereCondition: Filterable["where"] = { + [Op.or]: [{ userId }, { status: "pending" }], + queueId: { [Op.or]: [queueIds, null] } + }; - let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } }; + console.log("PAGE NUMBER TICKET: ", pageNumber); - console.log('PAGE NUMBER TICKET: ', pageNumber) - if (pageNumber.trim().length == 0) { - pageNumber = '1' + pageNumber = "1"; } + // if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) { + // try { + // const offset = 40 * (+pageNumber - 1); - if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) { + // searchParam = searchParam.replace(/\s+/g, " ").trim().toLowerCase(); - try { + // console.log("QUERY TICKET SEARCH PARAM FROM CACHE: ", searchParam); - const offset = 40 * (+pageNumber - 1); + // let tickets: any = await searchTicketCache(searchParam, offset, 40); - searchParam = searchParam.replace(/\s+/g, ' ').trim().toLowerCase(); + // if (tickets) { + // console.log( + // "QUERY TICKET SEARCH PARAM FROM CACHE LENGTH...: ", + // tickets.length + // ); - console.log('QUERY TICKET SEARCH PARAM FROM CACHE: ', searchParam) + // tickets.map((t: any) => { + // t["contact.number"] = t["contact_number"]; + // delete t["contact_number"]; - let tickets: any = await searchTicketCache(searchParam, offset, 40); - - if (tickets) { - console.log('QUERY TICKET SEARCH PARAM FROM CACHE LENGTH...: ', tickets.length) - - tickets.map((t: any) => { - - t['contact.number'] = t['contact_number'] - delete t['contact_number'] - - return { ...['contact_number'] } - }) - - tickets = tickets.map((e: any) => unflatten(e)) - - return { tickets, count: tickets.length, hasMore: tickets.length > 0 ? true : false }; - } - - } catch (error) { - console.log('There was an error on search ListTicketservice.ts search cache: ', error) - } - - console.log('QUERY TICKETS FROM DATABASE...') - - } + // return { ...["contact_number"] }; + // }); + // tickets = tickets.map((e: any) => unflatten(e)); + // return { + // tickets, + // count: tickets.length, + // hasMore: tickets.length > 0 ? true : false + // }; + // } + // } catch (error) { + // console.log( + // "There was an error on search ListTicketservice.ts search cache: ", + // error + // ); + // } + // console.log("QUERY TICKETS FROM DATABASE..."); + // } let includeCondition: Includeable[]; - - includeCondition = [ { model: Contact, @@ -127,10 +131,8 @@ const ListTicketsService = async ({ } if (status) { - whereCondition = { ...whereCondition, status }; - if (unlimited === "current" && status !== "pending") { whereCondition = { ...whereCondition, @@ -140,70 +142,123 @@ const ListTicketsService = async ({ } }; } - } if (searchParam) { const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); - const sanitizedSearchParamContent = searchParamContent.toLocaleLowerCase().trim(); - - - if (searchParamContent.length > 0) { - - includeCondition = [ - ...includeCondition, - { - model: Message, - as: "messages", - attributes: ["id", "body"], - where: { - body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) - }, - required: false, - duplicating: false - } - ]; - - whereCondition = { - ...whereCondition, - "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) - }; - - } + includeCondition = [ + ...includeCondition, + { + model: Message, + as: "messages", + attributes: ["id", "body"], + where: { + body: where( + fn("LOWER", col("body")), + "LIKE", + `%${sanitizedSearchParam}%` + ) + }, + required: false, + duplicating: false + } + ]; + whereCondition = { + ...whereCondition, + createdAt: { + [Op.gte]: monthsAgo(4) + " 00:00:00.000000", + [Op.lte]: dateToday.fullDate + " 23:59:59.999999" + } + }; whereCondition = { ...whereCondition, [Op.or]: [ { - "$contact.name$": where(fn("LOWER", col("contact.name")), "LIKE", `%${sanitizedSearchParam}%`) + "$contact.name$": where( + fn("LOWER", col("contact.name")), + "LIKE", + `%${sanitizedSearchParam}%` + ) }, - { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } }, - - ], + { + "$message.body$": where( + fn("LOWER", col("body")), + "LIKE", + `%${sanitizedSearchParam}%` + ) + } + ] }; + // const sanitizedSearchParamContent = searchParamContent + // .toLocaleLowerCase() + // .trim(); - const userProfile: any = await User.findByPk(userId) + // if (searchParamContent.length > 0) { + // includeCondition = [ + // ...includeCondition, + // { + // model: Message, + // as: "messages", + // attributes: ["id", "body"], + // where: { + // body: where( + // fn("LOWER", col("body")), + // "LIKE", + // `%${sanitizedSearchParamContent}%` + // ) + // }, + // required: false, + // duplicating: false + // } + // ]; + + // whereCondition = { + // ...whereCondition, + // "$message.body$": where( + // fn("LOWER", col("body")), + // "LIKE", + // `%${sanitizedSearchParamContent}%` + // ) + // }; + // } + + // whereCondition = { + // ...whereCondition, + // [Op.or]: [ + // { + // "$contact.name$": where( + // fn("LOWER", col("contact.name")), + // "LIKE", + // `%${sanitizedSearchParam}%` + // ) + // }, + + // { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } } + // ] + // }; + + const userProfile: any = await User.findByPk(userId); if ( userProfile.dataValues.profile != "admin" && - userProfile.dataValues.profile != "master" && + userProfile.dataValues.profile != "master" && userProfile.dataValues.profile != "supervisor" ) { whereCondition = { ...whereCondition, userId }; } - - } if (date) { whereCondition = { createdAt: { [Op.between]: [+startOfDay(parseISO(date)), +endOfDay(parseISO(date))] - } + }, + queueId: { [Op.or]: [queueIds, null] }, }; } @@ -218,12 +273,18 @@ const ListTicketsService = async ({ }; } - const limit = unlimited === "current" || unlimited === "all" ? 100000 : 40; - const offset = limit * (+pageNumber - 1); + let limit; + + if (unlimited === "current") limit = 10000; + else if (unlimited === "all") limit = 100; + else limit = 40; + + // const limit = unlimited === "current" || unlimited === "all" ? 1000 : 40; + const offset = limit * (+pageNumber - 1); console.log("kkkkkkkkk limit: ", limit); - const { count, rows: tickets } = await Ticket.findAndCountAll({ + let { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, include: includeCondition, distinct: true, @@ -234,12 +295,13 @@ const ListTicketsService = async ({ const hasMore = count > offset + tickets.length; - + const ticketIds = await get({ key: `remote:controll`, parse: true }); return { tickets, count, - hasMore + hasMore, + remoteTicketsControll: ticketIds ? ticketIds : [] }; }; diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index c60156d..a3aa41e 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -7,14 +7,18 @@ import Queue from "../../models/Queue"; import Message from "../../models/Message"; import { userInfo } from "os"; -import { Op, where } from "sequelize"; +import { Op, QueryTypes, json, where } from "sequelize"; import { Sequelize } from "sequelize"; import moment from "moment"; +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); import { startOfDay, endOfDay, parseISO, getDate } from "date-fns"; import { string } from "yup/lib/locale"; import Whatsapp from "../../models/Whatsapp"; +import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query"; +import { te } from "date-fns/locale"; interface Request { userId: string | number; @@ -40,51 +44,61 @@ const ShowTicketReport = async ({ createdOrUpdated = "created", queueId }: Request): Promise => { - let where_clause: any = {}; - // let where_clause_msg: any = {}; + // let where_clause: any = {}; + // let query = ""; - if (userId !== "0") { - where_clause.userid = userId; - } + // if (userId !== "0") { + // where_clause.userid = userId; + // query = `AND t.userId = ${userId}`; + // } - if (createdOrUpdated === "updated") { - where_clause = { - ...where_clause, - updatedAt: { - [Op.gte]: startDate + " 00:00:00.000000", - [Op.lte]: endDate + " 23:59:59.999999" - } - }; - } + // if (queueId) { + // where_clause.queueId = queueId; + // query = `AND t.queueId = ${queueId}`; + // } - if (createdOrUpdated === "created") { - where_clause = { - ...where_clause, - createdAt: { - [Op.gte]: startDate + " 00:00:00.000000", - [Op.lte]: endDate + " 23:59:59.999999" - } - }; - } + const createdAtOrUpdatedAt = + createdOrUpdated == "created" ? "createdAt" : "updatedAt"; - let { userid, ...where_clause_msg } = where_clause; + let where_clause = {}; if (queueId) { - where_clause.queueId = queueId; + where_clause = { + queueId: queueId, + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if (userId == "0") { + where_clause = { + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if (userId != "0") { + where_clause = { + userid: userId, + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; } const limit = 40; const offset = limit * (+pageNumber - 1); - const { count, rows: tickets } = await Ticket.findAndCountAll({ + let { count, rows: tickets }: any = await Ticket.findAndCountAll({ where: where_clause, limit, offset, - attributes: [ "id", "status", "statusChatEnd", + "isRemote", [ Sequelize.fn( "DATE_FORMAT", @@ -108,7 +122,6 @@ const ShowTicketReport = async ({ model: Message, required: true, separate: true, - where: where_clause_msg , attributes: [ "body", @@ -143,16 +156,66 @@ const ShowTicketReport = async ({ model: Whatsapp, attributes: ["name"] } - ], + ], order: [["updatedAt", "DESC"]] }); - + const hasMore = count > offset + tickets.length; if (!tickets) { throw new AppError("ERR_NO_TICKET_FOUND", 404); } + if (tickets.length > 0) { + const waiting_time: any = await sequelize.query( + `SELECT t.id as ticketId, t.status, TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.id IN (${tickets.map((t: any) => t.id).join()}) + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + + for (let w of waiting_time) { + const { ticketId, status, WAITING_TIME } = w; + + const index = tickets.findIndex((t: any) => +t?.id == +ticketId); + + if (index != -1) { + tickets[index].dataValues.waiting_time = WAITING_TIME; + } + } + } + return { tickets, count, hasMore }; }; diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index df0e321..8745737 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -10,16 +10,18 @@ import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import AppError from "../../errors/AppError"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import BotIsOnQueue from "../../helpers/BotIsOnQueue"; -import { deleteObject } from "../../helpers/RedisClient" +import { del, deleteObject, get, set } from "../../helpers/RedisClient"; var flatten = require("flat"); interface TicketData { status?: string; - userId?: number; + userId?: number | null; queueId?: number; statusChatEnd?: string; + statusChatEndId?: number; unreadMessages?: number; whatsappId?: string | number; + isRemote?: boolean; } interface Request { @@ -46,7 +48,9 @@ const UpdateTicketService = async ({ queueId, statusChatEnd, unreadMessages, - whatsappId + statusChatEndId, + whatsappId, + isRemote } = ticketData; const ticket = await ShowTicketService(ticketId); @@ -67,20 +71,42 @@ const UpdateTicketService = async ({ await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId); } + if (status == "closed") { + del(`remote:ticketId:${ticket.id}`); + + let ticketsIds = await get({ key: `remote:controll`, parse: true }); + + if (ticketsIds) { + const index = ticketsIds.findIndex((t: any) => t == ticket.id); + + console.log("ticketsIds 1: ", ticketsIds); + + if (index != -1) { + ticketsIds.splice(index, 1); + + console.log("ticketsIds 2: ", ticketsIds); + + set(`remote:controll`, JSON.stringify(ticketsIds)); + } + } + } + await ticket.update({ status, queueId, userId, unreadMessages, statusChatEnd, - whatsappId + statusChatEndId, + whatsappId, + isRemote }); await ticket.reload(); if (msg?.trim().length > 0) { setTimeout(async () => { - sendWhatsAppMessageSocket(ticket, msg); + sendWhatsAppMessageSocket(ticket, `\u200e${msg}`); }, 2000); } diff --git a/backend/src/services/UserServices/AuthUserService.ts b/backend/src/services/UserServices/AuthUserService.ts index b1cac22..9abf4cf 100644 --- a/backend/src/services/UserServices/AuthUserService.ts +++ b/backend/src/services/UserServices/AuthUserService.ts @@ -9,8 +9,7 @@ import Queue from "../../models/Queue"; interface SerializedUser { id: number; - name: string; - positionCompany: string; + name: string; email: string; profile: string; queues: Queue[]; diff --git a/backend/src/services/UserServices/CreateUserService.ts b/backend/src/services/UserServices/CreateUserService.ts index 5c6badb..ab2e948 100644 --- a/backend/src/services/UserServices/CreateUserService.ts +++ b/backend/src/services/UserServices/CreateUserService.ts @@ -3,49 +3,56 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import { SerializeUser } from "../../helpers/SerializeUser"; import User from "../../models/User"; +import ShowUserService from "./ShowUserService"; interface Request { email: string; password: string; - name: string; - positionCompany?: string; + name: string; + positionId?: string; queueIds?: number[]; profile?: string; + ignoreThrow?: boolean; + transferToOtherQueues?: boolean; } interface Response { email: string; - name: string; - positionCompany: string; + name: string; + positionId: string; id: number; profile: string; + transferToOtherQueues: boolean; } const CreateUserService = async ({ email, password, - name, - positionCompany, + name, + positionId, queueIds = [], - profile = "master" -}: Request): Promise => { - + profile = "master", + ignoreThrow = false, + transferToOtherQueues +}: Request): Promise => { try { - const schema = Yup.object().shape({ name: Yup.string().required().min(2), - email: Yup.string().required().trim().test( - "Check-email", - "An user with this email already exists.", - async value => { - if (!value) return false; - const emailExists = await User.findOne({ - where: { email: value } - }); - return !emailExists; - } - ), + email: Yup.string() + .required() + .trim() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value } + }); + return !emailExists; + } + ), // email: Yup.string().email().required().test( // "Check-email", @@ -65,6 +72,8 @@ const CreateUserService = async ({ try { await schema.validate({ email, password, name }); } catch (err: any) { + if (ignoreThrow) return { error: true, msg: err.message, status: 400 }; + throw new AppError(err.message); } @@ -72,9 +81,10 @@ const CreateUserService = async ({ { email, password, - name, - positionCompany, - profile + name, + positionId: !positionId ? null : positionId, + profile, + transferToOtherQueues: transferToOtherQueues? transferToOtherQueues : false }, { include: ["queues"] } ); @@ -83,15 +93,19 @@ const CreateUserService = async ({ await user.reload(); - const serializedUser = SerializeUser(user); + const _user = await ShowUserService(user.id); + + const serializedUser = SerializeUser(_user); return serializedUser; - } catch (error: any) { - console.error('===> Error on CreateUserService.ts file: \n', error) + console.error("===> Error on CreateUserService.ts file: \n", error); + + if (ignoreThrow) + return { error: true, msg: "Create user error", status: 500 }; + throw new AppError(error.message); } - }; export default CreateUserService; diff --git a/backend/src/services/UserServices/DeleteUserService.ts b/backend/src/services/UserServices/DeleteUserService.ts index 7209cf2..e1e6a2c 100644 --- a/backend/src/services/UserServices/DeleteUserService.ts +++ b/backend/src/services/UserServices/DeleteUserService.ts @@ -2,14 +2,24 @@ import User from "../../models/User"; import AppError from "../../errors/AppError"; import Ticket from "../../models/Ticket"; import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; -import { set } from "../../helpers/RedisClient" +import { set } from "../../helpers/RedisClient"; -const DeleteUserService = async (id: string | number): Promise => { +const DeleteUserService = async ( + id: string | number, + ignoreThrow = false +): Promise => { const user = await User.findOne({ where: { id } }); if (!user) { + if (ignoreThrow) + return { + error: true, + msg: `No user found with this id ${id}`, + status: 404 + }; + throw new AppError("ERR_NO_USER_FOUND", 404); } diff --git a/backend/src/services/UserServices/ListUserParamiterService.ts b/backend/src/services/UserServices/ListUserParamiterService.ts index 2d92498..c9faccf 100644 --- a/backend/src/services/UserServices/ListUserParamiterService.ts +++ b/backend/src/services/UserServices/ListUserParamiterService.ts @@ -10,6 +10,7 @@ interface Request { profiles?: Array; raw?: boolean; userIds?: string | number; + userQueues?: string[]; } const ListUser = async ({ @@ -17,10 +18,31 @@ const ListUser = async ({ userId, raw, userIds, - profiles + profiles, + userQueues: userQueuesToNumber }: Request): Promise => { let where_clause = {}; + let userIdInQueues: number[] = []; + + if(userQueuesToNumber !== undefined){ + let userQueues = userQueuesToNumber.map(id => parseInt(id)); + const userQueuesFiltered = await UserQueue.findAll({ + where: { queueId: { [Op.or]: [userQueues, null] } }, + order: [ + ['userId', 'ASC'] + ], + raw: true + }); + if(userQueuesFiltered) for(let queueId of userQueues){ + for(let userQueue of userQueuesFiltered){ + if(queueId == userQueue.queueId){ + const isAlready = userIdInQueues.indexOf(userQueue.userId); + if(isAlready === -1) userIdInQueues.push(userQueue.userId); + } + } + } + } if (userId && profile) { where_clause = { [Op.and]: [{ userId: userId }, { profile: profile }] @@ -37,16 +59,23 @@ const ListUser = async ({ where_clause = { id: { [Op.in]: userIds } }; - } else if (profiles) { + } + else if (profiles && userIdInQueues.length > 0) { where_clause = { - profile: { [Op.in]: profiles } + profile: { [Op.in]: profiles }, + id: {[Op.in]: userIdInQueues} + }; + } + else if (profiles) { + where_clause = { + profile: { [Op.in]: profiles }, }; } const users = await User.findAll({ where: where_clause, raw, - attributes: ["id", "name", "email", "positionCompany"], + attributes: ["id", "name", "email", "transferToOtherQueues"], include: [ { model: Queue, as: "queues", attributes: ["id", "name", "color"] } diff --git a/backend/src/services/UserServices/ListUsersService.ts b/backend/src/services/UserServices/ListUsersService.ts index 5f2b07a..e2fb993 100644 --- a/backend/src/services/UserServices/ListUsersService.ts +++ b/backend/src/services/UserServices/ListUsersService.ts @@ -1,6 +1,7 @@ import { Sequelize, Op } from "sequelize"; import Queue from "../../models/Queue"; import User from "../../models/User"; +import Position from "../../models/Position"; interface Request { searchParam?: string; @@ -19,15 +20,13 @@ const ListUsersService = async ({ pageNumber = "1", profile = "" }: Request): Promise => { - - let whereCondition = {} + let whereCondition = {}; if (profile.length > 0) { whereCondition = { profile: profile - } - } - else { + }; + } else { whereCondition = { [Op.or]: [ { @@ -55,20 +54,26 @@ const ListUsersService = async ({ // ] // }; - - // const limit = 20; const limit = 100; const offset = limit * (+pageNumber - 1); const { count, rows: users } = await User.findAndCountAll({ where: whereCondition, - attributes: ["name", "id", "email","positionCompany", "profile", "createdAt"], + attributes: [ + "name", + "id", + "email", + "profile", + "createdAt", + "transferToOtherQueues" + ], limit, offset, order: [["createdAt", "DESC"]], include: [ - { model: Queue, as: "queues", attributes: ["id", "name", "color"] } + { model: Queue, as: "queues", attributes: ["id", "name", "color"] }, + { model: Position, attributes: ["id", "name"] } ] }); diff --git a/backend/src/services/UserServices/ShowUserService.ts b/backend/src/services/UserServices/ShowUserService.ts index 3f2b501..d0d22ec 100644 --- a/backend/src/services/UserServices/ShowUserService.ts +++ b/backend/src/services/UserServices/ShowUserService.ts @@ -1,14 +1,24 @@ import User from "../../models/User"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; +import Position from "../../models/Position"; const ShowUserService = async (id: string | number): Promise => { const user = await User.findByPk(id, { - attributes: ["name", "id", "email", "profile", "positionCompany", "tokenVersion"], - include: [ - { model: Queue, as: "queues", attributes: ["id", "name", "color"] } + attributes: [ + "name", + "id", + "email", + "profile", + "positionId", + "tokenVersion", + "transferToOtherQueues" ], - order: [ [ { model: Queue, as: "queues"}, 'name', 'asc' ] ] + include: [ + { model: Queue, as: "queues", attributes: ["id", "name", "color"] }, + { model: Position, attributes: ["id", "name"] } + ], + order: [[{ model: Queue, as: "queues" }, "name", "asc"]] }); if (!user) { throw new AppError("ERR_NO_USER_FOUND", 404); diff --git a/backend/src/services/UserServices/UpdateUserService.ts b/backend/src/services/UserServices/UpdateUserService.ts index 8a0a4ba..3cdad6d 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -7,15 +7,17 @@ import User from "../../models/User"; interface UserData { email?: string; password?: string; - name?: string; - positionCompany?: string; + name?: string; + positionId?: string; profile?: string; queueIds?: number[]; + transferToOtherQueues?: boolean; } interface Request { userData: UserData; userId: string | number; + ignoreThrow?: boolean; } interface Response { @@ -27,41 +29,54 @@ interface Response { const UpdateUserService = async ({ userData, - userId -}: Request): Promise => { - + userId, + ignoreThrow = false +}: Request): Promise => { try { - const user = await ShowUserService(userId); const schema = Yup.object().shape({ name: Yup.string().min(2), - // email: Yup.string().min(2), + // email: Yup.string().min(2), profile: Yup.string(), password: Yup.string(), - email: Yup.string().trim().required().test( - "Check-email", - "An user with this email already exists.", - async value => { + email: Yup.string() + .trim() + .required() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; - if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value }, + raw: true, + attributes: ["email", "id"] + }); - const emailExists = await User.findOne({ where: { email: value }, raw: true, attributes: ['email', 'id'] }); + if (emailExists && user.id != emailExists?.id) { + console.error( + "The email already exists in another user profile!" + ); + return !emailExists; + } - if (emailExists && user.id != emailExists?.id) { - - console.error('The email already exists in another user profile!') - return !emailExists; + return true; } - - return true - } - ), - + ) }); - const { email, password, profile, name, positionCompany, queueIds = [] } = userData; + const { + email, + password, + profile, + name, + positionId, + queueIds = [], + transferToOtherQueues + } = userData; try { await schema.validate({ email, password, profile, name }); @@ -69,35 +84,56 @@ const UpdateUserService = async ({ throw new AppError(err.message); } - await user.update({ email, password, - profile, - positionCompany, - name + profile, + positionId: !positionId ? null : positionId, + name, + transferToOtherQueues }); await user.$set("queues", queueIds); await user.reload(); + // const serializedUser = { + // id: user.id, + // name: user.name, + // email: user.email, + // profile: user.profile, + // queues: user.queues, + // positionId: user?.positionId + // }; + + // return serializedUser; + + const _user = await ShowUserService(user.id); + const serializedUser = { - id: user.id, - name: user.name, - email: user.email, - profile: user.profile, - queues: user.queues + id: _user.id, + name: _user.name, + email: _user.email, + profile: _user.profile, + queues: _user.queues, + positionId: _user?.positionId, + position: _user.position, + transferToOtherQueues: _user.transferToOtherQueues }; return serializedUser; + } catch (err: any) { + console.error("===> Error on UpdateUserService.ts file: \n", err); - } catch (error: any) { - console.error('===> Error on UpdateUserService.ts file: \n', error) - throw new AppError(error.message); + if (ignoreThrow) + return { + error: true, + msg: err.message, + status: 500 + }; + + throw new AppError(err.message); } - - }; export default UpdateUserService; diff --git a/backend/src/services/WbotServices/CheckIsValidContact.ts b/backend/src/services/WbotServices/CheckIsValidContact.ts index d663206..cf9afec 100644 --- a/backend/src/services/WbotServices/CheckIsValidContact.ts +++ b/backend/src/services/WbotServices/CheckIsValidContact.ts @@ -29,7 +29,8 @@ const CheckIsValidContact = async ( try { let _status: any; - if (!isValidNumber) { + if (!isValidNumber) { + const { data, status } = await axios.post( `${process.env.WHATS_NUMBER_VALIDATOR_URL}/api/validate`, { mobile: number }, @@ -44,6 +45,8 @@ const CheckIsValidContact = async ( _status = status; } if (ignoreThrow) return isValidNumber?.number; + + console.log('_status: ', _status) if (_status && _status == 422) throw new AppError("ERR_NO_WAPP_FOUND"); diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index d838b00..53b50e2 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -10,6 +10,7 @@ import path from "path"; import { isHoliday, isOutBusinessTime, + isOutBusinessTimeSaturday, isWeekend } from "../../helpers/TicketConfig"; @@ -45,7 +46,7 @@ import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketServi import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import { debounce } from "../../helpers/Debounce"; import UpdateTicketService from "../TicketServices/UpdateTicketService"; -import { date } from "faker"; +import { date, name } from "faker"; import ShowQueueService from "../QueueService/ShowQueueService"; import ShowTicketMessage from "../TicketServices/ShowTicketMessage"; @@ -92,13 +93,18 @@ import { createObject, findByContain, findObject, - get + get, + getSimple, + set } from "../../helpers/RedisClient"; import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"; import ShowTicketService from "../TicketServices/ShowTicketService"; import ShowQueuesByUser from "../UserServices/ShowQueuesByUser"; import ListWhatsappQueuesByUserQueue from "../UserServices/ListWhatsappQueuesByUserQueue"; import CreateContactService from "../ContactServices/CreateContactService"; +import { number } from "yup"; +import AssociateContatctQueue from "../ContactServices/AssociateContatctQueue"; +import ContactQueue from "../../models/ContactQueues"; var lst: any[] = getWhatsappIds(); @@ -164,7 +170,7 @@ const verifyMediaMessage = async ( if (!media) { throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); } - + let mediaAuthorized = true; let messageData = { id: msg.id.id, ticketId: ticket.id, @@ -175,8 +181,17 @@ const verifyMediaMessage = async ( mediaUrl: media.filename, mediaType: media.mimetype.split("/")[0], quotedMsgId: quotedMsg, - phoneNumberId: msg?.phoneNumberId + phoneNumberId: msg?.phoneNumberId, + fromAgent: false }; + if(getSettingValue('blockAudioVideoMedia')?.value === 'enabled'){ + if( messageData.mediaType === 'video' || messageData.mediaType === 'audio' ){ + mediaAuthorized = false; + } + } + if (msg?.fromMe) { + messageData = { ...messageData, fromAgent: true }; + } if (!ticket?.phoneNumberId) { if (!media.filename) { @@ -188,23 +203,49 @@ const verifyMediaMessage = async ( body: media.filename }; } - - try { - await writeFileAsync( - join(__dirname, "..", "..", "..", "..", "..", "public", media.filename), - media.data, - "base64" - ); - } catch (err) { - Sentry.captureException(err); - logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); + if (mediaAuthorized) { + try { + await writeFileAsync( + join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "public", + media.filename + ), + media.data, + "base64" + ); + } catch (err) { + Sentry.captureException(err); + logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); + } } } - - await ticket.update({ lastMessage: msg.body || media.filename }); - const newMessage = await CreateMessageService({ messageData }); - - return newMessage; + if (mediaAuthorized) { + await ticket.update({ lastMessage: msg.body || media.filename }); + const newMessage = await CreateMessageService({ messageData }); + return newMessage; + } else { + if (ticket.status !== "queueChoice") { + botSendMessage( + ticket, + `Atenção! Mensagem ignorada, tipo de mídia não suportado.` + ); + } + messageData.body = `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`; + messageData.mediaUrl = ""; + await ticket.update({ + lastMessage: `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.` + }); + const newMessage = await CreateMessageService({ messageData }); + console.log( + `--------->>> Mensagem do tipo: ${messageData.mediaType}, ignorada!` + ); + } }; // const verifyMediaMessage = async ( @@ -280,20 +321,46 @@ const verifyMessage = async ( contact: Contact, quotedMsg?: any ) => { - const messageData = { + let messageData = { id: msg.id.id, ticketId: ticket.id, contactId: msg.fromMe ? undefined : contact.id, body: msg.body, fromMe: msg.fromMe, + fromAgent: false, mediaType: msg.type, read: msg.fromMe, quotedMsgId: quotedMsg, phoneNumberId: msg?.phoneNumberId }; + if (msg?.fromMe) { + const botInfo = await BotIsOnQueue("botqueue"); + + if (botInfo.isOnQueue) { + let whatsapp = await whatsappCache(ticket.whatsappId); + + const ura: any = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + }); + + if (ura && !ura.includes(JSON.stringify(msg?.body))) { + messageData = { ...messageData, fromAgent: true }; + } + } else if (msg?.body?.trim().length > 0 && !/\u200e/.test(msg.body[0])) { + messageData = { ...messageData, fromAgent: true }; + } + } await ticket.update({ lastMessage: msg.body }); + if (!msg?.fromMe && msg?.vCards && msg?.vCards?.length > 0) { + if (msg.vCards.length == 1) { + messageData = { ...messageData, body: msg.vCards[0] }; + } else { + messageData = { ...messageData, body: JSON.stringify(msg.vCards) }; + } + } + await CreateMessageService({ messageData }); }; @@ -303,6 +370,12 @@ const verifyQueue = async ( ticket: Ticket, contact: Contact ) => { + + if(ticket?.isGroup){ + + return + } + const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); let selectedOption = null; @@ -322,9 +395,10 @@ const verifyQueue = async ( if (selectedOption && selectedOption.trim().length > 0) { //////////////// EXTRAIR APENAS O NÚMERO /////////////////// - selectedOption = selectedOption.replace(/[^1-9]/g, ""); + selectedOption = selectedOption.match(/\d+/); /////////////////////////////////// - choosenQueue = queues[+selectedOption - 1]; + + if (selectedOption) choosenQueue = queues[+selectedOption - 1]; } } @@ -349,13 +423,19 @@ const verifyQueue = async ( ticketId: ticket.id }); - const data = await get("ura"); + const whatsapp = await whatsappCache(ticket.whatsappId); + + const data = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + parse: true + }); await createObject({ whatsappId: `${ticket.whatsappId}`, contactId: `${ticket.contactId}`, - identifier: "ura", - value: data[1].id + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: data[1].id, + history: `|${data[1].id}` }); botSendMessage(ticket, data[1].value); @@ -363,6 +443,8 @@ const verifyQueue = async ( } // + await assingContactByQueue(ticket, choosenQueue.id); + let whatsapp: any = await whatsappInfo(ticket?.whatsappId); const outService = await outOfService(whatsapp?.number); @@ -370,24 +452,26 @@ const verifyQueue = async ( if (outService.length > 0) { const { type, msg: msgOutService } = outService[0]; console.log(`${type} message ignored on queue`); - botSendMessage(ticket, msgOutService); + botSendMessage(ticket, `\u200e${msgOutService}`); return; } let body = ""; - + const io = getIO(); if (botOptions.length > 0) { body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`; } else { body = `\u200e${choosenQueue.greetingMessage}`; } + io.emit("notifyPeding", { data: { ticket, queue: choosenQueue } }); + sendWhatsAppMessageSocket(ticket, body); } else { //test del transfere o atendimento se entrar na ura infinita const repet: any = await mostRepeatedPhrase(ticket.id); - if (repet.occurrences > 4) { + if (repet.occurrences > 5) { await UpdateTicketService({ ticketData: { status: "pending", queueId: queues[0].id }, ticketId: ticket.id @@ -481,7 +565,7 @@ const isValidMsg = (msg: any): boolean => { msg.type === "image" || msg.type === "document" || msg.type === "vcard" || - // msg.type === "multi_vcard" || + msg.type === "multi_vcard" || msg.type === "sticker" ) return true; @@ -507,8 +591,7 @@ const transferTicket = async ( sendGreetingMessage?: boolean ) => { const botInfo = await BotIsOnQueue("botqueue"); - - console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName); + const io = getIO(); const queuesWhatsGreetingMessage = await queuesOutBot( wbot, @@ -527,7 +610,12 @@ const transferTicket = async ( queue = queues[queueName]; } - if (queue) await botTransferTicket(queue, ticket, sendGreetingMessage); + if (queue) { + await assingContactByQueue(ticket, queue.id); + + await botTransferTicket(queue, ticket, sendGreetingMessage); + } + io.emit("notifyPeding", { data: { ticket, queue } }); }; const botTransferTicket = async ( @@ -619,29 +707,12 @@ const handleMessage = async ( try { let msgContact: any = wbot.msgContact; - // let groupContact: Contact | undefined; + let groupContact: Contact | undefined; if (msg.fromMe) { - const whatsapp = await whatsappInfo(wbot.id); - - if (whatsapp?.number) { - const ticketExpiration = await SettingTicket.findOne({ - where: { key: "ticketExpiration", number: whatsapp.number } - }); - - if ( - ticketExpiration && - ticketExpiration.value == "enabled" && - ticketExpiration?.message.trim() == msg.body.trim() - ) { - console.log("*********** TICKET EXPIRATION"); - return; - } - } - // messages sent automatically by wbot have a special character in front of it // if so, this message was already been stored in database; - // if (/\u200e/.test(msg.body[0])) return; + if (/\u200e/.test(msg.body[0])) return; // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" @@ -676,18 +747,21 @@ const handleMessage = async ( // console.log('----------> chat: ', JSON.parse(JSON.stringify(chat))) - // if (chat.isGroup) { - // let msgGroupContact; - // if (msg.fromMe) { - // msgGroupContact = await wbot.getContactById(msg.to); - // } else { - // msgGroupContact = await wbot.getContactById(msg.from); - // } + console - // groupContact = await verifyContact(msgGroupContact); - // } + if (chat.isGroup) { + // let msgGroupContact; + // if (msg.fromMe) { + // msgGroupContact = await wbot.getContactById(msg.to); + // } else { + // msgGroupContact = await wbot.getContactById(msg.from); + // } + + groupContact = await verifyContact(wbot.getContactById); + } + const whatsapp = await ShowWhatsAppService(wbot.id!); const unreadMessages = msg.fromMe ? 0 : chat.unreadCount; @@ -710,12 +784,12 @@ const handleMessage = async ( const _botInfo = await BotIsOnQueue("botqueue"); - if (_botInfo.isOnQueue) { + if (_botInfo.isOnQueue && !chat.isGroup) { let ticket_obj: any = await FindOrCreateTicketServiceBot( contact, wbot.id!, - unreadMessages - // groupContact + unreadMessages, + groupContact ); ticket = ticket_obj.ticket; @@ -734,17 +808,14 @@ const handleMessage = async ( ticket = await FindOrCreateTicketService( contact, wbot.id!, - unreadMessages - // groupContact + unreadMessages, + groupContact ); } if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") { // Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen if (wbot.id != ticket.whatsappId) { - // console.log('PARA RESPONDER PELO MEMOS WHATSAPP wbot.id: ', wbot.id, ' | wbot.status: ', wbot.status) - // console.log('WHATSAPP STATUS ticket.whatsappId: ', ticket.whatsappId) - try { await ticket.update({ whatsappId: wbot.id }); } catch (error: any) { @@ -781,32 +852,8 @@ const handleMessage = async ( await verifyQueue(wbot, msg, ticket, contact); } - if (msg.type === "vcard") { - try { - const array = msg.body.split("\n"); - const obj = []; - let contact = ""; - for (let index = 0; index < array.length; index++) { - const v = array[index]; - const values = v.split(":"); - for (let ind = 0; ind < values.length; ind++) { - if (values[ind].indexOf("+") !== -1) { - obj.push({ number: values[ind] }); - } - if (values[ind].indexOf("FN") !== -1) { - contact = values[ind + 1]; - } - } - } - for await (const ob of obj) { - const cont = await CreateContactService({ - name: contact, - number: ob.number.replace(/\D/g, "") - }); - } - } catch (error) { - console.log(error); - } + if (msg.type === "vcard" || msg.type === "multi_vcard") { + await vcard(msg); } const botInfo = await BotIsOnQueue("botqueue"); @@ -864,7 +911,7 @@ const handleMessage = async ( console.log("repet.occurrences: ", repet.occurrences); - if (repet.occurrences > 4) { + if (repet.occurrences > 5) { await transferTicket(0, wbot, ticket); await SendWhatsAppMessage({ @@ -912,6 +959,29 @@ const handleMessage = async ( msg.body == "0" && ticket.status == "pending" && ticket.queueId + ) { + if (botInfo.isOnQueue) { + let choosenQueue = await ShowQueueService(botInfo.botQueueId); + + await UpdateTicketService({ + ticketData: { + status: "open", + userId: botInfo.userIdBot, + queueId: choosenQueue.id + }, + ticketId: ticket.id + }); + const menuMsg: any = await menu(msg.body, wbot.id, contact.id); + await botSendMessage(ticket, menuMsg.value); + } + + return; + } else if ( + !msg.fromMe && + msg.body == "#" && + ticket.status == "pending" && + ticket.queueId && + botInfo.isOnQueue ) { let choosenQueue = await ShowQueueService(botInfo.botQueueId); @@ -923,10 +993,12 @@ const handleMessage = async ( }, ticketId: ticket.id }); + const menuMsg: any = await menu(msg.body, wbot.id, contact.id); + await botSendMessage(ticket, menuMsg.value); - return; + // return; } if (msg && !msg.fromMe && ticket.status == "pending") { @@ -952,7 +1024,7 @@ const handleMessage = async ( return; } - botSendMessage(ticket, msgOutService); + botSendMessage(ticket, `\u200e${msgOutService}`); return; } } @@ -964,8 +1036,17 @@ const handleMessage = async ( }; const menu = async (userTyped: string, whatsappId: any, contactId: any) => { - let lastId = await findObject(whatsappId, contactId, "ura"); - const data: any = await get("ura"); + let whatsapp = await whatsappCache(whatsappId); + + let lastId = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); + const data: any = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + parse: true + }); console.log("lastId[0]: ", lastId[0]); @@ -973,18 +1054,23 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", - value: data[1].id + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: data[1].id, + history: `|${data[1].id}` }); } - lastId = await findObject(whatsappId, contactId, "ura"); + lastId = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); console.log("LAST ID: ", lastId); let option: any; if ( lastId && - lastId.length == 4 && + (lastId.length == 4 || lastId.length == 5) && lastId[3] && lastId[3].trim().length > 0 ) { @@ -994,23 +1080,48 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { o.value.toLowerCase() == userTyped.toLowerCase() ); - // TEST DEL - console.log("OPTION: ", option); - - if (!option && userTyped != "0") { + if (!option && userTyped != "0" && userTyped != "#") { if (!existSubMenu()) { const response = await mainOptionsMenu(userTyped); if (response) return response; else { - console.log("kkkkkkkkkkkkkkkkkkk"); - await createObject({ + let uraOptionSelected = await findObject( whatsappId, contactId, - identifier: "ura", - value: data[1].id - }); + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); - return data[1]; + uraOptionSelected = uraOptionSelected[4].split("|"); + + if (uraOptionSelected.length == 1) { + await createObject({ + whatsappId, + contactId, + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: data[1].id, + history: `|${data[1].id}` + }); + + return data[1]; + } else if (uraOptionSelected.length > 1) { + const id = uraOptionSelected[uraOptionSelected.length - 1]; + + console.log(" ID FROM THE MENU/SUBMENU: ", id); + + const history = await historyUra(whatsappId, contactId, id); + + await createObject({ + whatsappId, + contactId, + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: id, + history + }); + + let response: any = data.find((o: any) => o.id == id); + + return response; + } } } } @@ -1018,19 +1129,16 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { if (option) { let response: any = data.find((o: any) => o.idmaster == option.id); + // console.log(" RESPONSE OPTION: ", response, " | OPTION: ", option); - console.log( - "RRRRRRRRRRRRRRRRRRRRRRRRRRRRR response: ", - response, - " | option: ", - option - ); + let history: any = await historyUra(whatsappId, contactId, response.id); await createObject({ whatsappId, contactId, - identifier: "ura", - value: response.id + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: response.id, + history }); return response; @@ -1038,26 +1146,14 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", - value: data[1].id + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: data[1].id, + history: `|${data[1].id}` }); return data[1]; - } else { - console.log("INVALID SEARCH"); - - let response = await existSubMenu(); - if (response) return response; - - return { - value: data.find((o: any) => o.id == lastId[3])?.value - }; - - // return { - // value: `Você digitou uma opçao inválida!\n\n${ - // data.find((o: any) => o.id == lastId[3])?.value - // }\n\nDigite 0 para voltar ao menu ` - // }; + } else if (userTyped == "#") { + return await backUra(whatsappId, contactId, data); } } @@ -1070,18 +1166,33 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { } async function mainOptionsMenu(userTyped: any) { - let menuOption = data.find( - (o: any) => o.value.toLowerCase() == userTyped.toLowerCase() + let currentMenu = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" ); - console.log("============> menuOption OPTION: ", menuOption); + + const menuValues = data + .filter((m: any) => m.idmaster == currentMenu[3]) + .map((m: any) => m.value); + + let menuOption = data.find( + (o: any) => + o.value.toLowerCase() == userTyped.toLowerCase() && + menuValues.includes(userTyped.toLowerCase()) + ); + if (menuOption) { let response = data.find((o: any) => o.idmaster == menuOption.id); if (response) { + let history = await historyUra(whatsappId, contactId, response.id); + await createObject({ whatsappId, contactId, - identifier: "ura", - value: response.id + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: response.id, + history }); return response; @@ -1161,6 +1272,13 @@ const outOfService = async (number: string) => { objs.push({ type: "holiday", msg: holiday.msg }); } + // MESSAGE TO SATURDAY BUSINESS TIME + const businessTimeSaturday = await isOutBusinessTimeSaturday(number); + + if (businessTimeSaturday && businessTimeSaturday.set) { + objs.push({ type: "saturdayBusinessTime", msg: businessTimeSaturday.msg }); + } + // MESSAGES TO SATURDAY OR SUNDAY const weekend: any = await isWeekend(number); @@ -1190,6 +1308,153 @@ export { mediaTypeWhatsappOfficial, botSendMessage }; +async function assingContactByQueue(ticket: Ticket, queueId: any) { + if (getSettingValue("contactByqueues")?.value == "enabled") { + const contact = await Contact.findByPk(ticket.contact.id); + + if (contact) { + let queueIds = await ContactQueue.findAll({ + where: { contactId: contact.id } + }); + queueIds = queueIds.map((q: any) => q.queueId); + + await AssociateContatctQueue(contact, [...queueIds, queueId]); + } + } +} + +async function vcard(msg: any) { + let array: any[] = []; + let contact: any; + let obj: any[] = []; + + try { + const multi_vcard = msg?.vCards?.length === 0 ? false : true; + + if (multi_vcard) { + array = msg?.vCards; + contact = []; + } else { + array = msg.body.split("\n"); + contact = ""; + } + + for (let index = 0; index < array.length; index++) { + const v = array[index]; + const values = v.split(":"); + for (let ind = 0; ind < values.length; ind++) { + if (values[ind].indexOf("+") !== -1) { + obj.push({ number: values[ind] }); + } + if (values[ind].indexOf("FN") !== -1) { + if (multi_vcard) + contact.push({ name: values[ind + 1].split("\n")[0] }); + else contact = values[ind + 1]; + } + } + } + + for (const i in obj) { + let data: any = {}; + + if (multi_vcard) { + data = { + name: contact[i].name, + number: obj[i].number.replace(/\D/g, "") + }; + } else { + data = { + name: contact, + number: obj[i].number.replace(/\D/g, "") + }; + } + + const cont = await CreateContactService(data); + } + } catch (error) { + console.log(error); + } +} + +async function backUra(whatsappId: any, contactId: any, data: any) { + let whatsapp = await whatsappCache(whatsappId); + + let uraOptionSelected = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); + + uraOptionSelected = uraOptionSelected[4].split("|").filter(Boolean); + + let id = uraOptionSelected[0]; + + let history = `|${uraOptionSelected[0]}`; + + if (uraOptionSelected.length > 1) { + const idRemove = uraOptionSelected[uraOptionSelected.length - 1]; + + history = await historyUra(whatsappId, contactId, idRemove, true); + + const lstIds = history.split("|").filter(Boolean); + + id = lstIds[lstIds.length - 1]; + } + + await createObject({ + whatsappId, + contactId, + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: id, + history + }); + + let response: any = data.find((o: any) => o.id == id); + + return response; +} + +async function historyUra( + whatsappId: any, + contactId: any, + id: any, + remove?: boolean +) { + let whatsapp = await whatsappCache(whatsappId); + + let uraOptionSelected = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); + let history = ""; + + console.log("SELECED OPTION uraOptionSelected: ", uraOptionSelected); + + if (remove) { + return uraOptionSelected[4]?.replace(`|${id}`, ""); + } + + if (uraOptionSelected && uraOptionSelected.length == 5) { + if (!uraOptionSelected[4]?.includes(`${id}`)) + history += `${uraOptionSelected[4]}|${id}`; + else history = `${uraOptionSelected[4]}`; + } else { + history = `|${id}`; + } + return history; +} + async function whatsappInfo(whatsappId: string | number) { return await Whatsapp.findByPk(whatsappId); } +async function whatsappCache(whatsappId: any) { + let whatsapp = await get({ key: "whatsapp:*", parse: true }); + + let uraByNumber = await get({ key: "ura_*", parse: true }); + // console.log("------------------------> uraByNumber: ", uraByNumber); + + whatsapp = whatsapp.filter((w: any) => +w?.id == +whatsappId); + + if (whatsapp && whatsapp.length > 0 && uraByNumber) return whatsapp[0]; +} diff --git a/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts b/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts index 1f16647..63d6c91 100644 --- a/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts +++ b/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts @@ -6,17 +6,30 @@ const { QueryTypes } = require("sequelize"); const sequelize = new Sequelize(dbConfig); -const ListWhatsAppsForQueueService = async (queueId: number | string): Promise => { - const distinctWhatsapps = await sequelize.query( - `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId -FROM Whatsapps w -JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} -GROUP BY w.number;`, - { type: QueryTypes.SELECT } - ); +const ListWhatsAppsForQueueService = async ( + queueId: number | string, + status?: string +): Promise => { + let distinctWhatsapps: any[] = []; + + if (status) { + distinctWhatsapps = await sequelize.query( + `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId FROM Whatsapps w + JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} AND w.status = '${status}' + AND phoneNumberId = false + GROUP BY w.number;`, + { type: QueryTypes.SELECT } + ); + } else { + distinctWhatsapps = await sequelize.query( + `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId FROM Whatsapps w + JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} + GROUP BY w.number;`, + { type: QueryTypes.SELECT } + ); + } return distinctWhatsapps; }; export default ListWhatsAppsForQueueService; - \ No newline at end of file diff --git a/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts b/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts index 8bb9758..928b8d5 100644 --- a/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts +++ b/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts @@ -9,12 +9,12 @@ const ListWhatsAppsNumber = async ( let whatsapps: any = []; if (whatsapp) { - if (status) { + if (status) { whatsapps = await Whatsapp.findAll({ raw: true, where: { number: whatsapp.number, status: status, }, attributes: ["id", "number", "status", "isDefault", "url", 'isOfficial'] - }); + }); } else { whatsapps = await Whatsapp.findAll({ raw: true, diff --git a/frontend/src/components/ChatEnd/ModalChatEnd/index.js b/frontend/src/components/ChatEnd/ModalChatEnd/index.js index e64916d..b92f227 100644 --- a/frontend/src/components/ChatEnd/ModalChatEnd/index.js +++ b/frontend/src/components/ChatEnd/ModalChatEnd/index.js @@ -1,67 +1,67 @@ -import React, { useState, useEffect, useRef, useReducer } from 'react'; -import Button from '@mui/material/Button'; -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import DialogTitle from '@mui/material/DialogTitle'; -import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; -import SelectField from "../../Report/SelectField"; +import React, { useState, useEffect, useRef, useReducer } from 'react' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogContentText from '@mui/material/DialogContentText' +import DialogTitle from '@mui/material/DialogTitle' +import PropTypes from 'prop-types' +import Box from '@mui/material/Box' +import SelectField from "../../Report/SelectField" import DatePicker from '../../Report/DatePicker' import TimerPickerSelect from '../TimerPickerSelect' -import TextareaAutosize from '@mui/material/TextareaAutosize'; +import TextareaAutosize from '@mui/material/TextareaAutosize' -import { subHours, addDays, subDays } from "date-fns"; -import TextFieldSelectHourBefore from '@mui/material/TextField'; -import MenuItem from '@mui/material/MenuItem'; +import { subHours, addDays, subDays } from "date-fns" +import TextFieldSelectHourBefore from '@mui/material/TextField' +import MenuItem from '@mui/material/MenuItem' -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from '@mui/material/Checkbox' +import FormControlLabel from "@mui/material/FormControlLabel" -import api from "../../../services/api"; -import toastError from "../../../errors/toastError"; +import api from "../../../services/api" +import toastError from "../../../errors/toastError" const reducer = (state, action) => { if (action.type === "LOAD_SCHEDULES") { - const schedulesContact = action.payload; - const newSchedules = []; + const schedulesContact = action.payload + const newSchedules = [] schedulesContact.forEach((schedule) => { - const scheduleIndex = state.findIndex((s) => s.id === schedule.id); + const scheduleIndex = state.findIndex((s) => s.id === schedule.id) if (scheduleIndex !== -1) { - state[scheduleIndex] = schedule; + state[scheduleIndex] = schedule } else { - newSchedules.push(schedule); + newSchedules.push(schedule) } - }); + }) - return [...state, ...newSchedules]; + return [...state, ...newSchedules] } if (action.type === "DELETE_SCHEDULE") { - const scheduleId = action.payload; - const scheduleIndex = state.findIndex((q) => q.id === scheduleId); + const scheduleId = action.payload + const scheduleIndex = state.findIndex((q) => q.id === scheduleId) if (scheduleIndex !== -1) { - state.splice(scheduleIndex, 1); + state.splice(scheduleIndex, 1) } - return [...state]; + return [...state] } if (action.type === "RESET") { - return []; + return [] } -}; +} const Item = (props) => { - const { sx, ...other } = props; + const { sx, ...other } = props return ( { }} {...other} /> - ); + ) } Item.propTypes = { @@ -90,7 +90,7 @@ Item.propTypes = { PropTypes.func, PropTypes.object, ]), -}; +} @@ -98,27 +98,24 @@ const Modal = (props) => { // const [clientSchedules, dispatch] = useReducer(reducer, []); // const [selectedSchedule, setSelectedSchedule] = useState(null); - const [open, setOpen] = useState(true); - const [scroll, /*setScroll*/] = useState('body'); - const [statusChatEndId, setStatusChatEnd] = useState(null) + const [open, setOpen] = useState(true) + const [scroll, /*setScroll*/] = useState('body') + const [statusChatEndName, setStatusChatEnd] = useState(null) const [startDate, setDatePicker] = useState(new Date()) const [timerPicker, setTimerPicker] = useState(new Date()) const [textArea1, setTextArea1] = useState() - const [schedulesContact, dispatch] = useReducer(reducer, []); - - const [currencyHourBefore, setCurrency] = useState(null); - const [currenciesTimeBefore, setCurrenciesTimeBefore] = useState(null); - - const [checked, setChecked] = useState(false); - + const [schedulesContact, dispatch] = useReducer(reducer, []) + const [currencyHourBefore, setCurrency] = useState(null) + const [currenciesTimeBefore, setCurrenciesTimeBefore] = useState(null) + const [checked, setChecked] = useState(false) const handleCancel = (event, reason) => { if (reason && reason === "backdropClick") - return; + return - setOpen(false); - }; + setOpen(false) + } useEffect(() => { @@ -126,15 +123,14 @@ const Modal = (props) => { (async () => { try { - const { data } = await api.get("/tickets/" + props.ticketId); - - dispatch({ type: "LOAD_SCHEDULES", payload: data.schedulesContact }); + const { data } = await api.get("/tickets/" + props.ticketId) + dispatch({ type: "LOAD_SCHEDULES", payload: data.schedulesContact }) } catch (err) { - toastError(err); + toastError(err) } - })(); - }, [props]); + })() + }, [props]) function formatedTimeHour(timer) { @@ -143,15 +139,13 @@ const Modal = (props) => { function formatedFullCurrentDate() { let dateCurrent = new Date() - let day = dateCurrent.getDate().toString().padStart(2, '0'); - let month = (dateCurrent.getMonth() + 1).toString().padStart(2, '0'); - let year = dateCurrent.getFullYear(); - return `${year}-${month}-${day}`; + let day = dateCurrent.getDate().toString().padStart(2, '0') + let month = (dateCurrent.getMonth() + 1).toString().padStart(2, '0') + let year = dateCurrent.getFullYear() + return `${year}-${month}-${day}` } - - // const handleDeleteSchedule = async (scheduleId) => { // try { // await api.delete(`/schedule/${scheduleId}`); @@ -165,7 +159,7 @@ const Modal = (props) => { // Get from child 2 const datePickerValue = (data) => { - + setDatePicker(data) @@ -173,7 +167,7 @@ const Modal = (props) => { // Get from child 3 const timerPickerValue = (data) => { - + setTimerPicker(data) @@ -187,12 +181,12 @@ const Modal = (props) => { date = new Date(dateF) } else { - date = new Date(); + date = new Date() } - let day = date.getDate().toString().padStart(2, '0'); - let month = (date.getMonth() + 1).toString().padStart(2, '0'); - let year = date.getFullYear(); + let day = date.getDate().toString().padStart(2, '0') + let month = (date.getMonth() + 1).toString().padStart(2, '0') + let year = date.getFullYear() return `${year}-${month}-${day}` @@ -200,14 +194,14 @@ const Modal = (props) => { const handleChatEnd = (event, reason) => { - let dataSendServer = { 'statusChatEndId': statusChatEndId, 'farewellMessage':checked } + let dataSendServer = { 'statusChatEndName': statusChatEndName, 'farewellMessage': checked } if (reason && reason === "backdropClick") - return; + return + + if (statusChatEndName === 'LEMBRETE' || statusChatEndName === "AGENDAMENTO À CONFIRMAR") { - if (statusChatEndId === '2' || statusChatEndId === '3') { - if (startDate.trim().length === 0) { @@ -243,7 +237,7 @@ const Modal = (props) => { let dateSendMessage = startDate let timeBefore = formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`)) - if (statusChatEndId === '3') { + if (statusChatEndName === "AGENDAMENTO À CONFIRMAR") { if (!currencyHourBefore) { alert('Para agendamentos do dia corrente, essa funcionalidade atende a agendeamentos com no mínimo 2 horas adiantado a partir da hora atual!') @@ -252,26 +246,26 @@ const Modal = (props) => { timeBefore = currencyHourBefore - let sendMessageDayBefore = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0); + let sendMessageDayBefore = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0) if (sendMessageDayBefore.length > 0 && timeBefore === formatedTimeHour(timerPicker)) { - - + + dateSendMessage = dateCurrentFormated(new Date(subDays(new Date(startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`))), 1))) } - - } else if (statusChatEndId === '2') { - + } else if (statusChatEndName === 'LEMBRETE') { + + } dataSendServer = { - 'statusChatEndId': statusChatEndId, + 'statusChatEndName': statusChatEndName, 'schedulingDate': startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}`)) + ':00', 'schedulingTime': `${dateSendMessage} ${timeBefore}:00`, 'message': textArea1 @@ -283,8 +277,8 @@ const Modal = (props) => { props.func(dataSendServer) - setOpen(false); - }; + setOpen(false) + } @@ -297,29 +291,29 @@ const Modal = (props) => { let hours = [] let hour = 1 - - - - + + + + if (typeof (startDate) === 'string' && startDate.trim().length > 0 && startDate === dateCurrentFormated()) { - + while (subHours(timer, hour).getHours() >= 6 && subHours(timer, hour).getHours() >= new Date().getHours() && subHours(timer, hour).getHours() <= 20) { - + hours.push({ value: formatedTimeHour(subHours(timer, hour)), label: `${hour} HORA ANTES DO HORÁRIO DO AGENDAMENTO` }) - hour++; + hour++ } if (hours.length > 1) { - + hours.pop() setCurrency(hours[0].value) } @@ -332,7 +326,7 @@ const Modal = (props) => { while (subHours(timer, hour).getHours() >= 6 && subHours(timer, hour).getHours() <= 20) { - + hours.push( { @@ -340,11 +334,11 @@ const Modal = (props) => { label: `${hour} HORA ANTES DO HORÁRIO DO AGENDAMENTO` }) - hour++; + hour++ } if (hours.length > 0) { - + setCurrency(hours[0].value) } else { @@ -358,18 +352,18 @@ const Modal = (props) => { hours.push({ value: formatedTimeHour(timerPicker), label: `24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO` }) - + } - + return { time: hours, hour: hour } } - + @@ -381,51 +375,48 @@ const Modal = (props) => { - const descriptionElementRef = useRef(null); + const descriptionElementRef = useRef(null) useEffect(() => { if (open) { - const { current: descriptionElement } = descriptionElementRef; + const { current: descriptionElement } = descriptionElementRef if (descriptionElement !== null) { - descriptionElement.focus(); + descriptionElement.focus() } } - }, [open]); + }, [open]) // Get from child 1 const textFieldSelect = (data) => { - setStatusChatEnd(data) - } const handleChange = (event) => { - setTextArea1(event.target.value); + setTextArea1(event.target.value) - }; + } const handleCheckBoxChange = (event) => { - - setChecked(event.target.checked); - }; + + setChecked(event.target.checked) + } const handleChangeHourBefore = (event) => { - + // var matchedTime = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0); - - setCurrency(event.target.value); - }; + setCurrency(event.target.value) + } return ( @@ -470,10 +461,10 @@ const Modal = (props) => { { - return { 'value': obj.id, 'label': obj.name } + return { 'value': obj.name, 'label': obj.name } })} /> @@ -481,7 +472,7 @@ const Modal = (props) => { - {statusChatEndId === '2' && + {statusChatEndName === 'LEMBRETE' && @@ -513,8 +504,7 @@ const Modal = (props) => { } - {statusChatEndId === '3' && - + {statusChatEndName === "AGENDAMENTO À CONFIRMAR" && @@ -594,7 +584,7 @@ const Modal = (props) => { - ); + ) } export default Modal \ No newline at end of file diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index 40099ea..cf41fa3 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -77,8 +77,16 @@ const ConfigModal = ({ open, onClose, change }) => { const initialState = { startTimeBus: new Date(), endTimeBus: new Date(), + + startTimeBusSaturday: new Date(), + endTimeBusSaturday: new Date(), + messageBus: '', + messageBusSaturday: '', + businessTimeEnable: false, + businessTimeEnableSaturday: false, + ticketTimeExpiration: new Date(), ticketExpirationMsg: '', ticketExpirationEnable: false, @@ -115,13 +123,16 @@ const ConfigModal = ({ open, onClose, change }) => { if (!selectedNumber) return const { data } = await api.get(`/settings/ticket/${selectedNumber}`) - + if (data?.config && data.config.length === 0) { setConfig(initialState) return } const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours") + + const saturdayBusinessTime = data.config.find((c) => c.key === "saturdayBusinessTime") + const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration") const saturday = data.config.find((c) => c.key === "saturday") const sunday = data.config.find((c) => c.key === "sunday") @@ -134,6 +145,11 @@ const ConfigModal = ({ open, onClose, change }) => { messageBus: outBusinessHours.message, businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false, + startTimeBusSaturday: saturdayBusinessTime.startTime, + endTimeBusSaturday: saturdayBusinessTime.endTime, + messageBusSaturday: saturdayBusinessTime.message, + businessTimeEnableSaturday: saturdayBusinessTime.value === 'enabled' ? true : false, + ticketTimeExpiration: ticketExpiration.startTime, ticketExpirationMsg: ticketExpiration.message, ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false, @@ -165,6 +181,14 @@ const ConfigModal = ({ open, onClose, change }) => { message: values.messageBus, value: values.businessTimeEnable ? 'enabled' : 'disabled' }, + + saturdayBusinessTime: { + startTime: values.startTimeBusSaturday, + endTime: values.endTimeBusSaturday, + message: values.messageBusSaturday, + value: values.businessTimeEnableSaturday ? 'enabled' : 'disabled' + }, + ticketExpiration: { startTime: values.ticketTimeExpiration, message: values.ticketExpirationMsg, @@ -205,7 +229,7 @@ const ConfigModal = ({ open, onClose, change }) => { onClose() // setConfig(initialState) } - + return (
{
+
+ {/* SABADO INICIO */} +
+ + {' '} + + + + } + label={'Ativar/Desativar'} /> +
+ +
+ +
+ {/* SABADO FIM */}
diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js index 3681c6c..b8310e7 100644 --- a/frontend/src/components/ContactCreateTicketModal/index.js +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -157,7 +157,7 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { const { data } = await api.get("/whatsapp/official/matchQueue", { params: { userId: user.id, queueId: selectedQueue }, }) - console.log('WHATSAPP DATA: ', data) + // console.log('WHATSAPP DATA: ', data) setWhatsQueue(data) @@ -173,12 +173,7 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { }, 500) return () => clearTimeout(delayDebounceFn) - }, [selectedQueue, user.id]) - - useEffect(() => { - console.log('selectedWhatsId: ', selectedWhatsId) - console.log('whatsQuee: ', whatsQueue) - }, [whatsQueue, selectedWhatsId]) + }, [selectedQueue, user.id]) return ( diff --git a/frontend/src/components/ContactModal/index.js b/frontend/src/components/ContactModal/index.js index 95e16e9..b8f4b5c 100644 --- a/frontend/src/components/ContactModal/index.js +++ b/frontend/src/components/ContactModal/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react" +import React, { useState, useEffect, useRef, useContext } from "react" import * as Yup from "yup" import { Formik, FieldArray, Form, Field } from "formik" @@ -16,6 +16,9 @@ import Typography from "@material-ui/core/Typography" import IconButton from "@material-ui/core/IconButton" import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline" import CircularProgress from "@material-ui/core/CircularProgress" +import QueueSelect from '../QueueSelect' +import { AuthContext } from '../../context/Auth/AuthContext' + import { i18n } from "../../translate/i18n" @@ -68,6 +71,9 @@ const ContactSchema = Yup.object().shape({ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { const classes = useStyles() const isMounted = useRef(true) + const { user, setting, getSettingValue } = useContext(AuthContext) + const [settings, setSettings] = useState(setting) + const [selectedQueueIds, setSelectedQueueIds] = useState([]) const initialState = { name: "", @@ -86,6 +92,9 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { }, []) useEffect(() => { + + setSelectedQueueIds([]) + const fetchContact = async () => { if (initialValues) { setContact(prevState => { @@ -93,12 +102,22 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { }) } - if (!contactId) return + if (!contactId) { + return + } + try { const { data } = await api.get(`/contacts/${contactId}`) + if (isMounted.current) { setContact(data) + + if (data?.queues) { + const userQueueIds = data.queues?.map(queue => queue.id) + setSelectedQueueIds(userQueueIds) + } + } } catch (err) { toastError(err) @@ -108,13 +127,28 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { fetchContact() }, [contactId, open, initialValues]) + + useEffect(() => { + + if (open && selectedQueueIds.length === 0 && !contactId) { + setSelectedQueueIds(user.queues.map(q => q.id)) + } + + }, [open,]) + + + const handleClose = () => { onClose() setContact(initialState) } const handleSaveContact = async values => { - try { + try { + + values = { ...values, queueIds: selectedQueueIds } + + if (contactId) { await api.put(`/contacts/${contactId}`, values) handleClose() @@ -125,7 +159,7 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { } handleClose() } - toast.success(i18n.t("contactModal.success")) + toast.success(i18n.t("contactModal.success")) } catch (err) { toastError(err) } @@ -246,6 +280,20 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { )} + +
+ { + ((settings && getSettingValue('contactByqueues') === 'enabled')) && ( + { + return setSelectedQueueIds(selectedIds) + }} + _queues={user.queues} + /> + ) + } +
- - - -
- ); +
+ + + + {statusChatEndName === 'LEMBRETE' && + + + + Lembrete + + + + + + + + + + + + + + + + + + + + + + + } + + + {statusChatEndName === 'AGENDAMENTO À CONFIRMAR' && + + + + + Agendamento + + + + + + + + + + + + + + + + + + + {currencyHourBefore && startDate && typeof (startDate) === 'string' && startDate.trim().length > 0 && currenciesTimeBefore.length > 0 && + 0 ? false : true} + select + label="Enviar mensagem para cliente" + value={currencyHourBefore} + size="small" + onChange={handleChangeHourBefore} + > + {currenciesTimeBefore.map((option) => ( + + {option.label} + + ))} + + } + + + + + + + + + + + + + } + + + + + + + + + +
+ +
+ +
+ + + ) } export default Modal \ No newline at end of file diff --git a/frontend/src/components/NewTicketModal/index.js b/frontend/src/components/NewTicketModal/index.js index 4ff67f7..0851294 100644 --- a/frontend/src/components/NewTicketModal/index.js +++ b/frontend/src/components/NewTicketModal/index.js @@ -45,7 +45,7 @@ const NewTicketModal = ({ modalOpen, onClose }) => { const fetchContacts = async () => { try { const { data } = await api.get("contacts", { - params: { searchParam }, + params: { searchParam, userId: user.id }, }); setOptions(data.contacts); setLoading(false); diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index b14678d..866c4a4 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -20,6 +20,9 @@ import useTickets from "../../hooks/useTickets" import alertSound from "../../assets/sound.mp3" import { AuthContext } from "../../context/Auth/AuthContext" +import api from "../../services/api"; +import toastError from "../../errors/toastError"; + const useStyles = makeStyles(theme => ({ tabContainer: { overflowY: "auto", @@ -38,7 +41,29 @@ const useStyles = makeStyles(theme => ({ noShadow: { boxShadow: "none !important", }, -})) +})) + + +let _fifo + +// const onlineEmitter = async (socket, user) => { + +// try { +// clearInterval(_fifo); + +// socket.emit("online", user.id) + +// } catch (error) { +// console.log('error on onlineEmitter: ', error) +// } +// finally { +// _fifo = setInterval(onlineEmitter, 3000); +// } +// } + +// _fifo = setInterval(onlineEmitter, 3000); + + const NotificationsPopOver = () => { @@ -61,9 +86,11 @@ const NotificationsPopOver = () => { const historyRef = useRef(history) const { handleLogout } = useContext(AuthContext) - + const [settings, setSettings] = useState([]); // const [lastRef] = useState(+history.location.pathname.split("/")[2]) - + + + useEffect(() => { soundAlertRef.current = play @@ -80,10 +107,28 @@ const NotificationsPopOver = () => { }, [tickets]) useEffect(() => { + + + ticketIdRef.current = ticketIdUrl }, [ticketIdUrl]) + useEffect(() => { + const fetchSession = async () => { + try { + const { data } = await api.get('/settings') + setSettings(data.settings) + } catch (err) { + toastError(err) + } + } + fetchSession() + }, []) + const getSettingValue = (key) => { + const { value } = settings.find((s) => s.key === key) + return value + } useEffect(() => { @@ -103,6 +148,8 @@ const NotificationsPopOver = () => { if (data.action === "logout") { + + if (`${user.id}` === data.userOnlineTime['userId']) { socket.emit("online", { logoutUserId: user.id }) @@ -111,6 +158,7 @@ const NotificationsPopOver = () => { } + }) // socket.on("isOnline", (data) => { @@ -129,24 +177,21 @@ const NotificationsPopOver = () => { if (user.profile === 'user') { - // if (_fifo) { - // clearInterval(_fifo); - // } + if (_fifo) { + clearInterval(_fifo) + } - // _fifo = setInterval(() => { - // socket.emit("online", user.id) - // }, 3000); - - const intID = setInterval(() => { - console.log('emitting the online') + _fifo = setInterval(() => { socket.emit("online", user.id) }, 3000) - return () => clearInterval(intID) - - } + + + + + return () => { socket.disconnect() } @@ -162,7 +207,9 @@ const NotificationsPopOver = () => { socket.on("ticket", data => { - if (data.action === "updateUnread" || data.action === "delete") { + if (data.action === "updateUnread" || data.action === "delete") { + + setNotifications(prevState => { const ticketIndex = prevState.findIndex(t => t.id === data.ticketId) @@ -187,15 +234,26 @@ const NotificationsPopOver = () => { } }) - socket.on("appMessage", data => { + socket.on("appMessage", data => { + + if ( data.action === "create" && !data.message.read && (data.ticket.userId === user?.id || !data.ticket.userId) - ) { + ) { - setNotifications(prevState => { + + + + setNotifications(prevState => { + + + + // prevState.forEach((e)=>{ + // + // }) const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id) if (ticketIndex !== -1) { @@ -205,55 +263,90 @@ const NotificationsPopOver = () => { } return [data.ticket, ...prevState] - }) + }) + + const shouldNotNotificate = (data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || (data.ticket.userId && data.ticket.userId !== user?.id) || data.ticket.isGroup || !data.ticket.userId - if (shouldNotNotificate) return + if (shouldNotNotificate) return handleNotifications(data) } }) + socket.on('notifyPeding', data =>{ + if(settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data); + }); + return () => { socket.disconnect() } - }, [user]) + }, [user, settings]) - const handleNotifications = data => { - const { message, contact, ticket } = data + const handleNotifications = (data, notify) => { + let isQueue = false; + if(!notify){ + const { message, contact, ticket } = data - const options = { - body: `${message.body} - ${format(new Date(), "HH:mm")}`, - icon: contact.profilePicUrl, - tag: ticket.id, - renotify: true, - } - - const notification = new Notification( - `${i18n.t("tickets.notification.message")} ${contact.name}`, - options - ) - - notification.onclick = e => { - e.preventDefault() - window.focus() - historyRef.current.push(`/tickets/${ticket.id}`) - } - - setDesktopNotifications(prevState => { - const notfiticationIndex = prevState.findIndex( - n => n.tag === notification.tag - ) - if (notfiticationIndex !== -1) { - prevState[notfiticationIndex] = notification - return [...prevState] + const options = { + body: `${message.body} - ${format(new Date(), "HH:mm")}`, + icon: contact.profilePicUrl, + tag: ticket.id, + renotify: true, } - return [notification, ...prevState] - }) + const notification = new Notification( + `${i18n.t("tickets.notification.message")} ${contact.name}`, + options + ) + + notification.onclick = e => { + e.preventDefault() + window.focus() + historyRef.current.push(`/tickets/${ticket.id}`) + } + + setDesktopNotifications(prevState => { + const notfiticationIndex = prevState.findIndex( + n => n.tag === notification.tag + ) + if (notfiticationIndex !== -1) { + prevState[notfiticationIndex] = notification + return [...prevState] + } + return [notification, ...prevState] + }) + }else{ + user.queues.forEach(queue =>{ + if(queue.id === notify.data?.queue?.id){ + isQueue = true; + } + }) + if(!isQueue){ + return; + }else { + const notification = new Notification(`${i18n.t("tickets.notification.messagePeding")} ${notify.data?.queue?.name}`); + notification.onclick = e => { + e.preventDefault() + window.focus() + historyRef.current.push(`/tickets`) + } + + setDesktopNotifications(prevState => { + const notfiticationIndex = prevState.findIndex( + n => n.tag === notification.tag + ) + if (notfiticationIndex !== -1) { + prevState[notfiticationIndex] = notification + return [...prevState] + } + return [notification, ...prevState] + }) + } + } soundAlertRef.current() } @@ -294,10 +387,10 @@ const NotificationsPopOver = () => { vertical: "top", horizontal: "right", }} - classes={{ paper: classes.popoverPaper }} + classes={{ paper: classes?.popoverPaper }} onClose={handleClickAway} > - + {notifications.length === 0 ? ( {i18n.t("notifications.noTickets")} diff --git a/frontend/src/components/PositionModal/index.js b/frontend/src/components/PositionModal/index.js new file mode 100644 index 0000000..29dcde5 --- /dev/null +++ b/frontend/src/components/PositionModal/index.js @@ -0,0 +1,254 @@ +import React, { useState, useEffect, useRef, useContext } from "react" + +import * as Yup from "yup" +import { Formik, Form, Field } from "formik" +import { toast } from "react-toastify" +import openSocket from 'socket.io-client' + + +import { + makeStyles, + Button, + TextField, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + CircularProgress, +} from "@material-ui/core" +import { green } from "@material-ui/core/colors" +import { i18n } from "../../translate/i18n" +import QueueSelect from '../QueueSelect' +import { AuthContext } from '../../context/Auth/AuthContext' + +import api from "../../services/api" +import toastError from "../../errors/toastError" + +const useStyles = makeStyles((theme) => ({ + root: { + flexWrap: "wrap", + }, + textField: { + marginRight: theme.spacing(1), + width: "100%", + }, + + btnWrapper: { + position: "relative", + }, + + buttonProgress: { + color: green[500], + position: "absolute", + top: "50%", + left: "50%", + marginTop: -12, + marginLeft: -12, + }, + textQuickAnswerContainer: { + width: "100%", + }, +})) + +const PositionSchema = Yup.object().shape({ + name: Yup.string() + .required("Required"), +}) + +const PositionModal = ({ + open, + onClose, + positionId, + initialValues, + onSave, +}) => { + const classes = useStyles() + const isMounted = useRef(true) + + const initialState = { + name: "", + } + + const [position, setPosition] = useState(initialState) + // const [selectedQueueIds, setSelectedQueueIds] = useState([]) + const { setting } = useContext(AuthContext) + const [settings, setSettings] = useState(setting) + + + useEffect(() => { + return () => { + isMounted.current = false + } + }, []) + + useEffect(() => { + setSettings(setting) + }, [setting]) + + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + useEffect(() => { + const fetchPosition = async () => { + if (initialValues) { + setPosition((prevState) => { + return { ...prevState, ...initialValues } + }) + } + + if (!positionId) return + + try { + const { data } = await api.get(`/positions/${positionId}`) + if (isMounted.current) { + setPosition(data) + + // if (data?.queues) { + // console.log('data.queues: ', data.queues) + // const quickQueueIds = data.queues?.map((queue) => queue.id) + // setSelectedQueueIds(quickQueueIds) + // } + } + } catch (err) { + toastError(err) + } + } + + fetchPosition() + }, [positionId, open, initialValues]) + + const handleClose = () => { + onClose() + setPosition(initialState) + } + + const handleSavePosition = async (values) => { + try { + + if (positionId) { + await api.put(`/positions/${positionId}`, values) + handleClose() + toast.success("Cargo editado com sucesso") + } else { + const { data } = await api.post("/positions", values) + if (onSave) { + onSave(data) + } + handleClose() + toast.success("Cargo salvo com sucesso") + } + + } catch (err) { + toastError(err) + } + } + + return ( +
+ + + {positionId + ? `Editar Cargo` + : `Adicionar Cargo`} + + { + setTimeout(() => { + handleSavePosition(values) + actions.setSubmitting(false) + }, 400) + }} + > + {({ values, errors, touched, isSubmitting }) => ( +
+ +
+ +
+ {/*
+ { + ((settings && getSettingValue('quickAnswerByQueue') === 'enabled')) && ( + { + return setSelectedQueueIds(selectedIds) + }} + _queues={user.queues} + /> + ) + } +
*/} +
+ + + + +
+ )} +
+
+
+ ) +} + +export default PositionModal diff --git a/frontend/src/components/QueueModal/index.js b/frontend/src/components/QueueModal/index.js index fa5caa9..eb82ee8 100644 --- a/frontend/src/components/QueueModal/index.js +++ b/frontend/src/components/QueueModal/index.js @@ -1,26 +1,31 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useContext } from "react" -import * as Yup from "yup"; -import { Formik, Form, Field } from "formik"; -import { toast } from "react-toastify"; +import * as Yup from "yup" +import { Formik, Form, Field } from "formik" +import { toast } from "react-toastify" -import { makeStyles } from "@material-ui/core/styles"; -import { green } from "@material-ui/core/colors"; -import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; -import Dialog from "@material-ui/core/Dialog"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import CircularProgress from "@material-ui/core/CircularProgress"; +import { makeStyles } from "@material-ui/core/styles" +import { green } from "@material-ui/core/colors" +import Button from "@material-ui/core/Button" +import TextField from "@material-ui/core/TextField" +import Dialog from "@material-ui/core/Dialog" +import DialogActions from "@material-ui/core/DialogActions" +import DialogContent from "@material-ui/core/DialogContent" +import DialogTitle from "@material-ui/core/DialogTitle" +import CircularProgress from "@material-ui/core/CircularProgress" + +import { i18n } from "../../translate/i18n" + +import api from "../../services/api" +import toastError from "../../errors/toastError" +import ColorPicker from "../ColorPicker" +import { IconButton, InputAdornment } from "@material-ui/core" +import { Colorize } from "@material-ui/icons" + +import { AuthContext } from '../../context/Auth/AuthContext' +import openSocket from 'socket.io-client' -import { i18n } from "../../translate/i18n"; -import api from "../../services/api"; -import toastError from "../../errors/toastError"; -import ColorPicker from "../ColorPicker"; -import { IconButton, InputAdornment } from "@material-ui/core"; -import { Colorize } from "@material-ui/icons"; const useStyles = makeStyles(theme => ({ root: { @@ -52,7 +57,7 @@ const useStyles = makeStyles(theme => ({ width: 20, height: 20, }, -})); +})) const QueueSchema = Yup.object().shape({ name: Yup.string() @@ -61,61 +66,92 @@ const QueueSchema = Yup.object().shape({ .required("Required"), color: Yup.string().min(3, "Too Short!").max(9, "Too Long!").required(), greetingMessage: Yup.string(), -}); +}) const QueueModal = ({ open, onClose, queueId }) => { - const classes = useStyles(); + const classes = useStyles() const initialState = { name: "", color: "", greetingMessage: "", - }; + farewellMessage: "", + cc: "" + } - const [colorPickerModalOpen, setColorPickerModalOpen] = useState(false); - const [queue, setQueue] = useState(initialState); - const greetingRef = useRef(); + const { user, setting, getSettingValue } = useContext(AuthContext) + const [settings, setSettings] = useState(setting) + + + const [colorPickerModalOpen, setColorPickerModalOpen] = useState(false) + const [queue, setQueue] = useState(initialState) + const greetingRef = useRef() + + useEffect(() => { + setSettings(setting) + }, [setting]) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) useEffect(() => { (async () => { - if (!queueId) return; + if (!queueId) return try { - const { data } = await api.get(`/queue/${queueId}`); + const { data } = await api.get(`/queue/${queueId}`) setQueue(prevState => { - return { ...prevState, ...data }; - }); + return { ...prevState, ...data } + }) } catch (err) { - toastError(err); + toastError(err) } - })(); + })() return () => { setQueue({ name: "", color: "", greetingMessage: "", - }); - }; - }, [queueId, open]); + farewellMessage: "", + cc: "" + }) + } + }, [queueId, open]) const handleClose = () => { - onClose(); - setQueue(initialState); - }; + onClose() + setQueue(initialState) + } const handleSaveQueue = async values => { try { if (queueId) { - await api.put(`/queue/${queueId}`, values); + await api.put(`/queue/${queueId}`, values) } else { - await api.post("/queue", values); + await api.post("/queue", values) } - toast.success("Queue saved successfully"); - handleClose(); + toast.success("Queue saved successfully") + handleClose() } catch (err) { - toastError(err); + toastError(err) } - }; + } return (
@@ -131,9 +167,9 @@ const QueueModal = ({ open, onClose, queueId }) => { validationSchema={QueueSchema} onSubmit={(values, actions) => { setTimeout(() => { - handleSaveQueue(values); - actions.setSubmitting(false); - }, 400); + handleSaveQueue(values) + actions.setSubmitting(false) + }, 400) }} > {({ touched, errors, isSubmitting, values }) => ( @@ -156,8 +192,8 @@ const QueueModal = ({ open, onClose, queueId }) => { name="color" id="color" onFocus={() => { - setColorPickerModalOpen(true); - greetingRef.current.focus(); + setColorPickerModalOpen(true) + greetingRef.current.focus() }} error={touched.color && Boolean(errors.color)} helperText={touched.color && errors.color} @@ -187,10 +223,10 @@ const QueueModal = ({ open, onClose, queueId }) => { open={colorPickerModalOpen} handleClose={() => setColorPickerModalOpen(false)} onChange={color => { - values.color = color; + values.color = color setQueue(() => { - return { ...values, color }; - }); + return { ...values, color } + }) }} />
@@ -213,6 +249,45 @@ const QueueModal = ({ open, onClose, queueId }) => { margin="dense" />
+ + { + ((settings && getSettingValue('farewellMessageByQueue') === 'enabled')) && ( +
+ +
+ ) + } + +
+ +
- ); -}; + ) +} -export default QueueModal; \ No newline at end of file +export default QueueModal \ No newline at end of file diff --git a/frontend/src/components/QueueSelect/index.js b/frontend/src/components/QueueSelect/index.js index b80164e..552b552 100644 --- a/frontend/src/components/QueueSelect/index.js +++ b/frontend/src/components/QueueSelect/index.js @@ -1,13 +1,13 @@ -import React, { useEffect, useState } from "react"; -import { makeStyles } from "@material-ui/core/styles"; -import InputLabel from "@material-ui/core/InputLabel"; -import MenuItem from "@material-ui/core/MenuItem"; -import FormControl from "@material-ui/core/FormControl"; -import Select from "@material-ui/core/Select"; -import Chip from "@material-ui/core/Chip"; -import toastError from "../../errors/toastError"; -import api from "../../services/api"; -import { i18n } from "../../translate/i18n"; +import React, { useEffect, useState } from "react" +import { makeStyles } from "@material-ui/core/styles" +import InputLabel from "@material-ui/core/InputLabel" +import MenuItem from "@material-ui/core/MenuItem" +import FormControl from "@material-ui/core/FormControl" +import Select from "@material-ui/core/Select" +import Chip from "@material-ui/core/Chip" +import toastError from "../../errors/toastError" +import api from "../../services/api" +import { i18n } from "../../translate/i18n" const useStyles = makeStyles(theme => ({ chips: { @@ -17,26 +17,30 @@ const useStyles = makeStyles(theme => ({ chip: { margin: 2, }, -})); +})) -const QueueSelect = ({ selectedQueueIds, onChange }) => { - const classes = useStyles(); - const [queues, setQueues] = useState([]); +const QueueSelect = ({ selectedQueueIds, onChange, _queues = [] }) => { + const classes = useStyles() + + const [queues, setQueues] = useState(_queues) useEffect(() => { + + if (_queues.length > 0) return + (async () => { try { - const { data } = await api.get("/queue"); - setQueues(data); + const { data } = await api.get("/queue") + setQueues(data) } catch (err) { - toastError(err); + toastError(err) } - })(); - }, []); + })() + }, []) const handleChange = e => { - onChange(e.target.value); - }; + onChange(e.target.value) + } return (
@@ -62,7 +66,7 @@ const QueueSelect = ({ selectedQueueIds, onChange }) => {
{selected?.length > 0 && selected.map(id => { - const queue = queues.find(q => q.id === id); + const queue = queues.find(q => q.id === id) return queue ? ( { label={queue.name} className={classes.chip} /> - ) : null; + ) : null })}
)} @@ -84,7 +88,7 @@ const QueueSelect = ({ selectedQueueIds, onChange }) => {
- ); -}; + ) +} -export default QueueSelect; +export default QueueSelect diff --git a/frontend/src/components/QuickAnswersModal/index.js b/frontend/src/components/QuickAnswersModal/index.js index ba100c6..10008f8 100644 --- a/frontend/src/components/QuickAnswersModal/index.js +++ b/frontend/src/components/QuickAnswersModal/index.js @@ -1,8 +1,10 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useContext } from "react" + +import * as Yup from "yup" +import { Formik, Form, Field } from "formik" +import { toast } from "react-toastify" +import openSocket from 'socket.io-client' -import * as Yup from "yup"; -import { Formik, Form, Field } from "formik"; -import { toast } from "react-toastify"; import { makeStyles, @@ -13,12 +15,14 @@ import { DialogContent, DialogTitle, CircularProgress, -} from "@material-ui/core"; -import { green } from "@material-ui/core/colors"; -import { i18n } from "../../translate/i18n"; +} from "@material-ui/core" +import { green } from "@material-ui/core/colors" +import { i18n } from "../../translate/i18n" +import QueueSelect from '../QueueSelect' +import { AuthContext } from '../../context/Auth/AuthContext' -import api from "../../services/api"; -import toastError from "../../errors/toastError"; +import api from "../../services/api" +import toastError from "../../errors/toastError" const useStyles = makeStyles((theme) => ({ root: { @@ -44,7 +48,7 @@ const useStyles = makeStyles((theme) => ({ textQuickAnswerContainer: { width: "100%", }, -})); +})) const QuickAnswerSchema = Yup.object().shape({ shortcut: Yup.string() @@ -55,7 +59,7 @@ const QuickAnswerSchema = Yup.object().shape({ .min(8, "Too Short!") .max(30000, "Too Long!") .required("Required"), -}); +}) const QuickAnswersModal = ({ open, @@ -64,67 +68,115 @@ const QuickAnswersModal = ({ initialValues, onSave, }) => { - const classes = useStyles(); - const isMounted = useRef(true); + const classes = useStyles() + const isMounted = useRef(true) const initialState = { shortcut: "", message: "", - }; + } - const [quickAnswer, setQuickAnswer] = useState(initialState); + const [quickAnswer, setQuickAnswer] = useState(initialState) + const [selectedQueueIds, setSelectedQueueIds] = useState([]) + const { user, setting, getSettingValue } = useContext(AuthContext) + const [settings, setSettings] = useState(setting) useEffect(() => { return () => { - isMounted.current = false; - }; - }, []); + isMounted.current = false + } + }, []) useEffect(() => { + setSettings(setting) + }, [setting]) + + useEffect(() => { + + setSelectedQueueIds([]) + if (open && selectedQueueIds.length === 0 && !quickAnswerId) { + setSelectedQueueIds(user.queues.map(q => q.id)) + } + + }, [open,]) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + useEffect(() => { + + // setSelectedQueueIds([]) + const fetchQuickAnswer = async () => { if (initialValues) { setQuickAnswer((prevState) => { - return { ...prevState, ...initialValues }; - }); + return { ...prevState, ...initialValues } + }) } - if (!quickAnswerId) return; + if (!quickAnswerId) { + return + } try { - const { data } = await api.get(`/quickAnswers/${quickAnswerId}`); + const { data } = await api.get(`/quickAnswers/${quickAnswerId}`) if (isMounted.current) { - setQuickAnswer(data); + setQuickAnswer(data) + + if (data?.queues) { + const quickQueueIds = data.queues?.map((queue) => queue.id) + setSelectedQueueIds(quickQueueIds) + } } } catch (err) { - toastError(err); + toastError(err) } - }; + } - fetchQuickAnswer(); - }, [quickAnswerId, open, initialValues]); + fetchQuickAnswer() + }, [quickAnswerId, open, initialValues]) const handleClose = () => { - onClose(); - setQuickAnswer(initialState); - }; + onClose() + setQuickAnswer(initialState) + } const handleSaveQuickAnswer = async (values) => { try { + + values = { ...values, queueIds: selectedQueueIds } + if (quickAnswerId) { - await api.put(`/quickAnswers/${quickAnswerId}`, values); - handleClose(); + await api.put(`/quickAnswers/${quickAnswerId}`, values) + handleClose() } else { - const { data } = await api.post("/quickAnswers", values); + const { data } = await api.post("/quickAnswers", values) if (onSave) { - onSave(data); + onSave(data) } - handleClose(); + handleClose() } - toast.success(i18n.t("quickAnswersModal.success")); + toast.success(i18n.t("quickAnswersModal.success")) } catch (err) { - toastError(err); + toastError(err) } - }; + } return (
@@ -146,9 +198,9 @@ const QuickAnswersModal = ({ validationSchema={QuickAnswerSchema} onSubmit={(values, actions) => { setTimeout(() => { - handleSaveQuickAnswer(values); - actions.setSubmitting(false); - }, 400); + handleSaveQuickAnswer(values) + actions.setSubmitting(false) + }, 400) }} > {({ values, errors, touched, isSubmitting }) => ( @@ -183,6 +235,19 @@ const QuickAnswersModal = ({ fullWidth />
+
+ { + ((settings && getSettingValue('quickAnswerByQueue') === 'enabled')) && ( + { + return setSelectedQueueIds(selectedIds) + }} + _queues={user.queues} + /> + ) + } +
+ + Relatórios + + + + Escolha uma opção do tipo de relatório abaixo + + + + + + opcoes + + + + + + {/* + } + label="Full width" + /> */} + + +
{textOption}
+ +
+ + + + + + +
+ +
+ + ) +} diff --git a/frontend/src/components/StatusChatEndModal/index.js b/frontend/src/components/StatusChatEndModal/index.js new file mode 100644 index 0000000..1c18884 --- /dev/null +++ b/frontend/src/components/StatusChatEndModal/index.js @@ -0,0 +1,260 @@ +import React, { useState, useEffect, useRef, useContext } from "react" + +import * as Yup from "yup" +import { Formik, Form, Field } from "formik" +import { toast } from "react-toastify" +import openSocket from 'socket.io-client' + + +import { + makeStyles, + Button, + TextField, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + CircularProgress, +} from "@material-ui/core" +import { green } from "@material-ui/core/colors" +import { i18n } from "../../translate/i18n" +import QueueSelect from '../QueueSelect' +import { AuthContext } from '../../context/Auth/AuthContext' + +import api from "../../services/api" +import toastError from "../../errors/toastError" + +const useStyles = makeStyles((theme) => ({ + root: { + flexWrap: "wrap", + }, + textField: { + marginRight: theme.spacing(1), + width: "100%", + }, + + btnWrapper: { + position: "relative", + }, + + buttonProgress: { + color: green[500], + position: "absolute", + top: "50%", + left: "50%", + marginTop: -12, + marginLeft: -12, + }, + textQuickAnswerContainer: { + width: "100%", + }, +})) + +const StatusChatEndSchema = Yup.object().shape({ + name: Yup.string() + .min(2, "Too Short!") + .max(40, "Too Long!") + .required("Required"), +}) + +const StatusChatEndModal = ({ + open, + onClose, + statusChatEndId, + initialValues, + onSave, +}) => { + const classes = useStyles() + const isMounted = useRef(true) + + const initialState = { + name: "", + farewellMessage: "", + isDefault: false, + } + + const [statusChatEnd, setStatusChatEnd] = useState(initialState) + const { user, setting, getSettingValue } = useContext(AuthContext) + const [settings, setSettings] = useState(setting) + + // console.log('USER: ', JSON.stringify(user, null, 6)) + + useEffect(() => { + return () => { + isMounted.current = false + } + }, []) + + useEffect(() => { + setSettings(setting) + }, [setting]) + + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + useEffect(() => { + const fetchQuickAnswer = async () => { + if (initialValues) { + setStatusChatEnd((prevState) => { + return { ...prevState, ...initialValues } + }) + } + + if (!statusChatEndId) return + + try { + const { data } = await api.get(`/statusChatEnd/${statusChatEndId}`) + if (isMounted.current) { + setStatusChatEnd(data) + } + } catch (err) { + toastError(err) + } + } + + fetchQuickAnswer() + }, [statusChatEndId, open, initialValues]) + + const handleClose = () => { + onClose() + setStatusChatEnd(initialState) + } + + const handleSaveStatusChatEnd = (values) => { + + (async () => { + try { + + if (statusChatEndId) { + await api.put(`/statusChatEnd/${statusChatEndId}`, values) + handleClose() + toast.success("Status de encerramento editado com sucesso") + + } else { + const { data } = await api.post("/statusChatEnd", values) + if (onSave) { + onSave(data) + } + handleClose() + toast.success("Status de encerramento criado com sucesso") + + } + } catch (err) { + toastError(err) + } + })() + + + } + + return ( +
+ + + {statusChatEndId + ? `Editar Status de encerramento` + : `Adicionar Status de encerramento`} + + { + setTimeout(() => { + handleSaveStatusChatEnd(values) + actions.setSubmitting(false) + }, 400) + }} + > + {({ values, errors, touched, isSubmitting }) => ( +
+ +
+ +
+
+ +
+
+ + + + +
+ )} +
+
+
+ ) +} + +export default StatusChatEndModal diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index a88507b..dbf78bf 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -1,23 +1,24 @@ -import React, { useState, useEffect } from "react"; -import { useParams, useHistory } from "react-router-dom"; +import React, { useState, useEffect } from "react" +import { useParams, useHistory } from "react-router-dom" -import { toast } from "react-toastify"; -import openSocket from "socket.io-client"; -import clsx from "clsx"; +import { toast } from "react-toastify" +import openSocket from "socket.io-client" +import clsx from "clsx" -import { Paper, makeStyles } from "@material-ui/core"; +import { Paper, makeStyles } from "@material-ui/core" -import ContactDrawer from "../ContactDrawer"; -import MessageInput from "../MessageInput/"; -import TicketHeader from "../TicketHeader"; -import TicketInfo from "../TicketInfo"; -import TicketActionButtons from "../TicketActionButtons"; -import MessagesList from "../MessagesList"; -import api from "../../services/api"; -import { ReplyMessageProvider } from "../../context/ReplyingMessage/ReplyingMessageContext"; -import toastError from "../../errors/toastError"; +import ContactDrawer from "../ContactDrawer" +import MessageInput from "../MessageInput/" +import TicketHeader from "../TicketHeader" +import TicketInfo from "../TicketInfo" +import TicketActionButtons from "../TicketActionButtons" +import MessagesList from "../MessagesList" +import api from "../../services/api" +import { ReplyMessageProvider } from "../../context/ReplyingMessage/ReplyingMessageContext" +import toastError from "../../errors/toastError" +import { CountTicketMsgProvider } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider" -const drawerWidth = 320; +const drawerWidth = 320 const useStyles = makeStyles((theme) => ({ root: { @@ -71,88 +72,123 @@ const useStyles = makeStyles((theme) => ({ }), marginRight: 0, }, -})); +})) const Ticket = () => { - const { ticketId } = useParams(); - const history = useHistory(); - const classes = useStyles(); + const { ticketId } = useParams() + const history = useHistory() + const classes = useStyles() - const [drawerOpen, setDrawerOpen] = useState(false); - const [loading, setLoading] = useState(true); - const [contact, setContact] = useState({}); - const [ticket, setTicket] = useState({}); + const [drawerOpen, setDrawerOpen] = useState(false) + const [loading, setLoading] = useState(true) + const [contact, setContact] = useState({}) + const [ticket, setTicket] = useState({}) const [statusChatEnd, setStatusChatEnd] = useState({}) + const [defaultStatusChatEnd, setDefaultStatusChatEnd] = useState('') useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchTicket = async () => { try { // maria julia - const { data } = await api.get("/tickets/" + ticketId); + const { data } = await api.get("/tickets/" + ticketId) // setContact(data.contact); // setTicket(data); - setContact(data.contact.contact); - setTicket(data.contact); + setContact(data.contact.contact) + setTicket(data.contact) setStatusChatEnd(data.statusChatEnd) - setLoading(false); + setLoading(false) } catch (err) { - setLoading(false); - toastError(err); + setLoading(false) + toastError(err) } - }; - fetchTicket(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [ticketId, history]); + } + fetchTicket() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [ticketId, history]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.on("connect", () => socket.emit("joinChatBox", ticketId)); + (async () => { + try { + + const { data } = await api.get("/statusChatEnd/true") + + setDefaultStatusChatEnd(data?.name?.trim()) + + } catch (err) { + toastError(err) + } + })() + }, []) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on("connect", () => socket.emit("joinChatBox", ticketId)) socket.on("ticket", (data) => { if (data.action === "update") { - setTicket(data.ticket); + setTicket(data.ticket) } if (data.action === "delete") { - toast.success("Ticket deleted sucessfully."); - history.push("/tickets"); + toast.success("Ticket deleted sucessfully.") + history.push("/tickets") } - }); + }) socket.on("contact", (data) => { if (data.action === "update") { setContact((prevState) => { if (prevState.id === data.contact?.id) { - return { ...prevState, ...data.contact }; + return { ...prevState, ...data.contact } } - return prevState; - }); + return prevState + }) } - }); + }) + + socket.on("remoteTickesControllIdleOpen", (data) => { + if (data.action === "update") { + let url_ticketId + try { + + let url_split = window.location.href.split('tickets') + + url_ticketId = url_split[url_split.length - 1].match(/\d+/)[0] + + if (url_ticketId && +url_ticketId === data.ticketId) { + history.push("/tickets") + } + + } catch (error) { + console.log('error on try do the send seen: ', error) + } + } + }) return () => { - socket.disconnect(); - }; - }, [ticketId, history]); + socket.disconnect() + } + }, [ticketId, history]) const handleDrawerOpen = () => { - setDrawerOpen(true); - }; + setDrawerOpen(true) + } const handleDrawerClose = () => { - setDrawerOpen(false); - }; + setDrawerOpen(false) + } return (
@@ -171,16 +207,26 @@ const Ticket = () => { onClick={handleDrawerOpen} />
-
- +
+
- - + + + + + + + + + { loading={loading} />
- ); -}; + ) +} -export default Ticket; +export default Ticket diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index 8382add..e23e5c8 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -1,21 +1,21 @@ -import React, { useContext, useState } from "react"; -import { useHistory } from "react-router-dom"; +import React, { useContext, useState } from "react" +import { useHistory } from "react-router-dom" -import { makeStyles } from "@material-ui/core/styles"; -import { IconButton } from "@material-ui/core"; -import { MoreVert, Replay } from "@material-ui/icons"; +import { makeStyles } from "@material-ui/core/styles" +import { IconButton } from "@material-ui/core" +import { MoreVert, Replay } from "@material-ui/icons" -import { i18n } from "../../translate/i18n"; -import api from "../../services/api"; -import TicketOptionsMenu from "../TicketOptionsMenu"; -import ButtonWithSpinner from "../ButtonWithSpinner"; -import toastError from "../../errors/toastError"; -import { AuthContext } from "../../context/Auth/AuthContext"; +import { i18n } from "../../translate/i18n" +import api from "../../services/api" +import TicketOptionsMenu from "../TicketOptionsMenu" +import ButtonWithSpinner from "../ButtonWithSpinner" +import toastError from "../../errors/toastError" +import { AuthContext } from "../../context/Auth/AuthContext" -import Modal from "../ChatEnd/ModalChatEnd"; -import { render } from '@testing-library/react'; +import Modal from "../ChatEnd/ModalChatEnd" +import { render } from '@testing-library/react' -import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"; +import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption" const useStyles = makeStyles(theme => ({ actionButtons: { @@ -27,15 +27,15 @@ const useStyles = makeStyles(theme => ({ margin: theme.spacing(1), }, }, -})); +})) -const TicketActionButtons = ({ ticket, statusChatEnd }) => { - const classes = useStyles(); - const history = useHistory(); +const TicketActionButtons = ({ ticket, statusChatEnd, defaultStatusChatEnd }) => { + const classes = useStyles() + const history = useHistory() + + const [anchorEl, setAnchorEl] = useState(null) + const [loading, setLoading] = useState(false) - const [anchorEl, setAnchorEl] = useState(null); - const [loading, setLoading] = useState(false); - // const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow); // const [/*useDialogflow*/, setUseDialogflow] = useState(() => { @@ -47,25 +47,25 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { // } // }); - const ticketOptionsMenuOpen = Boolean(anchorEl); - const { user } = useContext(AuthContext); + const ticketOptionsMenuOpen = Boolean(anchorEl) + const { user } = useContext(AuthContext) - const { tabOption, setTabOption } = useContext(TabTicketContext); + const { tabOption, setTabOption } = useContext(TabTicketContext) const handleOpenTicketOptionsMenu = e => { - setAnchorEl(e.currentTarget); - }; + setAnchorEl(e.currentTarget) + } const handleCloseTicketOptionsMenu = e => { - setAnchorEl(null); - }; + setAnchorEl(null) + } const chatEndVal = (data) => { if (data) { - data = { ...data, 'ticketId': ticket.id } + data = { ...data, 'ticketId': ticket.id } handleUpdateTicketStatus(null, "closed", user?.id, data) @@ -75,20 +75,20 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { const handleModal = (/*status, userId*/) => { - render() - }; + } const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => { - setLoading(true); + setLoading(true) try { if (status === 'closed') { @@ -101,7 +101,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { status: status, userId: userId || null, schedulingNotifyData: JSON.stringify(schedulingData) - }); + }) } else { @@ -113,24 +113,24 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { await api.put(`/tickets/${ticket.id}`, { status: status, userId: userId || null - }); + }) } - setLoading(false); + setLoading(false) if (status === "open") { - history.push(`/tickets/${ticket.id}`); + history.push(`/tickets/${ticket.id}`) } else { - history.push("/tickets"); + history.push("/tickets") } } catch (err) { - setLoading(false); - toastError(err); + setLoading(false) + toastError(err) } - }; + } // const handleContactToggleUseDialogflow = async () => { // setLoading(true); @@ -207,7 +207,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { )} - ); -}; + ) +} -export default TicketActionButtons; +export default TicketActionButtons diff --git a/frontend/src/components/TicketListItem/index.js b/frontend/src/components/TicketListItem/index.js index 60c0fee..417b9d1 100644 --- a/frontend/src/components/TicketListItem/index.js +++ b/frontend/src/components/TicketListItem/index.js @@ -22,6 +22,7 @@ import MarkdownWrapper from "../MarkdownWrapper" import { Tooltip } from "@material-ui/core" import { AuthContext } from "../../context/Auth/AuthContext" import toastError from "../../errors/toastError" +import openSocket from 'socket.io-client' const useStyles = makeStyles(theme => ({ ticket: { @@ -101,13 +102,15 @@ const useStyles = makeStyles(theme => ({ }, })) -const TicketListItem = ({ ticket }) => { +const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => { const classes = useStyles() const history = useHistory() const [loading, setLoading] = useState(false) const { ticketId } = useParams() const isMounted = useRef(true) - const { user } = useContext(AuthContext) + const { user, getSettingValue } = useContext(AuthContext) + const [_remoteTicketsControll, setRemoteTicketsControll] = useState([]) + const [_settings, setSettings] = useState(null) useEffect(() => { return () => { @@ -115,6 +118,14 @@ const TicketListItem = ({ ticket }) => { } }, []) + useEffect(() => { + setSettings(settings) + }, [settings]) + + useEffect(() => { + setRemoteTicketsControll(remoteTicketsControll) + }, [remoteTicketsControll, settings]) + const handleAcepptTicket = async id => { setLoading(true) try { @@ -131,8 +142,6 @@ const TicketListItem = ({ ticket }) => { setLoading(false) } - - history.push(`/tickets/${id}`) } @@ -140,116 +149,192 @@ const TicketListItem = ({ ticket }) => { history.push(`/tickets/${id}`) } + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('remoteTickesControll', (data) => { + console.log('REMOTE TICKETS CONTROLL UPDATE2: ', data.tickets) + if (data.action === 'update') { + setRemoteTicketsControll(data.tickets) + } + }) + + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + + return () => { + socket.disconnect() + } + }, []) + return ( - { - if (ticket.status === "pending") return - handleSelectTicket(ticket.id) - }} - selected={ticketId && +ticketId === ticket.id} - className={clsx(classes.ticket, { - [classes.pendingTicket]: ticket.status === "pending", - })} + - { + if (ticket.status === "pending") return + handleSelectTicket(ticket.id) + }} + selected={ticketId && +ticketId === ticket.id} + className={clsx(classes.ticket, { + [classes.pendingTicket]: ticket.status === "pending", + })} + + style={((ticket.status === "open" || ticket.status === "closed") && ticket?.isRemote) ? { + border: (ticket.status === "open" || ticket.status === "closed") ? "1px solid rgba(121,123,127,0.9)" : "1px solid transparent", + } : {}} + > - - - - - - - - {ticket.contact.name} - - {ticket.status === "closed" && ( - - )} - {ticket.lastMessage && ( + + + + + + + + {ticket?.contact?.name} + + {ticket.status === "closed" && ( + + )} + {ticket.lastMessage && ( + + {ticket?.phoneNumberId && Oficial}{" "} + {isSameDay(parseISO(ticket.updatedAt), new Date()) ? ( + <>{format(parseISO(ticket.updatedAt), "HH:mm")} + ) : ( + <>{format(parseISO(ticket.updatedAt), "dd/MM/yyyy")} + )} + + )} + + } + secondary={ + + - {ticket?.phoneNumberId && Oficial}{" "} - {isSameDay(parseISO(ticket.updatedAt), new Date()) ? ( - <>{format(parseISO(ticket.updatedAt), "HH:mm")} + {ticket.lastMessage ? ( + {ticket.lastMessage} ) : ( - <>{format(parseISO(ticket.updatedAt), "dd/MM/yyyy")} +
)}
- )} -
- } - secondary={ - - - {ticket.lastMessage ? ( - {ticket.lastMessage} - ) : ( -
- )} -
- + - {/* */} -
- } - /> - {ticket.status === "pending" && ( - handleAcepptTicket(ticket.id)} - > - {i18n.t("ticketsList.buttons.accept")} - - )} -
+ + } + /> + {ticket.status === "pending" && ( + + + 0 && + getSettingValue('remoteTicketSendControll') && + getSettingValue('remoteTicketSendControll') === 'enabled') && !ticket?.remoteDone && !_remoteTicketsControll?.includes(+ticket.id)) ? + { style: { backgroundColor: "rgba(121,123,127,0.5)", color: "white" } } : + { style: { backgroundColor: "rgba(121,123,127,0.9)", color: "white" } } : + + { color: "primary" })} + + variant="contained" + + disabled={true ? ((settings && + settings.length > 0 && + getSettingValue('remoteTicketSendControll') && + getSettingValue('remoteTicketSendControll') === 'enabled') && ticket?.isRemote && !ticket?.remoteDone && !_remoteTicketsControll?.includes(+ticket.id)) : false} + + className={classes.acceptButton} + size="small" + loading={loading} + onClick={e => handleAcepptTicket(ticket.id)} + > + + <> + {(ticket?.isRemote && !ticket?.remoteDone) ? ( + <>{i18n.t("ticketsList.buttons.accept")}
CAMPANHA + ) : ( + <>{i18n.t("ticketsList.buttons.accept")} + )} + + + +
+ + + + )} + +
+ ) diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index 9754d79..a481700 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -15,6 +15,8 @@ import { i18n } from "../../translate/i18n" import { AuthContext } from "../../context/Auth/AuthContext" import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket" +import { Divider } from "@material-ui/core" +import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider" const useStyles = makeStyles(theme => ({ ticketsListWrapper: { @@ -181,10 +183,20 @@ const TicketsList = (props) => { const classes = useStyles() const [pageNumber, setPageNumber] = useState(1) const [ticketsList, dispatch] = useReducer(reducer, []) - const { user } = useContext(AuthContext) + const { user, setting, } = useContext(AuthContext) const { searchTicket } = useContext(SearchTicketContext) + const [_remoteTicketsControll, setRemoteTicketsControll] = useState([]) + + const [settings, setSettings] = useState([]) + + useEffect(() => { + setSettings(setting) + }, [setting]) + const { setTickets } = useContext(ticketsContext) + + useEffect(() => { dispatch({ type: "RESET" }) @@ -192,17 +204,22 @@ const TicketsList = (props) => { }, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket]) - const { tickets, hasMore, loading } = useTickets({ + let { tickets, hasMore, loading, remoteTicketsControll } = useTickets({ pageNumber, searchParam, searchParamContent, status, - showAll, + showAll, queueIds: JSON.stringify(selectedQueueIds), tab, unlimited: status === 'open' ? "all" : "false" }) + + useEffect(() => { + setRemoteTicketsControll(remoteTicketsControll) + }, [remoteTicketsControll]) + useEffect(() => { if (!status && !searchParam) return @@ -301,6 +318,27 @@ const TicketsList = (props) => { } }) + + socket.on('remoteTickesControll', (data) => { + console.log('REMOTE TICKETS CONTROLL UPDATE 1: ', data.tickets) + if (data.action === 'update') { + setRemoteTicketsControll(data.tickets) + } + }) + + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { socket.disconnect() } @@ -311,6 +349,12 @@ const TicketsList = (props) => { if (typeof updateCount === "function") { updateCount(ticketsList.length) } + if (ticketsList && status === "pending"){ + setTickets(ticketsList) + } + // else{ + // setTickets([]) + // } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ticketsList]) @@ -354,7 +398,7 @@ const TicketsList = (props) => { ) : ( <> {ticketsList.map(ticket => ( - + ))} )} diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 06ac9c9..19b922b 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -1,38 +1,45 @@ -import React, { useContext, useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react" -import { makeStyles } from "@material-ui/core/styles"; -import { IconButton } from "@mui/material"; -import Paper from "@material-ui/core/Paper"; -import InputBase from "@material-ui/core/InputBase"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; -import Badge from "@material-ui/core/Badge"; +import { makeStyles } from "@material-ui/core/styles" +import { IconButton } from "@mui/material" +import Paper from "@material-ui/core/Paper" +import InputBase from "@material-ui/core/InputBase" +import Tabs from "@material-ui/core/Tabs" +import Tab from "@material-ui/core/Tab" +import Badge from "@material-ui/core/Badge" -import Tooltip from "@material-ui/core/Tooltip"; +import Tooltip from "@material-ui/core/Tooltip" -import SearchIcon from "@material-ui/icons/Search"; -import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; -import CheckBoxIcon from "@material-ui/icons/CheckBox"; -import MenuIcon from "@material-ui/icons/Menu"; -import FindInPageIcon from '@material-ui/icons/FindInPage'; +import SearchIcon from "@material-ui/icons/Search" +import MoveToInboxIcon from "@material-ui/icons/MoveToInbox" +import CheckBoxIcon from "@material-ui/icons/CheckBox" +import MenuIcon from "@material-ui/icons/Menu" +import FindInPageIcon from '@material-ui/icons/FindInPage' -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import Switch from "@material-ui/core/Switch"; +import FormControlLabel from "@material-ui/core/FormControlLabel" +import Switch from "@material-ui/core/Switch" +import openSocket from "socket.io-client" -import NewTicketModal from "../NewTicketModal"; -import TicketsList from "../TicketsList"; -import TabPanel from "../TabPanel"; +import NewTicketModal from "../NewTicketModal" +import TicketsList from "../TicketsList" +import TabPanel from "../TabPanel" -import { i18n } from "../../translate/i18n"; -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../Can"; -import TicketsQueueSelect from "../TicketsQueueSelect"; -import { Button } from "@material-ui/core"; +import { i18n } from "../../translate/i18n" +import { AuthContext } from "../../context/Auth/AuthContext" +import { Can } from "../Can" +import TicketsQueueSelect from "../TicketsQueueSelect" +import { Button } from "@material-ui/core" -import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"; +import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption" + +import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket" +import useTickets from "../../hooks/useTickets" +import api from "../../services/api" +import toastError from "../../errors/toastError" + +import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider" -import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket"; const useStyles = makeStyles((theme) => ({ ticketsWrapper: { @@ -124,59 +131,106 @@ const useStyles = makeStyles((theme) => ({ hide: { display: "none !important", }, -})); +})) const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } const TicketsManager = () => { - const { tabOption, setTabOption } = useContext(TabTicketContext); + const { tabOption, setTabOption } = useContext(TabTicketContext) const { setSearchTicket } = useContext(SearchTicketContext) - const classes = useStyles(); + const classes = useStyles() - const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); - const [tab, setTab] = useState("open"); - const [tabOpen, setTabOpen] = useState("open"); - const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); - const [showAllTickets, setShowAllTickets] = useState(false); - const { user } = useContext(AuthContext); + const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM) + const [tab, setTab] = useState("open") + const [tabOpen, setTabOpen] = useState("open") + const [newTicketModalOpen, setNewTicketModalOpen] = useState(false) + const [showAllTickets, setShowAllTickets] = useState(false) + const { user, setting, getSettingValue } = useContext(AuthContext) - const [openCount, setOpenCount] = useState(0); - const [pendingCount, setPendingCount] = useState(0); + const [openCount, setOpenCount] = useState(0) + const [pendingCount, setPendingCount] = useState(0) - const userQueueIds = user.queues.map((q) => q.id); - const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); + const userQueueIds = user.queues.map((q) => q.id) + const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []) const [showContentSearch, setShowContentSearch] = useState(false) - const searchInputRef = useRef(); - const searchContentInputRef = useRef(); - const [inputSearch, setInputSearch] = useState(''); + const searchInputRef = useRef() + const searchContentInputRef = useRef() + const [inputSearch, setInputSearch] = useState('') const [inputContentSearch, setInputContentSearch] = useState("") const [openTooltipSearch, setOpenTooltipSearch] = useState(false) - let searchTimeout; - let searchContentTimeout; + const [waitingTime, setWaitingTime] = useState('00:00') + // const [tickets, setTickets] = useState([]); + const [settings, setSettings] = useState([]) + + let searchTimeout + let searchContentTimeout + + const { tickets, } = useContext(ticketsContext) useEffect(() => { - if (user.profile.toUpperCase() === "ADMIN" || - user.profile.toUpperCase() === "SUPERVISOR" || - user.profile.toUpperCase() === "MASTER") { - setShowAllTickets(true); + setSettings(setting) + }, [setting]) + + useEffect(() => { + if (user.profile.toUpperCase() === "ADMIN" || + user.profile.toUpperCase() === "SUPERVISOR" || + user.profile.toUpperCase() === "MASTER") { + setShowAllTickets(true) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, []) useEffect(() => { if (tab === "search") { - searchInputRef.current.focus(); + searchInputRef.current.focus() } setTabOption(tab) - }, [tab, setTabOption]); + }, [tab, setTabOption]) + + useEffect(() => { + + if (settings?.length > 0 && getSettingValue('waitingTimeTickets') !== 'enabled') return + + const calculateAverageTime = () => { + if (tickets.length > 0) { + const now = new Date() + const differenceTime = tickets?.map(ticket => { + const createdAt = new Date(ticket.createdAt) + const difference = now - createdAt + return difference + }) + const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0) + const averageTimeMilliseconds = sumDifferences / tickets?.length + let hours = Math.floor(averageTimeMilliseconds / 3600000) + const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000) + + let days = hours >= 24 ? parseInt(hours / 24) : '' + + if (days != '') hours = hours - (24 * days) + + const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}` + + return averageTimeFormated + } else return '00:00' + } + + setWaitingTime(calculateAverageTime()) + + const intervalId = setInterval(() => { + setWaitingTime(calculateAverageTime()) + }, 10000) + + return () => clearInterval(intervalId) + + }, [tickets]) useEffect(() => { @@ -184,7 +238,7 @@ const TicketsManager = () => { // setSearchParam(prev => ({ ...prev, searchParamContent: "" })) - if (!inputContentSearch) return + if (!inputContentSearch) return if (!searchContentTimeout) return @@ -194,29 +248,29 @@ const TicketsManager = () => { // }, 500); - clearTimeout(searchContentTimeout); + clearTimeout(searchContentTimeout) - setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) - }, [inputContentSearch, searchContentTimeout]); + }, [inputContentSearch, searchContentTimeout]) useEffect(() => { - + //console.log(selectedQueueIds); if (tabOption === 'open') { setTabOption('') - setSearchParam(DEFAULT_SEARCH_PARAM); - setInputSearch(''); + setSearchParam(DEFAULT_SEARCH_PARAM) + setInputSearch('') setInputContentSearch('') - setTab("open"); - return; + setTab("open") + return } }, [tabOption, setTabOption]) - + const removeExtraSpace = (str) => { str = str.replace(/^\s+/g, '') @@ -231,14 +285,14 @@ const TicketsManager = () => { setSearchTicket(searchParam.searchParam) - clearTimeout(searchTimeout); + clearTimeout(searchTimeout) if (searchedTerm === "") { setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setInputSearch(searchedTerm) setShowContentSearch(false) - setTab("open"); - return; + setTab("open") + return } if (searchedTerm.length < 4) { @@ -249,22 +303,22 @@ const TicketsManager = () => { searchTimeout = setTimeout(() => { - setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) - }, 500); - }; + }, 500) + } const handleContentSearch = e => { let searchedContentText = removeExtraSpace(e.target.value.toLowerCase()) - setInputContentSearch(searchedContentText) - + setInputContentSearch(searchedContentText) + searchContentTimeout = setTimeout(() => { - setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })); + setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })) - }, 500); + }, 500) } @@ -282,18 +336,18 @@ const TicketsManager = () => { } const handleChangeTab = (e, newValue) => { - setTab(newValue); - }; + setTab(newValue) + } const handleChangeTabOpen = (e, newValue) => { - setTabOpen(newValue); - }; + setTabOpen(newValue) + } const applyPanelStyle = (status) => { if (tabOpen !== status) { - return { width: 0, height: 0 }; + return { width: 0, height: 0 } } - }; + } return ( @@ -346,7 +400,7 @@ const TicketsManager = () => { {/* setShowContentSearch(prev => !prev)}> */} - handleOpenTooltipSearch()} onClose={() => handleCloseTooltipSearch()} @@ -360,22 +414,22 @@ const TicketsManager = () => { - + */} { // showContentSearch ? - (showContentSearch && searchParam.searchParam.length >= 4) ? - (
- - handleContentSearch(e)} - /> -
) : null + // (showContentSearch /*&& searchParam.searchParam.length >= 4*/) ? + // (
+ // + // handleContentSearch(e)} + // /> + //
) : null } ) : ( @@ -448,7 +502,25 @@ const TicketsManager = () => { } value={"pending"} - /> + />{ + (settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') && + + + + {/* */} + + + + + } { - ); -}; + ) +} -export default TicketsManager; \ No newline at end of file +export default TicketsManager \ No newline at end of file diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 472565b..02acdfc 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -97,7 +97,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { } else { - if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled') { + if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled' && !user.transferToOtherQueues) { setQueues(_queues) } else { @@ -190,7 +190,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { try { - if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled') { + if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled' && !user.transferToOtherQueues) { const { data } = await api.get(`/users/all`, { params: { userId: user.id }, }) @@ -202,7 +202,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { else { const { data } = await api.get(`/users/all`, { - params: { profile: 'user' }, + params: { profile: 'user', transferToOtherQueues: user.transferToOtherQueues }, }) setUsers(data.users) diff --git a/frontend/src/components/UserModal/index.js b/frontend/src/components/UserModal/index.js index cd568e8..6520545 100644 --- a/frontend/src/components/UserModal/index.js +++ b/frontend/src/components/UserModal/index.js @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useEffect, useContext } from "react" -import * as Yup from "yup"; -import { Formik, Form, Field } from "formik"; -import { toast } from "react-toastify"; +import * as Yup from "yup" +import { Formik, Form, Field } from "formik" +import { toast } from "react-toastify" import { Button, @@ -18,20 +18,21 @@ import { TextField, InputAdornment, IconButton - } from '@material-ui/core'; +} from '@material-ui/core' -import { Visibility, VisibilityOff } from '@material-ui/icons'; +import { Visibility, VisibilityOff } from '@material-ui/icons' -import { makeStyles } from "@material-ui/core/styles"; -import { green } from "@material-ui/core/colors"; +import { makeStyles } from "@material-ui/core/styles" +import { green } from "@material-ui/core/colors" -import { i18n } from "../../translate/i18n"; +import { i18n } from "../../translate/i18n" -import api from "../../services/api"; -import toastError from "../../errors/toastError"; -import QueueSelect from "../QueueSelect"; -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../Can"; +import api from "../../services/api" +import toastError from "../../errors/toastError" +import QueueSelect from "../QueueSelect" +import { AuthContext } from "../../context/Auth/AuthContext" +import { Can } from "../Can" +import Switch from '@mui/material/Switch' const useStyles = makeStyles(theme => ({ root: { @@ -61,7 +62,7 @@ const useStyles = makeStyles(theme => ({ margin: theme.spacing(1), minWidth: 120, }, -})); +})) const UserSchema = Yup.object().shape({ name: Yup.string() @@ -71,60 +72,87 @@ const UserSchema = Yup.object().shape({ password: Yup.string().min(5, "Too Short!").max(50, "Too Long!"), email: Yup.string().min(2, "Too Short!") - .max(50, "Too Long!") - .required("Required"), - - // email: Yup.string().email("Invalid email").required("Required"), -}); + .max(50, "Too Long!") + .required("Required"), -const UserModal = ({ open, onClose, userId }) => { - const classes = useStyles(); + // email: Yup.string().email("Invalid email").required("Required"), +}) + +const UserModal = ({ open, onClose, userId, }) => { + const classes = useStyles() const initialState = { name: "", email: "", - password: "", - positionCompany: "", - position: "", + password: "", profile: "user", - }; + } - const { user: loggedInUser } = useContext(AuthContext); - - const [user, setUser] = useState(initialState); - const [selectedQueueIds, setSelectedQueueIds] = useState([]); - const [showPassword, setShowPassword] = useState(false); + const { user: loggedInUser } = useContext(AuthContext) + const [user, setUser] = useState(initialState) + const [selectedQueueIds, setSelectedQueueIds] = useState([]) + const [showPassword, setShowPassword] = useState(false) + const [positions, setPositions] = useState([]) + const [selectedPosition, setSelectedPosition] = useState('') + const [checked, setChecked] = useState(false) useEffect(() => { const fetchUser = async () => { - if (!userId) return; + setSelectedPosition('') + if (!userId) return try { - const { data } = await api.get(`/users/${userId}`); + const { data } = await api.get(`/users/${userId}`) setUser(prevState => { - return { ...prevState, ...data }; - }); - const userQueueIds = data.queues?.map(queue => queue.id); - setSelectedQueueIds(userQueueIds); - } catch (err) { - toastError(err); - } - }; + return { ...prevState, ...data } + }) + const userQueueIds = data.queues?.map(queue => queue.id) + setSelectedQueueIds(userQueueIds) - fetchUser(); - }, [userId, open]); + if (data?.positionId) + setSelectedPosition(data.positionId) + else + setSelectedPosition('') + + + if(data.transferToOtherQueues) setChecked(data.transferToOtherQueues); + } catch (err) { + toastError(err) + } + } + + fetchUser() + }, [userId, open]) + + useEffect(() => { + const fetchUser = async () => { + try { + const { data } = await api.get(`/positions`) + setPositions(data.positions) + } catch (err) { + toastError(err) + } + } + + fetchUser() + }, [userId, open]) const handleClose = () => { - onClose(); - setUser(initialState); - }; + onClose() + setUser(initialState) + setChecked(false); + } + + const handleChange = (event) => { + setChecked(event.target.checked) + } const handleSaveUser = async values => { - const userData = { ...values, queueIds: selectedQueueIds }; + const userData = { ...values, queueIds: selectedQueueIds, positionId: selectedPosition, transferToOtherQueues: checked} try { if (userId) { - const user = await api.put(`/users/${userId}`, userData); + const user = await api.put(`/users/${userId}`, userData) console.log('USER: ', user.data) @@ -149,14 +177,14 @@ const UserModal = ({ open, onClose, userId }) => { } else { - await api.post("/users", userData); + await api.post("/users", userData) } - toast.success(i18n.t("userModal.success")); + toast.success(i18n.t("userModal.success")) } catch (err) { - toastError(err); + toastError(err) } - handleClose(); - }; + handleClose() + } return (
@@ -178,9 +206,9 @@ const UserModal = ({ open, onClose, userId }) => { validationSchema={UserSchema} onSubmit={(values, actions) => { setTimeout(() => { - handleSaveUser(values); - actions.setSubmitting(false); - }, 400); + handleSaveUser(values) + actions.setSubmitting(false) + }, 400) }} > {({ touched, errors, isSubmitting }) => ( @@ -208,41 +236,41 @@ const UserModal = ({ open, onClose, userId }) => { helperText={touched.password && errors.password} type={showPassword ? 'text' : 'password'} InputProps={{ - endAdornment: ( - - setShowPassword((e) => !e)} - > - {showPassword ? : } - - - ) + endAdornment: ( + + setShowPassword((e) => !e)} + > + {showPassword ? : } + + + ) }} fullWidth />
- -
- +
+ + { />
- + { /> )} /> + +
+ + {"Cargo"} + + +
+
- ); -}; + ) +} -export default UserModal; +export default UserModal diff --git a/frontend/src/components/VcardPreview/index.js b/frontend/src/components/VcardPreview/index.js index f2a63be..8f5d0b6 100644 --- a/frontend/src/components/VcardPreview/index.js +++ b/frontend/src/components/VcardPreview/index.js @@ -1,29 +1,29 @@ -import React, { useEffect, useState, useContext } from 'react'; -import { useHistory } from "react-router-dom"; -import toastError from "../../errors/toastError"; -import api from "../../services/api"; +import React, { useEffect, useState, useContext } from 'react' +import { useHistory } from "react-router-dom" +import toastError from "../../errors/toastError" +import api from "../../services/api" -import Avatar from "@material-ui/core/Avatar"; -import Typography from "@material-ui/core/Typography"; -import Grid from "@material-ui/core/Grid"; +import Avatar from "@material-ui/core/Avatar" +import Typography from "@material-ui/core/Typography" +import Grid from "@material-ui/core/Grid" -import { AuthContext } from "../../context/Auth/AuthContext"; +import { AuthContext } from "../../context/Auth/AuthContext" -import { Button, Divider, } from "@material-ui/core"; +import { Button, Divider, } from "@material-ui/core" -const VcardPreview = ({ contact, numbers }) => { - const history = useHistory(); - const { user } = useContext(AuthContext); +const VcardPreview = ({ contact, numbers, multi_vCard }) => { + const history = useHistory() + const { user } = useContext(AuthContext) const [selectedContact, setContact] = useState({ name: "", number: 0, profilePicUrl: "" - }); + }) useEffect(() => { const delayDebounceFn = setTimeout(() => { - const fetchContacts = async () => { + const fetchContacts = async () => { try { let contactObj = { name: contact, @@ -31,19 +31,20 @@ const VcardPreview = ({ contact, numbers }) => { number: numbers !== undefined && numbers.replace(/\D/g, ""), email: "" } - - const { data } = await api.post("/contact", contactObj); + + const { data } = await api.post("/contact", contactObj) + setContact(data) } catch (err) { console.log(err) - toastError(err); + toastError(err) } - }; - fetchContacts(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [contact, numbers]); + } + fetchContacts() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [contact, numbers]) const handleNewChat = async () => { try { @@ -51,10 +52,10 @@ const VcardPreview = ({ contact, numbers }) => { contactId: selectedContact.id, userId: user.id, status: "open", - }); - history.push(`/tickets/${ticket.id}`); + }) + history.push(`/tickets/${ticket.id}`) } catch (err) { - toastError(err); + toastError(err) } } @@ -73,19 +74,23 @@ const VcardPreview = ({ contact, numbers }) => { - + {!multi_vCard && } + + + {/* {multi_vCard && } */} + - ); + ) -}; +} -export default VcardPreview; \ No newline at end of file +export default VcardPreview \ No newline at end of file diff --git a/frontend/src/context/Auth/AuthContext.js b/frontend/src/context/Auth/AuthContext.js index 5368f33..fbeda88 100644 --- a/frontend/src/context/Auth/AuthContext.js +++ b/frontend/src/context/Auth/AuthContext.js @@ -5,14 +5,14 @@ import useAuth from '../../hooks/useAuth.js' const AuthContext = createContext() const AuthProvider = ({ children }) => { - const { loading, user, isAuth, handleLogin, handleLogout, setSetting } = + const { loading, user, isAuth, handleLogin, handleLogout, setSetting, setting, getSettingValue } = useAuth() //{ return ( {children} diff --git a/frontend/src/context/CountTicketMsgProvider/CountTicketMsgProvider.js b/frontend/src/context/CountTicketMsgProvider/CountTicketMsgProvider.js new file mode 100644 index 0000000..0af146c --- /dev/null +++ b/frontend/src/context/CountTicketMsgProvider/CountTicketMsgProvider.js @@ -0,0 +1,17 @@ +import React, { useState, createContext } from "react" + +const countTicketMsgContext = createContext() + + +const CountTicketMsgProvider = ({ children }) => { + + const [countTicketMsg, setCountTicketMsg] = useState(0) + + return ( + + {children} + + ) +} + +export { countTicketMsgContext, CountTicketMsgProvider } \ No newline at end of file diff --git a/frontend/src/context/Setting/SettingContext.js b/frontend/src/context/Setting/SettingContext.js new file mode 100644 index 0000000..c3a464c --- /dev/null +++ b/frontend/src/context/Setting/SettingContext.js @@ -0,0 +1,19 @@ +import React, { createContext } from 'react' + +import useAuth from '../../hooks/useAuth.js/index.js' + +const SettingContext = createContext() + +const SettingProvider = ({ children }) => { + const { loading, user, isAuth, handleLogin, handleLogout, setSetting } = useAuth() + + return ( + + {children} + + ) +} + +export { SettingContext, SettingProvider } diff --git a/frontend/src/context/TicketsProvider/TicketsProvider.js b/frontend/src/context/TicketsProvider/TicketsProvider.js new file mode 100644 index 0000000..11b6ec9 --- /dev/null +++ b/frontend/src/context/TicketsProvider/TicketsProvider.js @@ -0,0 +1,17 @@ +import React, { useState, createContext } from "react" + +const ticketsContext = createContext() + + +const TicketsProvider = ({ children }) => { + + const [tickets, setTickets] = useState(0) + + return ( + + {children} + + ) +} + +export { ticketsContext, TicketsProvider } \ No newline at end of file diff --git a/frontend/src/hooks/useAuth.js/index.js b/frontend/src/hooks/useAuth.js/index.js index 80db7f4..e2da363 100644 --- a/frontend/src/hooks/useAuth.js/index.js +++ b/frontend/src/hooks/useAuth.js/index.js @@ -16,6 +16,11 @@ const useAuth = () => { const [setting, setSetting] = useState({}) + const getSettingValue = (key) => { + return setting?.find((s) => s?.key === key)?.value + } + + api.interceptors.request.use( (config) => { const token = localStorage.getItem('token') @@ -153,6 +158,7 @@ const useAuth = () => { handleLogout, setting, setSetting, + getSettingValue } } diff --git a/frontend/src/hooks/useTickets/index.js b/frontend/src/hooks/useTickets/index.js index 7dfe366..4ce180b 100644 --- a/frontend/src/hooks/useTickets/index.js +++ b/frontend/src/hooks/useTickets/index.js @@ -1,7 +1,7 @@ -import { useState, useEffect } from "react"; -import toastError from "../../errors/toastError"; +import { useState, useEffect } from "react" +import toastError from "../../errors/toastError" -import api from "../../services/api"; +import api from "../../services/api" const useTickets = ({ searchParam, @@ -15,21 +15,21 @@ const useTickets = ({ unlimited, tab }) => { - const [loading, setLoading] = useState(true); - const [hasMore, setHasMore] = useState(false); - const [tickets, setTickets] = useState([]); - + const [loading, setLoading] = useState(true) + const [hasMore, setHasMore] = useState(false) + const [tickets, setTickets] = useState([]) + const [remoteTicketsControll, setRemoteTicketsControll] = useState([]) useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchTickets = async () => { try { - if ((tab === 'search') && ( !searchParam || searchParam.trim().length === 0 || searchParam.trim().length >40 || searchParam.endsWith(' '))) { + if ((tab === 'search') && (!searchParam || searchParam.trim().length === 0 || searchParam.trim().length > 40 || searchParam.endsWith(' '))) { return } @@ -45,24 +45,26 @@ const useTickets = ({ withUnreadMessages, unlimited }, - }); + }) + setTickets(data.tickets) + setHasMore(data.hasMore) + setLoading(false) + + if (data?.remoteTicketsControll) { + setRemoteTicketsControll(data.remoteTicketsControll.map(t => +t)) + } - setTickets(data.tickets); - setHasMore(data.hasMore); - setLoading(false); - - } catch (err) { - setLoading(false); - toastError(err); + setLoading(false) + toastError(err) } - }; - fetchTickets(); - }, 500); - return () => clearTimeout(delayDebounceFn); + } + fetchTickets() + }, 500) + return () => clearTimeout(delayDebounceFn) }, [ searchParam, searchParamContent, @@ -74,9 +76,9 @@ const useTickets = ({ withUnreadMessages, tab, unlimited - ]); + ]) - return { tickets, loading, hasMore }; -}; + return { tickets, loading, hasMore, remoteTicketsControll } +} -export default useTickets; +export default useTickets diff --git a/frontend/src/layout/MainListItems.js b/frontend/src/layout/MainListItems.js index 1e7611c..03339d2 100644 --- a/frontend/src/layout/MainListItems.js +++ b/frontend/src/layout/MainListItems.js @@ -24,6 +24,8 @@ import PeopleAltOutlinedIcon from '@material-ui/icons/PeopleAltOutlined' import ContactPhoneOutlinedIcon from '@material-ui/icons/ContactPhoneOutlined' import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined' import QuestionAnswerOutlinedIcon from '@material-ui/icons/QuestionAnswerOutlined' +import RateReviewOutlinedIcon from '@material-ui/icons/RateReviewOutlined' +import PlaylistAddIcon from '@material-ui/icons/PlaylistAdd' import { i18n } from '../translate/i18n' import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext' @@ -168,6 +170,18 @@ const MainListItems = (props) => { {i18n.t("mainDrawer.listItems.administration")} + } + /> + + } + /> + { return value } - useEffect(() => { + useEffect(() => { const delayDebounceFn = setTimeout(() => { @@ -195,7 +195,7 @@ const Contacts = () => { } }) - if (insertOnQueue && insertOnQueue.data) { + if (insertOnQueue && insertOnQueue.data) { setZipFile(insertOnQueue.data.app.file) setOnQueueProcessStatus(insertOnQueue.data.app.status) @@ -243,8 +243,7 @@ const Contacts = () => { return } - const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, }) - + const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber, userId: user.id }, }) dispatch({ type: "LOAD_CONTACTS", payload: data.contacts }) setHasMore(data.hasMore) @@ -342,14 +341,14 @@ const Contacts = () => { // if (isMounted.current) setLoading(false) }, [history]) - const handleOpenCreateTicketModal = (contactId) => { + const handleOpenCreateTicketModal = (contactId) => { setSelectedContactId(contactId) - if (getSettingValue('whatsaAppCloudApi') === 'disabled' && user?.queues?.length === 1){ + if (getSettingValue('whatsaAppCloudApi') === 'disabled' && user?.queues?.length === 1) { handleSaveTicketOneQueue(contactId, user.id, user.queues[0].id) } - else{ + else { setIsCreateTicketModalOpen(true) } @@ -359,8 +358,8 @@ const Contacts = () => { const handleCloseCreateTicketModal = () => { setIsCreateTicketModalOpen(false) - } - + } + const hadleEditContact = (contactId) => { setSelectedContactId(contactId) @@ -537,7 +536,7 @@ const Contacts = () => { open={contactModalOpen} onClose={handleCloseContactModal} aria-labelledby="form-dialog-title" - contactId={selectedContactId} + contactId={selectedContactId} > { const theme = useTheme(); const date = useRef(new Date().toISOString()); - let { tickets } = useTickets({ date: date.current, unlimited: "current" }); + const queueIds = JSON.stringify( props.selectedQueue) || {}; + let {tickets} = useTickets({ date: date.current, unlimited: "current", queueIds }); - const [chartData, setChartData] = useState([ + const modelChar = [ { time: "08:00", amount: 0 }, { time: "09:00", amount: 0 }, { time: "10:00", amount: 0 }, @@ -35,11 +36,12 @@ const Chart = (props) => { { time: "17:00", amount: 0 }, { time: "18:00", amount: 0 }, { time: "19:00", amount: 0 }, - ]); + ] + const [chartData, setChartData] = useState(modelChar); useEffect(() => { setChartData(prevState => { - let aux = [...prevState]; + let aux = modelChar; aux.forEach(a => { tickets.forEach(ticket => { format(startOfHour(parseISO(ticket.createdAt)), "HH:mm") === a.time && a.amount++; }); diff --git a/frontend/src/pages/Dashboard/PieChart.js b/frontend/src/pages/Dashboard/PieChart.js new file mode 100644 index 0000000..b1b6c46 --- /dev/null +++ b/frontend/src/pages/Dashboard/PieChart.js @@ -0,0 +1,134 @@ +import { Box } from '@material-ui/core'; +import React from 'react'; +import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; +import { PieChart as RechartsPieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'; + +import Title from './Title'; + +const generateDataExample = (amount) => { + const arr = [] + for (let i = 1; i <= amount; i++) { + arr.push({ + "id": i, + "name": `Exemplo ${i}`, + "count": Math.floor(Math.random() * 10 + 2) + }) + } + + return arr +} + +const dataExample = generateDataExample(20) + +const COLORS = [ + '#0088FE', // Azul escuro + '#00C49F', // Verde menta + '#FFBB28', // Laranja escuro + '#FF8042', // Vermelho escuro + '#9D38BD', // Roxo escuro + '#FFD166', // Laranja claro + '#331F00', // Marrom escuro + '#C0FFC0', // Verde Claro + '#C4E538', // Verde-amarelo vibrante + '#A2A2A2', // Cinza claro + '#FFF700', // Amarelo Canário + '#FF69B4', // Rosa Flamingo + '#87CEEB', // Azul Celeste + '#228B22', // Verde Esmeralda + '#9B59B6', // Roxo Ametista + '#FF9933', // Laranja Tangerina + '#FF7F50', // Coral Vivo + '#00CED1', // Verde Água + '#000080', // Azul Marinho + '#FFDB58', // Amarelo Mostarda +]; + +const RADIAN = Math.PI / 180; + +const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, count }) => { + const radius = innerRadius + (outerRadius - innerRadius) * 0.80; + const x = cx + radius * Math.cos(-midAngle * RADIAN); + const y = cy + radius * Math.sin(-midAngle * RADIAN); + + return ( + cx ? 'start' : 'end'} dominantBaseline="central"> + {count} + + ); +}; + +/** + * @param data array de objetos no formato + * { + "id": number | string, + "name": string, + "count": number + * } + */ +const PieChart = ({ data = dataExample }) => { + return ( + + + + Tickets encerramento + + + + + {data.map((entry, index) => ( + + ))} + + + + + + + {data.map((entry, index) => { + return ( + + + {entry.name} + + ) + })} + + + ); + +} + +export default PieChart \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/index.js b/frontend/src/pages/Dashboard/index.js index 83d26da..3fa4e2d 100644 --- a/frontend/src/pages/Dashboard/index.js +++ b/frontend/src/pages/Dashboard/index.js @@ -1,4 +1,4 @@ -import React, { useContext, useReducer, useEffect, useState } from "react" +import React, { useContext, useReducer, useEffect, useState, useCallback } from "react" import { addHours, addMinutes, addSeconds, intervalToDuration } from "date-fns" @@ -11,10 +11,12 @@ import Tooltip from "@mui/material/Tooltip" import Zoom from "@mui/material/Zoom" import IconButton from "@mui/material/IconButton" import Info from "@material-ui/icons/Info" +import SelectField from "../../components/Report/SelectField" import { AuthContext } from "../../context/Auth/AuthContext" // import { i18n } from "../../translate/i18n"; import Chart from "./Chart" +import PieChart from "./PieChart" import openSocket from "socket.io-client" import api from "../../services/api" @@ -31,7 +33,7 @@ const useStyles = makeStyles((theme) => ({ display: "flex", overflow: "auto", flexDirection: "column", - height: 240, + height: 280, }, customFixedHeightPaper: { padding: theme.spacing(2), @@ -108,7 +110,7 @@ const useStyles = makeStyles((theme) => ({ var _fifo -const sumOnlineTimeNow = (oldOnlineTimeSum) => { +const sumOnlineTimeNow = (oldOnlineTimeSum) => { let onlineTime = new Date() @@ -138,7 +140,7 @@ const sumOnlineTimeNow = (oldOnlineTimeSum) => { const isoDate = new Date(onlineTime) - const newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' ') + const newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' ') return newOnlinetime } @@ -207,9 +209,9 @@ const reducer = (state, action) => { if ("onlineTime" in onlineUser) { - if ("sumOnlineTime" in state[index]) { + if ("sumOnlineTime" in state[index]) { - state[index].sumOnlineTime.sum = onlineUser.onlineTime.split(" ")[1] + state[index].sumOnlineTime.sum = onlineUser.onlineTime.split(" ")[1] } else if (!("sumOnlineTime" in state[index])) { @@ -253,12 +255,15 @@ const reducer = (state, action) => { } const Dashboard = () => { + const { user } = useContext(AuthContext) const classes = useStyles() const [usersOnlineInfo, dispatch] = useReducer(reducer, []) const [ticketStatusChange, setStatus] = useState() const [ticketsStatus, setTicktsStatus] = useState({ open: 0, openAll: 0, pending: 0, closed: 0 }) + const [ticketStatusChatEnd, setTicketStatusChatEnd] = useState([]) - const { user } = useContext(AuthContext) + const userQueueIds = user.queues?.map((q) => q.id); + const [selectedQueue, setSelectedQueue] = useState(userQueueIds || []); useEffect(() => { dispatch({ type: "RESET" }) @@ -283,15 +288,20 @@ const Dashboard = () => { try { let date = new Date().toLocaleDateString("pt-BR").split("/") let dateToday = `${date[2]}-${date[1]}-${date[0]}` - - const { data } = await api.get("/reports/user/services", { - params: { userId: null, startDate: dateToday, endDate: dateToday }, - }) - // console.log('data.data: ', data.usersProfile) + const { data } = await api.get("/reports/user/services", { + params: { userId: null, startDate: dateToday, endDate: dateToday, userQueues: selectedQueue }, + }) dispatch({ type: "RESET" }) dispatch({ type: "LOAD_QUERY", payload: data.usersProfile }) + + const { data: ticketStatusChatEndData } = await api.get("/reports/count/statusChatEnd", { + params: { startDate: dateToday, endDate: dateToday, userQueues: selectedQueue }, + }) + + setTicketStatusChatEnd(ticketStatusChatEndData.reportStatusChatEnd) + } catch (err) { } @@ -300,7 +310,7 @@ const Dashboard = () => { fetchQueries() }, 500) return () => clearTimeout(delayDebounceFn) - }, []) + }, [selectedQueue]) useEffect(() => { @@ -319,7 +329,7 @@ const Dashboard = () => { if (usersOnlineInfo[i].statusOnline && usersOnlineInfo[i].statusOnline.status === 'online') { let onlineTimeCurrent = sumOnlineTimeNow({ onlineTime: usersOnlineInfo[i].statusOnline.onlineTime, updatedAt: usersOnlineInfo[i].statusOnline.updatedAt }) - + dispatch({ type: "UPDATE_STATUS_ONLINE", payload: { userId: usersOnlineInfo[i].id, status: usersOnlineInfo[i].statusOnline.status, onlineTime: onlineTimeCurrent } }) } @@ -356,7 +366,7 @@ const Dashboard = () => { }) socket.on("onlineStatus", (data) => { - if (data.action === "logout" || data.action === "update") { + if (data.action === "logout" || data.action === "update") { dispatch({ type: "UPDATE_STATUS_ONLINE", payload: data.userOnlineTime }) } else if (data.action === "delete") { @@ -374,6 +384,18 @@ const Dashboard = () => { socket.disconnect() } }, []) + + const handleSelectedQueue = useCallback((queueSelected) => { + if(queueSelected !== 'All'){ + const queueIndex = user?.queues?.findIndex((q) => q.id === parseInt(queueSelected)); + const queueIds = [] + queueIds.push(user?.queues[queueIndex]?.id); + setSelectedQueue(queueIds); + }else{ + const queueIds = user?.queues?.map((queue) => queue.id); + setSelectedQueue(queueIds); + } + },[user, setSelectedQueue]) useEffect(() => { if (ticketStatusChange === "") return @@ -384,17 +406,17 @@ const Dashboard = () => { let dateToday = `${date[2]}-${date[1]}-${date[0]}` const _open = await api.get("/tickets/count", { - params: { status: "open", date: dateToday }, + params: { status: "open", date: dateToday, queueIds: selectedQueue }, }) const _closed = await api.get("/tickets/count", { - params: { status: "closed", date: dateToday }, + params: { status: "closed", date: dateToday, queueIds: selectedQueue }, }) const _pending = await api.get("/tickets/count", { - params: { status: "pending" }, + params: { status: "pending", queueIds: selectedQueue }, }) const _openAll = await api.get("/tickets/count", { - params: { status: "open" }, + params: { status: "open", queueIds: selectedQueue }, }) setTicktsStatus({ open: _open.data.count, @@ -413,7 +435,7 @@ const Dashboard = () => { fetchQueries() }, 500) return () => clearTimeout(delayDebounceFn) - }, [ticketStatusChange]) + }, [ticketStatusChange, selectedQueue]) return ( { + + { + return { 'value': obj.id, 'label': obj.name } + })} /> + {
- - - - + + + + + + + + + + + diff --git a/frontend/src/pages/Position/index.js b/frontend/src/pages/Position/index.js new file mode 100644 index 0000000..5eb50ea --- /dev/null +++ b/frontend/src/pages/Position/index.js @@ -0,0 +1,286 @@ +import React, { useState, useContext, useEffect, useReducer } from "react" +import openSocket from "socket.io-client" + +import { + Button, + IconButton, + makeStyles, + Paper, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + InputAdornment, + TextField, +} from "@material-ui/core" +import { Edit, DeleteOutline } from "@material-ui/icons" +import SearchIcon from "@material-ui/icons/Search" + +import MainContainer from "../../components/MainContainer" +import MainHeader from "../../components/MainHeader" +import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper" +import Title from "../../components/Title" + +import api from "../../services/api" +import { i18n } from "../../translate/i18n" +import TableRowSkeleton from "../../components/TableRowSkeleton" +import PositionModal from "../../components/PositionModal" +import ConfirmationModal from "../../components/ConfirmationModal" +import { toast } from "react-toastify" +import toastError from "../../errors/toastError" +import { AuthContext } from '../../context/Auth/AuthContext' + +const reducer = (state, action) => { + if (action.type === "LOAD_POSITIONS") { + const positions = action.payload + const newPositions = [] + + positions.forEach((position) => { + const positionIndex = state.findIndex((q) => q.id === position.id) + if (positionIndex !== -1) { + state[positionIndex] = position + } else { + newPositions.push(position) + } + }) + + return [...state, ...newPositions] + } + + if (action.type === "UPDATE_POSITIONS") { + const position = action.payload + const positionIndex = state.findIndex((q) => q.id === position.id) + + if (positionIndex !== -1) { + state[positionIndex] = position + return [...state] + } else { + return [position, ...state] + } + } + + if (action.type === "DELETE_POSITIONS") { + const positionId = action.payload + + const positionIndex = state.findIndex((q) => q.id === positionId) + if (positionIndex !== -1) { + state.splice(positionIndex, 1) + } + return [...state] + } + + if (action.type === "RESET") { + return [] + } +} + +const useStyles = makeStyles((theme) => ({ + mainPaper: { + flex: 1, + padding: theme.spacing(1), + overflowY: "scroll", + ...theme.scrollbarStyles, + }, +})) + +const Position = () => { + const classes = useStyles() + + const [loading, setLoading] = useState(false) + const [pageNumber, setPageNumber] = useState(1) + const [searchParam, setSearchParam] = useState("") + const [positions, dispatch] = useReducer(reducer, []) + const [selectedPosition, setSelectedPosition] = useState(null) + const [positionModalOpen, setPositionModalOpen] = useState(false) + const [deletingPosition, setDeletingPosition] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + const [hasMore, setHasMore] = useState(false) + const { user, } = useContext(AuthContext) + + + useEffect(() => { + dispatch({ type: "RESET" }) + setPageNumber(1) + }, [searchParam]) + + useEffect(() => { + setLoading(true) + const delayDebounceFn = setTimeout(() => { + const fetchPositions = async () => { + try { + const { data } = await api.get("/positions/", { + params: { searchParam, pageNumber }, + }) + dispatch({ type: "LOAD_POSITIONS", payload: data.positions }) + setHasMore(data.hasMore) + setLoading(false) + } catch (err) { + toastError(err) + } + } + fetchPositions() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber]) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on("position", (data) => { + if (data.action === "update" || data.action === "create") { + dispatch({ type: "UPDATE_POSITIONS", payload: data.position }) + } + + if (data.action === "delete") { + dispatch({ + type: "DELETE_POSITIONS", + payload: +data.positionId, + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + const handleSearch = (event) => { + setSearchParam(event.target.value.toLowerCase()) + } + + const handleOpenPositionModal = () => { + setSelectedPosition(null) + setPositionModalOpen(true) + } + + const handleClosePositionModal = () => { + setSelectedPosition(null) + setPositionModalOpen(false) + } + + const handleEditPosition = (position) => { + setSelectedPosition(position) + setPositionModalOpen(true) + } + + const handleDeletePosition = async (positionId) => { + try { + await api.delete(`/positions/${positionId}`) + toast.success("Cargo deletado com sucesso") + } catch (err) { + toastError(err) + } + setDeletingPosition(null) + setSearchParam("") + setPageNumber(1) + } + + const loadMore = () => { + setPageNumber((prevState) => prevState + 1) + } + + const handleScroll = (e) => { + if (!hasMore || loading) return + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget + if (scrollHeight - (scrollTop + 100) < clientHeight) { + loadMore() + } + } + + return ( + + handleDeletePosition(deletingPosition.id)} + > + {i18n.t("quickAnswers.confirmationModal.deleteMessage")} + + + + {"Cargo"} + + + + + ), + }} + /> + + + + + + + + + Cargo + + + {i18n.t("quickAnswers.table.actions")} + + + + + <> + {positions.map((position) => ( + + {position.name} + + handleEditPosition(position)} + > + + + + { + setConfirmModalOpen(true) + setDeletingPosition(position) + }} + > + + + + + ))} + {loading && } + + +
+
+
+ ) +} + +export default Position diff --git a/frontend/src/pages/Queues/index.js b/frontend/src/pages/Queues/index.js index 570c3f2..a2ffea1 100644 --- a/frontend/src/pages/Queues/index.js +++ b/frontend/src/pages/Queues/index.js @@ -91,7 +91,7 @@ const reducer = (state, action) => { const Queues = () => { const classes = useStyles() - const { user } = useContext(AuthContext) + const { user, setting, getSettingValue } = useContext(AuthContext) const [queues, dispatch] = useReducer(reducer, []) const [loading, setLoading] = useState(false) @@ -102,6 +102,11 @@ const Queues = () => { const [settings, setSettings] = useState([]) + + useEffect(() => { + setSettings(setting) + }, [setting]) + useEffect(() => { ; (async () => { setLoading(true) @@ -115,25 +120,7 @@ const Queues = () => { setLoading(false) } })() - }, []) - - useEffect(() => { - const fetchSession = async () => { - try { - const { data } = await api.get('/settings') - setSettings(data.settings) - } catch (err) { - toastError(err) - } - } - fetchSession() - }, []) - - const getSettingValue = (key) => { - const { value } = settings.find((s) => s.key === key) - - return value - } + }, []) useEffect(() => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL) diff --git a/frontend/src/pages/QuickAnswers/index.js b/frontend/src/pages/QuickAnswers/index.js index 87958ea..5a71233 100644 --- a/frontend/src/pages/QuickAnswers/index.js +++ b/frontend/src/pages/QuickAnswers/index.js @@ -1,5 +1,5 @@ -import React, { useState, useEffect, useReducer } from "react"; -import openSocket from "socket.io-client"; +import React, { useState, useContext, useEffect, useReducer } from "react" +import openSocket from "socket.io-client" import { Button, @@ -13,66 +13,67 @@ import { TableRow, InputAdornment, TextField, -} from "@material-ui/core"; -import { Edit, DeleteOutline } from "@material-ui/icons"; -import SearchIcon from "@material-ui/icons/Search"; +} from "@material-ui/core" +import { Edit, DeleteOutline } from "@material-ui/icons" +import SearchIcon from "@material-ui/icons/Search" -import MainContainer from "../../components/MainContainer"; -import MainHeader from "../../components/MainHeader"; -import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; -import Title from "../../components/Title"; +import MainContainer from "../../components/MainContainer" +import MainHeader from "../../components/MainHeader" +import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper" +import Title from "../../components/Title" -import api from "../../services/api"; -import { i18n } from "../../translate/i18n"; -import TableRowSkeleton from "../../components/TableRowSkeleton"; -import QuickAnswersModal from "../../components/QuickAnswersModal"; -import ConfirmationModal from "../../components/ConfirmationModal"; -import { toast } from "react-toastify"; -import toastError from "../../errors/toastError"; +import api from "../../services/api" +import { i18n } from "../../translate/i18n" +import TableRowSkeleton from "../../components/TableRowSkeleton" +import QuickAnswersModal from "../../components/QuickAnswersModal" +import ConfirmationModal from "../../components/ConfirmationModal" +import { toast } from "react-toastify" +import toastError from "../../errors/toastError" +import { AuthContext } from '../../context/Auth/AuthContext' const reducer = (state, action) => { if (action.type === "LOAD_QUICK_ANSWERS") { - const quickAnswers = action.payload; - const newQuickAnswers = []; + const quickAnswers = action.payload + const newQuickAnswers = [] quickAnswers.forEach((quickAnswer) => { - const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id); + const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id) if (quickAnswerIndex !== -1) { - state[quickAnswerIndex] = quickAnswer; + state[quickAnswerIndex] = quickAnswer } else { - newQuickAnswers.push(quickAnswer); + newQuickAnswers.push(quickAnswer) } - }); + }) - return [...state, ...newQuickAnswers]; + return [...state, ...newQuickAnswers] } if (action.type === "UPDATE_QUICK_ANSWERS") { - const quickAnswer = action.payload; - const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id); + const quickAnswer = action.payload + const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id) if (quickAnswerIndex !== -1) { - state[quickAnswerIndex] = quickAnswer; - return [...state]; + state[quickAnswerIndex] = quickAnswer + return [...state] } else { - return [quickAnswer, ...state]; + return [quickAnswer, ...state] } } if (action.type === "DELETE_QUICK_ANSWERS") { - const quickAnswerId = action.payload; + const quickAnswerId = action.payload - const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswerId); + const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswerId) if (quickAnswerIndex !== -1) { - state.splice(quickAnswerIndex, 1); + state.splice(quickAnswerIndex, 1) } - return [...state]; + return [...state] } if (action.type === "RESET") { - return []; + return [] } -}; +} const useStyles = makeStyles((theme) => ({ mainPaper: { @@ -81,117 +82,118 @@ const useStyles = makeStyles((theme) => ({ overflowY: "scroll", ...theme.scrollbarStyles, }, -})); +})) const QuickAnswers = () => { - const classes = useStyles(); + const classes = useStyles() + + const [loading, setLoading] = useState(false) + const [pageNumber, setPageNumber] = useState(1) + const [searchParam, setSearchParam] = useState("") + const [quickAnswers, dispatch] = useReducer(reducer, []) + const [selectedQuickAnswers, setSelectedQuickAnswers] = useState(null) + const [quickAnswersModalOpen, setQuickAnswersModalOpen] = useState(false) + const [deletingQuickAnswers, setDeletingQuickAnswers] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + const [hasMore, setHasMore] = useState(false) + const { user, } = useContext(AuthContext) - const [loading, setLoading] = useState(false); - const [pageNumber, setPageNumber] = useState(1); - const [searchParam, setSearchParam] = useState(""); - const [quickAnswers, dispatch] = useReducer(reducer, []); - const [selectedQuickAnswers, setSelectedQuickAnswers] = useState(null); - const [quickAnswersModalOpen, setQuickAnswersModalOpen] = useState(false); - const [deletingQuickAnswers, setDeletingQuickAnswers] = useState(null); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); - const [hasMore, setHasMore] = useState(false); useEffect(() => { - dispatch({ type: "RESET" }); - setPageNumber(1); - }, [searchParam]); + dispatch({ type: "RESET" }) + setPageNumber(1) + }, [searchParam]) useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchQuickAnswers = async () => { try { const { data } = await api.get("/quickAnswers/", { - params: { searchParam, pageNumber }, - }); - dispatch({ type: "LOAD_QUICK_ANSWERS", payload: data.quickAnswers }); - setHasMore(data.hasMore); - setLoading(false); + params: { searchParam, pageNumber, userId: user.id }, + }) + dispatch({ type: "LOAD_QUICK_ANSWERS", payload: data.quickAnswers }) + setHasMore(data.hasMore) + setLoading(false) } catch (err) { - toastError(err); + toastError(err) } - }; - fetchQuickAnswers(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, pageNumber]); + } + fetchQuickAnswers() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) socket.on("quickAnswer", (data) => { if (data.action === "update" || data.action === "create") { - dispatch({ type: "UPDATE_QUICK_ANSWERS", payload: data.quickAnswer }); + dispatch({ type: "UPDATE_QUICK_ANSWERS", payload: data.quickAnswer }) } if (data.action === "delete") { dispatch({ type: "DELETE_QUICK_ANSWERS", payload: +data.quickAnswerId, - }); + }) } - }); + }) return () => { - socket.disconnect(); - }; - }, []); + socket.disconnect() + } + }, []) const handleSearch = (event) => { - setSearchParam(event.target.value.toLowerCase()); - }; + setSearchParam(event.target.value.toLowerCase()) + } const handleOpenQuickAnswersModal = () => { - setSelectedQuickAnswers(null); - setQuickAnswersModalOpen(true); - }; + setSelectedQuickAnswers(null) + setQuickAnswersModalOpen(true) + } const handleCloseQuickAnswersModal = () => { - setSelectedQuickAnswers(null); - setQuickAnswersModalOpen(false); - }; + setSelectedQuickAnswers(null) + setQuickAnswersModalOpen(false) + } const handleEditQuickAnswers = (quickAnswer) => { - setSelectedQuickAnswers(quickAnswer); - setQuickAnswersModalOpen(true); - }; + setSelectedQuickAnswers(quickAnswer) + setQuickAnswersModalOpen(true) + } const handleDeleteQuickAnswers = async (quickAnswerId) => { try { - await api.delete(`/quickAnswers/${quickAnswerId}`); - toast.success(i18n.t("quickAnswers.toasts.deleted")); + await api.delete(`/quickAnswers/${quickAnswerId}`) + toast.success(i18n.t("quickAnswers.toasts.deleted")) } catch (err) { - toastError(err); + toastError(err) } - setDeletingQuickAnswers(null); - setSearchParam(""); - setPageNumber(1); - }; + setDeletingQuickAnswers(null) + setSearchParam("") + setPageNumber(1) + } const loadMore = () => { - setPageNumber((prevState) => prevState + 1); - }; + setPageNumber((prevState) => prevState + 1) + } const handleScroll = (e) => { - if (!hasMore || loading) return; - const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; + if (!hasMore || loading) return + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget if (scrollHeight - (scrollTop + 100) < clientHeight) { - loadMore(); + loadMore() } - }; + } return ( { { - setConfirmModalOpen(true); - setDeletingQuickAnswers(quickAnswer); + setConfirmModalOpen(true) + setDeletingQuickAnswers(quickAnswer) }} > @@ -282,7 +284,7 @@ const QuickAnswers = () => { - ); -}; + ) +} -export default QuickAnswers; +export default QuickAnswers diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 75886f5..2a94ef3 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useReducer, useContext } from "react" +import React, { useState, useEffect, useReducer, useContext, useCallback } from "react" import MainContainer from "../../components/MainContainer" import api from "../../services/api" import SelectField from "../../components/Report/SelectField" @@ -11,29 +11,33 @@ import { AuthContext } from "../../context/Auth/AuthContext" import { Can } from "../../components/Can" import FormControlLabel from "@mui/material/FormControlLabel" import Checkbox from '@mui/material/Checkbox' - - - -import { Button } from "@material-ui/core" - +import { Button, Tooltip } from "@material-ui/core" import ReportModal from "../../components/ReportModal" +import ReportModalType from "../../components/ReportModalType" import MaterialTable from 'material-table' - import LogoutIcon from '@material-ui/icons/CancelOutlined' - import apiBroker from "../../services/apiBroker" import fileDownload from 'js-file-download' - - import openSocket from "socket.io-client" - import { i18n } from "../../translate/i18n" - import Switch from '@mui/material/Switch' const label = { inputProps: { 'aria-label': 'Size switch demo' } } -const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] +const report = [ + { 'value': '1', 'label': 'Atendimento por atendentes' }, + { 'value': '2', 'label': 'Usuários online/offline' }, + { 'value': '3', 'label': 'Relatorio de atendimento por numeros' }, + { 'value': '4', 'label': 'Relatorio de atendimento por filas' }, +] + +const reportOptType = [ + { 'value': '1', 'label': 'Padrão' }, + { 'value': '2', 'label': 'Sintético' }, + { 'value': '3', 'label': 'Analítico' } +] + + const reducerQ = (state, action) => { @@ -220,6 +224,7 @@ Item.propTypes = { let columnsData = [ + { title: `Tipo`, field: 'isRemote' }, { title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' }, { title: `${i18n.t("reports.listColumns.column1_2")}`, field: 'user.name' }, { title: `${i18n.t("reports.listColumns.column0_4")}`, field: 'contact.number' }, @@ -230,9 +235,14 @@ let columnsData = [ { title: `${i18n.t("reports.listColumns.column1_7")}`, field: 'createdAt' }, { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, - { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }] + { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }, + { title: `Espera`, field: 'waiting_time' }, + { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, + { title: `Link`, field: 'link', searchable: false, hidden: true, export: true }, +] let columnsDataSuper = [ + { title: `Tipo`, field: 'isRemote' }, { title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' }, { title: `${i18n.t("reports.listColumns.column1_2")}`, field: 'user.name' }, { title: `${i18n.t("reports.listColumns.column0_3")}`, field: 'contact.name' }, @@ -242,7 +252,10 @@ let columnsDataSuper = [ { title: `${i18n.t("reports.listColumns.column1_7")}`, field: 'createdAt' }, { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, - { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' } + { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }, + { title: `Espera`, field: 'waiting_time' }, + { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, + { title: `Link`, field: 'link', searchable: false, hidden: true, export: true }, ] @@ -276,28 +289,29 @@ const Report = () => { const [hasMore, setHasMore] = useState(false) const [pageNumberTickets, setTicketsPageNumber] = useState(1) const [totalCountTickets, setTotalCountTickets] = useState(0) - - - const [pageNumber, setPageNumber] = useState(1) const [users, dispatch] = useReducer(reducer, []) const [startDate, setDatePicker1] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date()) const [userId, setUser] = useState(null) const [query, dispatchQ] = useReducer(reducerQ, []) - const [reportOption, setReport] = useState('1') const [reporList,] = useState(report) const [profile, setProfile] = useState('') const [dataRows, setData] = useState([]) - const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) const [csvFile, setCsvFile] = useState() const [selectedValue, setSelectedValue] = useState('created') const [checked, setChecked] = useState(true) + const [checkedRemote, setCheckedRemote] = useState(false) const [queues, setQueues] = useState([]) const [queueId, setQueue] = useState(null) + const [reportTypeList,] = useState(reportOptType) + const [reportType, setReportType] = useState('1') + const [firstLoad, setFirstLoad] = useState(true) + + useEffect(() => { dispatch({ type: "RESET" }) dispatchQ({ type: "RESET" }) @@ -306,6 +320,14 @@ const Report = () => { }, [searchParam, profile]) + useEffect(() => { + if (firstLoad) { + setFirstLoad(false) + } else { + + } + }, [firstLoad]) + useEffect(() => { //setLoading(true); @@ -337,16 +359,14 @@ const Report = () => { useEffect(() => { //setLoading(true); + if (firstLoad) return const delayDebounceFn = setTimeout(() => { - setLoading(true) const fetchQueries = async () => { try { if (reportOption === '1') { - // const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, }) - const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets, createdOrUpdated: selectedValue, queueId }, userQueues: userA.queues }) let ticketsQueue = data.tickets @@ -358,8 +378,15 @@ const Report = () => { filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name) } data.tickets = filterQueuesTickets - dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }) - + const tickets = data.tickets.map(ticket => { + ticket.isRemote = ticket.isRemote ? 'Remoto' : 'Comum'; + return ({ + ...ticket, + messagesToFilter: ticket.messages.map(message => message.body).join(' '), + link: `${process.env.REACT_APP_FRONTEND_URL}/tickets/${ticket.id}` + }) + }) + dispatchQ({ type: "LOAD_QUERY", payload: tickets }) setHasMore(data.hasMore) setTotalCountTickets(data.count) setLoading(false) @@ -375,6 +402,18 @@ const Report = () => { //setLoading(false); } + else if (reportOption === '3') { + const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate, isRemote: checkedRemote }, }) + + dispatchQ({ type: "RESET" }) + dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService }) + } + else if (reportOption === '4') { + const dataQuery = await api.get("/reports/services/queues", { params: { startDate, endDate, isRemote: checkedRemote }, }) + + dispatchQ({ type: "RESET" }) + dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService }) + } } catch (err) { console.log(err) @@ -386,7 +425,7 @@ const Report = () => { }, 500) return () => clearTimeout(delayDebounceFn) - }, [userId, queueId, checked, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets, selectedValue]) + }, [userId, queueId, checked, checkedRemote, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets, selectedValue]) const handleCheckBoxChange = (value) => { @@ -426,6 +465,18 @@ const Report = () => { setReport(data) } + // Get from report type option + const reportTypeValue = (data) => { + let type = '1' + if (data === '1') type = 'default' + if (data === '2') type = 'synthetic' + if (data === '3') type = 'analytic' + + handleCSVMessages(type) + + setReportType(data) + } + useEffect(() => { if (reportOption === '1') { @@ -500,7 +551,7 @@ const Report = () => { - const handleCSVMessages = () => { + const handleCSVMessages = (type = 'default') => { const fetchQueries = async () => { @@ -519,7 +570,8 @@ const Report = () => { userId: userId, startDate: startDate, endDate: endDate - } + }, + query_type: type }) const onQueueStatus = querySavedOnQueue.data.queueStatus @@ -635,50 +687,56 @@ const Report = () => { const renderSwitch = (param) => { - switch (param) { - case 'empty': - return ( - <> - - ) + if(userA.profile !== 'supervisor'){ + switch (param) { + case 'empty': + return ( + <> + {query && query.length > 0 && + + } + {/* */} + ) - case 'pending' || 'processing': - return ( - <> - PROCESSING... - ) + case 'pending' || 'processing': + return ( + <> + PROCESSING... + ) - case 'success': - return ( - <> - - ) - case 'downloading': - return ( - <> - DOWNLOADING... - ) + case 'success': + return ( + <> + + ) + case 'downloading': + return ( + <> + DOWNLOADING... + ) - default: - return (<>WAITING...) + default: + return (<>WAITING...) + } } } @@ -687,6 +745,10 @@ const Report = () => { setChecked(event.target.checked) } + const handleChangeRemote = (event) => { + setCheckedRemote(event.target.checked) + } + return ( { - {checked ? - { - return { 'value': obj.id, 'label': obj.name } - })} /> : - { - return { 'value': obj.id, 'label': obj.name } - })} /> + {(reportOption === '1' || reportOption === '2') && + <> + {checked ? + { + return { 'value': obj.id, 'label': obj.name } + })} /> : + { + return { 'value': obj.id, 'label': obj.name } + })} /> + } + } - {reportOption === '1' &&
} + {(reportOption === '3' || reportOption === '4') && +
+ + + + + + + +
+ } + +
@@ -784,7 +880,7 @@ const Report = () => { <> { /> } + {reportOption === '3' && + + + + } + {reportOption === '4' && + + { + return { + whiteSpace: 'nowrap', + backgroundColor: rowData?.queueColor || 'inherit', + color: 'white' + } + } + + }, + { title: 'Conversas iniciadas', field: 'startedByAgent', }, + { title: 'Conversas recebidas', field: 'startedByClient' }, + { title: `Conversas finalizadas`, field: 'closedChat' }, + { title: `Tempo médio de espera`, field: 'avgChatWaitingTime' }, + { title: 'Aguardando', field: 'pendingChat' } + + ] : + + [ + + { title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, }, + { + title: 'Fila', field: 'queueName', + cellStyle: (evt, rowData) => { + return { + whiteSpace: 'nowrap', + backgroundColor: rowData?.queueColor || 'inherit', + color: 'white' + } + } + + }, + { title: 'Conversas iniciadas', field: 'startedByAgent', }, + { title: 'Conversas respondidas', field: 'startedByClient' }, + { title: `Conversas finalizadas`, field: 'closedChat' }, + { title: 'Aguardando', field: 'pendingChat' } + + ] + } + data={dataRows} + + options={ + { + search: true, + selection: false, + paging: false, + padding: 'dense', + sorting: true, + searchFieldStyle: { + width: 300, + }, + + pageSize: 20, + headerStyle: { + position: "sticky", + top: "0" + }, + maxBodyHeight: "400px", + + rowStyle: { + fontSize: 14, + } + + // rowStyle: rowData => ({ + // backgroundColor: rowData.queueColor || 'inherit', + // fontSize: 14 + // }) + + }} + /> + + } diff --git a/frontend/src/pages/SchedulesReminder/index.js b/frontend/src/pages/SchedulesReminder/index.js index c26d8dd..78ac312 100644 --- a/frontend/src/pages/SchedulesReminder/index.js +++ b/frontend/src/pages/SchedulesReminder/index.js @@ -1,43 +1,43 @@ -import React, { useState, useEffect, useReducer } from "react"; -import MainContainer from "../../components/MainContainer"; -import api from "../../services/api"; +import React, { useState, useEffect, useReducer } from "react" +import MainContainer from "../../components/MainContainer" +import api from "../../services/api" //import { data } from '../../components/Report/MTable/data'; import DatePicker1 from '../../components/Report/DatePicker' import DatePicker2 from '../../components/Report/DatePicker' //import { Button } from "@material-ui/core"; -import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; +import PropTypes from 'prop-types' +import Box from '@mui/material/Box' -import SearchIcon from "@material-ui/icons/Search"; -import TextField from "@material-ui/core/TextField"; -import InputAdornment from "@material-ui/core/InputAdornment"; -import Button from "@material-ui/core/Button"; +import SearchIcon from "@material-ui/icons/Search" +import TextField from "@material-ui/core/TextField" +import InputAdornment from "@material-ui/core/InputAdornment" +import Button from "@material-ui/core/Button" -import MaterialTable from 'material-table'; +import MaterialTable from 'material-table' -import Delete from '@material-ui/icons/Delete'; -import Edit from '@material-ui/icons/Edit'; +import Delete from '@material-ui/icons/Delete' +import Edit from '@material-ui/icons/Edit' -import { render } from '@testing-library/react'; +import { render } from '@testing-library/react' // import Modal from "../../../..ChatEnd/ModalChatEnd"; -import Modal from "../../components/ModalUpdateScheduleReminder"; +import Modal from "../../components/ModalUpdateScheduleReminder" -import openSocket from "socket.io-client"; +import openSocket from "socket.io-client" -import { toast } from "react-toastify"; -import toastError from "../../errors/toastError"; -import ConfirmationModal from "../../components/ConfirmationModal"; +import { toast } from "react-toastify" +import toastError from "../../errors/toastError" +import ConfirmationModal from "../../components/ConfirmationModal" -import { i18n } from "../../translate/i18n"; +import { i18n } from "../../translate/i18n" const reducerQ = (state, action) => { @@ -82,14 +82,14 @@ const reducerQ = (state, action) => { if (action.type === "DELETE_SCHEDULING") { - const scheduleId = action.payload; + const scheduleId = action.payload - const scheduleIndex = state.findIndex((u) => u.id === scheduleId); + const scheduleIndex = state.findIndex((u) => u.id === scheduleId) if (scheduleIndex !== -1) { - state.splice(scheduleIndex, 1); + state.splice(scheduleIndex, 1) } - return [...state]; + return [...state] } @@ -105,7 +105,7 @@ const reducerQ = (state, action) => { if (action.type === "RESET") { - return []; + return [] } } @@ -139,7 +139,7 @@ const reducerQ = (state, action) => { function Item(props) { - const { sx, ...other } = props; + const { sx, ...other } = props return ( - ); + ) } Item.propTypes = { @@ -168,7 +168,7 @@ Item.propTypes = { PropTypes.func, PropTypes.object, ]), -}; +} @@ -179,23 +179,23 @@ const SchedulesReminder = () => { //-------- - const [searchParam] = useState(""); - const [loading, setLoading] = useState(null); + const [searchParam] = useState("") + const [loading, setLoading] = useState(null) //const [hasMore, setHasMore] = useState(false); - const [pageNumber, setPageNumber] = useState(1); + const [pageNumber, setPageNumber] = useState(1) // const [users, dispatch] = useReducer(reducer, []); //const [columns, setColums] = useState([]) const [startDate, setDatePicker1] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date()) const [query, dispatchQ] = useReducer(reducerQ, []) - const [contactNumber, setContactNumber] = useState(""); + const [contactNumber, setContactNumber] = useState("") const [resetChild, setReset] = useState(false) - const [selectedSchedule, setSelectedSchedule] = useState(null); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); - const [dataRows, setData] = useState([]); + const [selectedSchedule, setSelectedSchedule] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + const [dataRows, setData] = useState([]) const [statusEndChat, setStatusEndChat] = useState(null) @@ -204,13 +204,13 @@ const SchedulesReminder = () => { useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) socket.on("schedulingNotify", (data) => { - setLoading(true); + setLoading(true) // if (data.action === "update" || data.action === "create") { @@ -221,27 +221,27 @@ const SchedulesReminder = () => { if (data.action === "delete") { - dispatchQ({ type: "DELETE_SCHEDULING", payload: +data.schedulingNotifyId }); + dispatchQ({ type: "DELETE_SCHEDULING", payload: +data.schedulingNotifyId }) //handleDeleteRows(data.schedulingNotifyId) } - setLoading(false); + setLoading(false) - }); + }) return () => { - socket.disconnect(); - }; - }, []); + socket.disconnect() + } + }, []) useEffect(() => { // dispatch({ type: "RESET" }); dispatchQ({ type: "RESET" }) - setPageNumber(1); - }, [searchParam]); + setPageNumber(1) + }, [searchParam]) //natalia @@ -252,71 +252,62 @@ const SchedulesReminder = () => { const fetchStatusChatEnd = async () => { try { - const statusChatEndLoad = await api.get("/statusChatEnd", { + const { data } = await api.get("/statusChatEnd", { params: { searchParam, pageNumber }, - }); - - // dispatch({ type: "LOAD_STATUS_CHAT_END", payload: statusChatEndLoad.data }); - - - - - // setStatusEndChat(statusChatEndLoad.data.filter(status => (status.id == '2' || status.id == '3'))) - - setStatusEndChat(statusChatEndLoad.data.filter(status => (`${status.id}` === '2' || `${status.id}` === '3'))) - + }) + setStatusEndChat(data.statusChatEnd.filter(status => (status.name === "LEMBRETE" || status.name === "AGENDAMENTO À CONFIRMAR"))) //setHasMore(data.hasMore); // setLoading(false); } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchStatusChatEnd(); + fetchStatusChatEnd() - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, pageNumber]); + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber]) useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchQueries = async () => { try { - const dataQuery = await api.get("/schedules/", { params: { contactNumber, startDate, endDate }, }); + const dataQuery = await api.get("/schedules/", { params: { contactNumber, startDate, endDate }, }) dispatchQ({ type: "RESET" }) - dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); - setLoading(false); + dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }) + setLoading(false) } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchQueries(); + fetchQueries() - }, 500); - return () => clearTimeout(delayDebounceFn); + }, 500) + return () => clearTimeout(delayDebounceFn) - }, [contactNumber, startDate, endDate]); + }, [contactNumber, startDate, endDate]) - useEffect(() => { - - if (!loading) { - + useEffect(() => { + if (!loading) { setData(query.map(({ scheduleReminder, ...others }) => ( - { ...others, 'scheduleReminder': `${others.statusChatEndId}` === '3' ? 'Agendamento' : 'Lembrete' } + { + ...others, 'scheduleReminder': others['statusChatEnd.name'] === 'AGENDAMENTO À CONFIRMAR' ? 'Agendamento' : 'Lembrete' + } ))) } @@ -338,8 +329,8 @@ const SchedulesReminder = () => { const handleSearch = (event) => { - setContactNumber(event.target.value.toLowerCase()); - }; + setContactNumber(event.target.value.toLowerCase()) + } const handleClear = () => { @@ -350,10 +341,10 @@ const SchedulesReminder = () => { } const handleCloseConfirmationModal = () => { - setConfirmModalOpen(false); + setConfirmModalOpen(false) - setSelectedSchedule(null); - }; + setSelectedSchedule(null) + } // const handleDeleteRows = (id) => { @@ -370,58 +361,46 @@ const SchedulesReminder = () => { const handleDeleteSchedule = async (scheduleId) => { try { - await api.delete(`/schedule/${scheduleId}`); - toast.success(("Lembrete/Agendamento deletado com sucesso!")); + await api.delete(`/schedule/${scheduleId}`) + toast.success(("Lembrete/Agendamento deletado com sucesso!")) //handleDeleteRows(scheduleId) } catch (err) { - toastError(err); + toastError(err) } - setSelectedSchedule(null); - }; + setSelectedSchedule(null) + } const handleUpdateSchedule = async (scheduleData, rowsDataNew) => { try { - await api.post("/schedule", scheduleData); - toast.success(("Lembrete/Agendamento atualizado com sucesso!")); + await api.post("/schedule", scheduleData) + toast.success(("Lembrete/Agendamento atualizado com sucesso!")) ////////////////// - const dataUpdate = [...dataRows]; - const index = rowsDataNew.tableData['id']; - dataUpdate[index] = rowsDataNew; + const dataUpdate = [...dataRows] + const index = rowsDataNew.tableData['id'] + dataUpdate[index] = rowsDataNew setData([...dataUpdate].map(({ scheduleReminder, ...others }) => ( - { ...others, 'scheduleReminder': `${others.statusChatEndId}` === '3' ? 'Agendamento' : 'Lembrete' } - ))); - - - - - ///////////////// + { ...others, 'scheduleReminder': others.statusChatEndName === 'AGENDAMENTO À CONFIRMAR' ? 'Agendamento' : 'Lembrete' } + ))) } catch (err) { - toastError(err); + toastError(err) } // - setSelectedSchedule(null); - }; + setSelectedSchedule(null) + } const chatEndVal = (data, rowsDataNew) => { if (data) { - - - - - - handleUpdateSchedule(data, rowsDataNew) - } } @@ -439,7 +418,7 @@ const SchedulesReminder = () => { rowData={rowData} />) - }; + } return ( @@ -492,7 +471,7 @@ const SchedulesReminder = () => { handleDeleteSchedule(selectedSchedule.id)} @@ -532,8 +511,8 @@ const SchedulesReminder = () => { icon: Edit, tooltip: 'Editar', onClick: (event, rowData) => { - - setSelectedSchedule(rowData); + console.log('ROW DATA EDIT: ', rowData) + setSelectedSchedule(rowData) handleModal(rowData) } @@ -543,8 +522,8 @@ const SchedulesReminder = () => { tooltip: 'Deletar', onClick: (event, rowData) => { - setSelectedSchedule(rowData); - setConfirmModalOpen(true); + setSelectedSchedule(rowData) + setConfirmModalOpen(true) } // onClick: handleDeleteRows } @@ -584,6 +563,6 @@ const SchedulesReminder = () => { ) -}; +} -export default SchedulesReminder; +export default SchedulesReminder diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index 149cb75..5dc6bbc 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -12,13 +12,38 @@ import api from '../../services/api' import { i18n } from '../../translate/i18n.js' import toastError from '../../errors/toastError' +import TextField from '@material-ui/core/TextField' +import Button from '@material-ui/core/Button' + + //-------- import { AuthContext } from '../../context/Auth/AuthContext' import { Can } from '../../components/Can' +import { boolean } from 'yup' // import Button from "@material-ui/core/Button"; +const IntegerInput = ({ value, onChange }) => { + const handleChange = (event) => { + const inputValue = event.target.value + // Only allow digits 0-9 + if (/^\d{0,3}$/.test(inputValue)) { + onChange(inputValue) + } + } + + return ( + + ) +} + const useStyles = makeStyles((theme) => ({ root: { display: 'flex', @@ -48,11 +73,48 @@ const Settings = () => { const [settings, setSettings] = useState([]) + + const [number1, setNumber1] = useState('') + const [number2, setNumber2] = useState('') + + const handleNumber1Change = (value) => { + setNumber1(value) + } + + const handleNumber2Change = (value) => { + setNumber2(value) + } + + const handleGetValues = () => { + let e = { + target: { + value: 'enabled', name: 'remoteTicketSendControll', obj: (number1.trim().length > 0 && number2.trim().length > 0) ? { seconds1: number1, seconds2: number2 } : null + } + } + + handleChangeSetting(e) + } + + useEffect(() => { const fetchSession = async () => { try { const { data } = await api.get('/settings') + console.log('data.settings: ', data.settings) setSettings(data.settings) + + if (data?.settings) { + let { obj } = data.settings.find((s) => s.key === 'remoteTicketSendControll') + + if (!obj) return + + obj = JSON.parse(obj) + console.log('SETTING obj: ', obj) + + setNumber1(obj.seconds1) + setNumber2(obj.seconds2) + } + } catch (err) { toastError(err) } @@ -60,6 +122,7 @@ const Settings = () => { fetchSession() }, []) + useEffect(() => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL) @@ -81,9 +144,6 @@ const Settings = () => { } }, []) - useEffect(() => { - console.log('------> settings: ', settings) - }, [settings]) const handleChangeSetting = async (e) => { const selectedValue = e.target.value @@ -92,19 +152,46 @@ const Settings = () => { try { await api.put(`/settings/${settingKey}`, { value: selectedValue, + // obj: e.target?.obj ? e.target.obj : null }) + + if (settingKey === 'farewellMessageByQueue' && + selectedValue === 'enabled' && + getSettingValue('farewellMessageByStatusChatEnd') === 'enabled') { + await api.put(`/settings/farewellMessageByStatusChatEnd`, { + value: 'disabled', + }) + } + + if (settingKey === 'farewellMessageByStatusChatEnd' && + selectedValue === 'enabled' && + getSettingValue('farewellMessageByQueue') === 'enabled') { + await api.put(`/settings/farewellMessageByQueue`, { + value: 'disabled', + }) + } + + toast.success(i18n.t('settings.success')) } catch (err) { toastError(err) } } - const getSettingValue = (key) => { - const { value } = settings.find((s) => s.key === key) + const getSettingValue = (key, _obj = false) => { + const { value, obj } = settings.find((s) => s.key === key) + + if (_obj) + return obj return value } + const isSaveDisabled = (settings && + settings.length > 0 && + getSettingValue('remoteTicketSendControll') === 'disabled') + + return ( { + +
+ + + + Respostas rápidas por fila + + + + + +
+ + +
+ + + + Mensagem de encerramento por fila + + + + + +
+ + +
+ + + + Mensagem de encerramento por status de fechamento + + + + + +
+ +
+ + + + Exibir contatos por fila + + + + + +
+ +
+ + + + Controle de envio de mensagem de ticket remoto por numero + + + + + {/* +
+

Tempo aleatorio em segundos

+ +
+ + + +
+ +
+
*/} + +
+
+ +
+ + + + Noficar quando entrar novo ticket na fila + + + + + +
+
+ + + + Bloquear mídias de Audio e Video + + + + + +
+ +
+ + + + Mostrar tempo de espera dos tickets aguardando + + + + + +
)} /> ) } + + export default Settings diff --git a/frontend/src/pages/StatusChatEnd/index.js b/frontend/src/pages/StatusChatEnd/index.js new file mode 100644 index 0000000..c8de207 --- /dev/null +++ b/frontend/src/pages/StatusChatEnd/index.js @@ -0,0 +1,328 @@ +import React, { useState, useContext, useEffect, useReducer } from "react" +import openSocket from "socket.io-client" + +import { + Button, + IconButton, + makeStyles, + Paper, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + InputAdornment, + TextField, + CheckCircle, +} from "@material-ui/core" +import { Edit, DeleteOutline } from "@material-ui/icons" +import SearchIcon from "@material-ui/icons/Search" + +import MainContainer from "../../components/MainContainer" +import MainHeader from "../../components/MainHeader" +import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper" +import Title from "../../components/Title" + +import api from "../../services/api" +import { i18n } from "../../translate/i18n" +import TableRowSkeleton from "../../components/TableRowSkeleton" +import QuickAnswersModal from "../../components/QuickAnswersModal" +import ConfirmationModal from "../../components/ConfirmationModal" +import { toast } from "react-toastify" +import toastError from "../../errors/toastError" +import { AuthContext } from '../../context/Auth/AuthContext' +import StatusChatEndModal from "../../components/StatusChatEndModal" +import Switch from '@mui/material/Switch' + +const reducer = (state, action) => { + if (action.type === "LOAD_STATUS_CHAT_END") { + const statusChatEnds = action.payload + const newQuickAnswers = [] + + statusChatEnds.forEach((statusChatEnd) => { + const quickAnswerIndex = state.findIndex((q) => q.id === statusChatEnd.id) + if (quickAnswerIndex !== -1) { + state[quickAnswerIndex] = statusChatEnd + } else { + newQuickAnswers.push(statusChatEnd) + } + }) + + return [...state, ...newQuickAnswers] + } + + if (action.type === "UPDATE_STATUS_CHAT_END") { + const statusChatEnd = action.payload + const quickAnswerIndex = state.findIndex((q) => q.id === statusChatEnd.id) + + if (quickAnswerIndex !== -1) { + state[quickAnswerIndex] = statusChatEnd + return [...state] + } else { + return [statusChatEnd, ...state] + } + } + + if (action.type === "DELETE_STATUS_CHAT_END") { + const quickAnswerId = action.payload + + const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswerId) + if (quickAnswerIndex !== -1) { + state.splice(quickAnswerIndex, 1) + } + return [...state] + } + + if (action.type === "RESET") { + return [] + } +} + +const useStyles = makeStyles((theme) => ({ + mainPaper: { + flex: 1, + padding: theme.spacing(1), + overflowY: "scroll", + ...theme.scrollbarStyles, + }, +})) + +const StatusChatEnd = () => { + const classes = useStyles() + + const [loading, setLoading] = useState(false) + const [pageNumber, setPageNumber] = useState(1) + const [searchParam, setSearchParam] = useState("") + const [statusChatEnds, dispatch] = useReducer(reducer, []) + const [selectedStatusChatEnd, setSelectedStatusChatEnd] = useState(null) + const [statusChatEndModalOpen, setStatusChatEndsModalOpen] = useState(false) + const [deletingStatusChatEnds, setDeletingStatusChatEnds] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + const [hasMore, setHasMore] = useState(false) + // const { user, } = useContext(AuthContext) + const [checked, setChecked] = useState(new Array(statusChatEnds.length).fill(false)) + + useEffect(() => { + dispatch({ type: "RESET" }) + setPageNumber(1) + }, [searchParam]) + + useEffect(() => { + setLoading(true) + const delayDebounceFn = setTimeout(() => { + const fetchQuickAnswers = async () => { + try { + const { data } = await api.get("/statusChatEnd", { + params: { searchParam, pageNumber }, + }) + + setChecked(data?.statusChatEnd?.map(s => s.isDefault ? true : false)) + + dispatch({ type: "LOAD_STATUS_CHAT_END", payload: data.statusChatEnd }) + setHasMore(data.hasMore) + setLoading(false) + } catch (err) { + toastError(err) + } + } + fetchQuickAnswers() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber]) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on("statusChatEnd", (data) => { + if (data.action === "update" || data.action === "create") { + dispatch({ type: "UPDATE_STATUS_CHAT_END", payload: data.statusChatEnd }) + } + + if (data.action === "delete") { + dispatch({ + type: "DELETE_STATUS_CHAT_END", + payload: +data.statusChatEndId, + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + const handleSearch = (event) => { + setSearchParam(event.target.value.toLowerCase()) + } + + const handleOpenQuickAnswersModal = () => { + setSelectedStatusChatEnd(null) + setStatusChatEndsModalOpen(true) + } + + const handleCloseQuickAnswersModal = () => { + setSelectedStatusChatEnd(null) + setStatusChatEndsModalOpen(false) + } + + const handleEditStatusChatEnd = (statusChatEnd) => { + setSelectedStatusChatEnd(statusChatEnd) + setStatusChatEndsModalOpen(true) + } + + const handleDeleteStatusChatEnd = async (statusChatEndId) => { + try { + await api.delete(`/statusChatEnd/${statusChatEndId}`) + toast.success("Status de encerramento excluido com sucesso") + } catch (err) { + toastError(err) + } + setDeletingStatusChatEnds(null) + setSearchParam("") + setPageNumber(1) + } + + const loadMore = () => { + setPageNumber((prevState) => prevState + 1) + } + + const handleChange = async (event, statusChatEnd, index) => { + + const newChecked = new Array(statusChatEnds.length).fill(false) + newChecked[index] = event.target.checked + setChecked(newChecked) + + try { + const { id } = statusChatEnd + await api.put(`/statusChatEnd/${id}`, { isDefault: event.target.checked }) + toast.success("Status de encerramento padrão salvo com sucesso") + + } catch (error) { + toast.success("Erro: ", error) + + } + } + + + const handleScroll = (e) => { + if (!hasMore || loading) return + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget + if (scrollHeight - (scrollTop + 100) < clientHeight) { + loadMore() + } + } + + return ( + + handleDeleteStatusChatEnd(deletingStatusChatEnds.id)} + > + {i18n.t("quickAnswers.confirmationModal.deleteMessage")} + + + + {"Status de encerramento"} + + + + + ), + }} + /> + + + + + + + + + {"Status de encerramento"} + + + {"Mensagem de despedida"} + + + {"Padrão"} + + + {i18n.t("quickAnswers.table.actions")} + + + + + <> + {statusChatEnds.map((statusChatEnd, index) => { + + return ( + + {statusChatEnd.name} + {statusChatEnd.farewellMessage} + + + handleChange(event, statusChatEnd, index)} + inputProps={{ 'aria-label': 'controlled' }} + /> + + + handleEditStatusChatEnd(statusChatEnd)} + > + + + + { + setConfirmModalOpen(true) + setDeletingStatusChatEnds(statusChatEnd) + }} + > + + + + + ) + })} + {loading && } + + +
+
+
+ ) +} + +export default StatusChatEnd diff --git a/frontend/src/pages/Tickets/index.js b/frontend/src/pages/Tickets/index.js index 21a254d..0900471 100644 --- a/frontend/src/pages/Tickets/index.js +++ b/frontend/src/pages/Tickets/index.js @@ -11,6 +11,7 @@ import { i18n } from "../../translate/i18n"; import Hidden from "@material-ui/core/Hidden"; import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket"; +import { TicketsProvider } from "../../context/TicketsProvider/TicketsProvider" const useStyles = makeStyles((theme) => ({ chatContainer: { @@ -82,7 +83,9 @@ const Chat = () => { } > - + + + diff --git a/frontend/src/pages/Users/index.js b/frontend/src/pages/Users/index.js index 1ace0ad..c6c3af2 100644 --- a/frontend/src/pages/Users/index.js +++ b/frontend/src/pages/Users/index.js @@ -118,6 +118,7 @@ const Users = () => { const { data } = await api.get("/users/", { params: { searchParam, pageNumber }, }) + console.log('data.users: ', data.users) dispatch({ type: "LOAD_USERS", payload: data.users }) setHasMore(data.hasMore) setLoading(false) @@ -132,7 +133,7 @@ const Users = () => { - useEffect(() => { + useEffect(() => { const delayDebounceFn = setTimeout(() => { const fetchSession = async () => { try { @@ -149,13 +150,13 @@ const Users = () => { } fetchSession() }, 500) - return () => clearTimeout(delayDebounceFn) + return () => clearTimeout(delayDebounceFn) }, []) const getSettingValue = (key) => { - - return settings?.find((s) => s?.key === key)?.value + + return settings?.find((s) => s?.key === key)?.value // const { value } = settings.find((s) => s?.key === key) // return value @@ -309,52 +310,52 @@ const Users = () => { )} /> */} - - - - - - - - - - {i18n.t("users.table.name")} - - {i18n.t("users.table.email")} - - - {i18n.t("users.table.profile")} - - - Cargo - - - {i18n.t("users.table.actions")} - - - - - - <> - {users.map((user) => ( - - {user.name} - {user.email} - {user.profile} - {user.positionCompany} - - - - handleEditUser(user)} - > - - + + + + + + +
+ + + {i18n.t("users.table.name")} + + {i18n.t("users.table.email")} + + + {i18n.t("users.table.profile")} + + + Cargo + + + {i18n.t("users.table.actions")} + + + + + + <> + {users.map((user) => ( + + {user.name} + {user.email} + {user.profile} + {user?.position?.name} + + + + handleEditUser(user)} + > + + { component={QuickAnswers} isPrivate /> + + diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index 7410d89..a499d94 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -241,6 +241,7 @@ const messages = { }, notification: { message: "Message from", + messagePeding: "new ticket in queue", }, tabs: { open: { title: "Inbox" }, diff --git a/frontend/src/translate/languages/es.js b/frontend/src/translate/languages/es.js index 6e047b2..a2c9f3a 100644 --- a/frontend/src/translate/languages/es.js +++ b/frontend/src/translate/languages/es.js @@ -245,6 +245,7 @@ const messages = { }, notification: { message: "Mensaje de", + messagePeding: "Nuevo billete en cola", }, tabs: { open: { title: "Bandeja" }, diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index 69117b9..6cdad45 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -244,6 +244,7 @@ const messages = { }, notification: { message: "Mensagem de", + messagePeding: "Novo ticket na fila", }, tabs: { open: { title: "Inbox" }, @@ -251,7 +252,7 @@ const messages = { search: { title: "Busca" }, }, search: { - placeholder: "Busca telefone/nome", + placeholder: "Tel/nome/conteúdo", }, buttons: { showAll: "Todos", @@ -312,7 +313,9 @@ const messages = { title0_1: "Lembretes/Agendamentos", title1_1: "Atendimento por atendentes", title2_1: "Chat do atendimento pelo Whatsapp", - title3_1: "Usuários online/offline" + title3_1: "Usuários online/offline", + title4_1: "Relatório de atendimento por números", + title5_1: "Relatório de atendimento por filas" }, listColumns:{ column0_1: 'Ações',