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..f8c030e 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() 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/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 493b951..9ee3eaa 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -8,6 +8,11 @@ import UpdateQueueService from "../services/QueueService/UpdateQueueService"; import Queue from "../models/Queue"; import AppError from "../errors/AppError"; import { del, get, set } from "../helpers/RedisClient"; +import { Op } from "sequelize"; +import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService"; +import Whatsapp from "../models/Whatsapp"; +import QuickAnswerQueue from "../models/QuickAnswerQueue"; +import ContactQueue from "../models/ContactQueues"; export const index = async (req: Request, res: Response): Promise => { const queues = await ListQueuesService(); @@ -15,10 +20,68 @@ export const index = async (req: Request, res: Response): Promise => { 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", { @@ -60,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) => @@ -162,6 +225,9 @@ export const remove = async ( await DeleteQueueService(queueId); + await QuickAnswerQueue.destroy({ where: { queueId } }); + await ContactQueue.destroy({ where: { queueId } }); + await del(`queue:${queueId}`); const io = getIO(); 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 6cd6a60..d7d70b3 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -20,6 +20,7 @@ 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; @@ -261,9 +262,7 @@ export const reportMessagesUserByDateStartDateEnd = async ( data_query_messages[i].fromMe = "Cliente"; } - data_query_messages[i].id = i + 1; - - console.log("data_query_messages: ", data_query_messages[i]); + data_query_messages[i].id = i + 1; } return res.status(200).json(data_query_messages); @@ -312,7 +311,7 @@ export const reportService = async ( const reportService = await ReportByNumberQueueService({ startDate, endDate - }); + }); return res.status(200).json({ reportService }); }; @@ -331,15 +330,37 @@ export const reportServiceByQueue = async ( const { startDate, endDate, queueId } = req.query as IndexQuery; - console.log( - `startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}` - ); - const reportService = await ReportByNumberQueueService({ startDate, endDate, queue: true - }); + }); 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 } = req.query as IndexQuery; + + const dateToday = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + + const reportStatusChatEnd = await CountStatusChatEndService( + startDate || dateToday.fullDate, + endDate || dateToday.fullDate + ); + + 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..3964a75 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, 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 2f62ba3..272bc10 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,9 +75,12 @@ import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; import CreateContactService from "../services/ContactServices/CreateContactService"; import { botSendMessage } from "../services/WbotServices/wbotMessageListener"; import WhatsappQueue from "../models/WhatsappQueue"; -import { get } from "../helpers/RedisClient" +import { get } from "../helpers/RedisClient"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; +import Queue from "../models/Queue"; +import StatusChatEnd from "../models/StatusChatEnd"; -export const index = async (req: Request, res: Response): Promise => { +export const index = async (req: Request, res: Response): Promise => { const { pageNumber, status, @@ -118,10 +121,19 @@ 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]) @@ -138,109 +150,144 @@ 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; - - const queue: any = await WhatsappQueue.findOne({ - where: { whatsappId }, - attributes: ["queueId"] - }); - - 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 (!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.` }); - - 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 - }); - } - } - - if (!ticket) { - ticket = await FindOrCreateTicketService( - contact, - whatsappId, - 0, - undefined, - queueId - ); - botSendMessage(ticket, msg); - } - - const io = getIO(); - io.to(ticket.status).emit("ticket", { - action: "update", - ticket - }); - - console.log( - `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success` - ); - return res.status(200).json({ msg: "success" }); } + const { id } = whatsapps[0]; + + whatsappId = id; + } else if (contact_from) { + const whatsapp = await Whatsapp.findOne({ + where: { number: contact_from, status: "CONNECTED" } + }); + + if (!whatsapp) { + return res.status(404).json({ + msg: `Whatsapp number ${contact_from} not found or disconnected!` + }); + } + + const { id } = whatsapp; + + const { queues } = await ShowWhatsAppService(id); + + if (!queues || queues.length == 0) { + return res.status(500).json({ + msg: `The WhatsApp number ${contact_from} is not associated with any queue! ` + }); + } + + queueId = queues[0].id; + whatsappId = id; + } else if (cc) { + const queue = await Queue.findOne({ where: { cc } }); + if (!queue) { + return res.status(404).json({ + msg: `Queue with cc ${cc} not found! ` + }); + } + + queueId = queue.id; + + const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED"); + + if (whatsapps.length === 0) { + return res.status(500).json({ + msg: `No WhatsApp found for this cc ${cc} or the WhatsApp number is disconnected! ` + }); + } + + whatsappId = whatsapps[0].id; + } + + // const validNumber = await CheckIsValidContact(contact_to, true); + const validNumber = contact_to; + + if (validNumber) { + let contact = await Contact.findOne({ where: { number: validNumber } }); + + if (!contact) { + // const profilePicUrl = await GetProfilePicUrl(validNumber); + + contact = await CreateContactService({ + name: contact_name ? contact_name : contact_to, + number: validNumber + // profilePicUrl + }); + + const io = getIO(); + io.emit("contact", { + action: "create", + contact + }); + } + + const { id: contactId } = contact; + + const botInfo = await BotIsOnQueue("botqueue"); + + let ticket = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: "queueChoice" }, + { contactId, status: "open", userId: botInfo.userIdBot } + ] + } + }); + + if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + ticket = null; + } + } else { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + } + } + + if (!ticket) { + ticket = await FindOrCreateTicketService( + contact, + whatsappId, + 0, + undefined, + queueId, + true + ); + botSendMessage(ticket, `${msg}`); + } + + const io = getIO(); + io.to(ticket.status).emit("ticket", { + action: "update", + ticket + }); + 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 => { @@ -340,34 +387,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,8 +465,8 @@ export const update = async ( } const schedulingNotifyCreate = await CreateSchedulingNotifyService({ - ticketId: scheduleData.ticketId, - statusChatEndId: scheduleData.statusChatEndId, + ticketId: scheduleData.ticketId, + statusChatEndId: `${statusChatEnd.id}`, schedulingDate: scheduleData.schedulingDate, schedulingTime: scheduleData.schedulingTime, message: scheduleData.message @@ -406,7 +494,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]: [ diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 013eeb6..7bfcf5f 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -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, + positionCompany, + positionId, + queueIds + } = req.body; console.log("===========> req.url: ", req.url); @@ -163,6 +170,7 @@ export const store = async (req: Request, res: Response): Promise => { password, name, positionCompany, + positionId, profile, queueIds }); 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/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/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/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/SerializeUser.ts b/backend/src/helpers/SerializeUser.ts index 8f7250b..03f70b0 100644 --- a/backend/src/helpers/SerializeUser.ts +++ b/backend/src/helpers/SerializeUser.ts @@ -5,6 +5,8 @@ interface SerializedUser { id: number; name: string; positionCompany: string; + positionId: string | number; + position: object; email: string; profile: string; queues: Queue[]; @@ -15,8 +17,11 @@ export const SerializeUser = (user: User): SerializedUser => { id: user.id, name: user.name, positionCompany: user.positionCompany, + positionId: user.positionId, + position: user.position, email: user.email, profile: user.profile, - queues: user.queues + queues: user.queues, + }; }; 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..03b27a4 100644 --- a/backend/src/helpers/WhatsappOfficialAPI.ts +++ b/backend/src/helpers/WhatsappOfficialAPI.ts @@ -1,11 +1,16 @@ 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}` - } + }, + httpAgent: new http.Agent({ keepAlive: true }), + httpsAgent: new https.Agent({ keepAlive: true }) }); export default api; diff --git a/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts index 84d1db9..ec87574 100644 --- a/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts +++ b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts @@ -53,7 +53,7 @@ async function sendWhatsAppMessageOfficialAPI( } console.log("SEND MESSAGE: ", JSON.stringify(data, null,2)); - + 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/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/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/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..71d12dd 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,14 @@ class Ticket extends Model { @Column isGroup: boolean; + @Default(false) + @Column + isRemote: boolean; + + @ForeignKey(() => StatusChatEnd) + @Column + statusChatEndId: number; + @Column statusChatEnd: string; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index af93cd5..1729d96 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 { @@ -66,6 +69,13 @@ class User extends Model { @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/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/index.ts b/backend/src/routes/index.ts index 1e769a5..4f041f5 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -15,7 +15,7 @@ import schedulingNotifiyRoutes from "./SchedulingNotifyRoutes"; import statusChatEndRoutes from "./statusChatEndRoutes"; import wbotMonitorRoutes from "./wbotMonitorRoutes"; import iamRoutesEL from "./iamRoutesEL"; - +import positionRoutes from "./PositionRoutes" const routes = Router(); @@ -35,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 d6228ef..ecaf41a 100644 --- a/backend/src/routes/reportRoutes.ts +++ b/backend/src/routes/reportRoutes.ts @@ -1,26 +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/user/services", + isAuth, + ReportController.reportUserService +); reportRoutes.get( "/reports/services/numbers", isAuth, ReportController.reportService -); +); -reportRoutes.get("/reports/services/queues", isAuth, ReportController.reportServiceByQueue); +reportRoutes.get( + "/reports/services/queues", + isAuth, + ReportController.reportServiceByQueue +); -reportRoutes.get("/reports/messages", isAuth, ReportController.reportMessagesUserByDateStartDateEnd); +reportRoutes.get( + "/reports/messages", + isAuth, + ReportController.reportMessagesUserByDateStartDateEnd +); + +reportRoutes.get( + "/reports/count/statusChatEnd", + isAuth, + ReportController.reportTicksCountByStatusChatEnds +); export default reportRoutes; 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 16c065a..4c9025b 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -28,6 +28,7 @@ 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"; const server = app.listen(process.env.PORT, () => { logger.info(`Server started on port: ${process.env.PORT}`); @@ -48,7 +49,14 @@ gracefulShutdown(server); (async () => { console.log("os.tmpdir(): ", os.tmpdir()); - await clearAllKeys("user:*", "whatsapp:*", "queue:*"); + await clearAllKeys("user:*", "whatsapp:*", "queue:*", "statusChatEnd:*"); + + 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(); @@ -63,12 +71,12 @@ gracefulShutdown(server); await set(`user:${id}`, { id, name }); } - // const queues = await Queue.findAll(); + const queues = await Queue.findAll(); - // for (const queue of queues) { - // const { id, greetingMessage, name } = queue; - // await set(`queue:${id}`, { id, name, greetingMessage }); - // } + for (const queue of queues) { + const { id, greetingMessage, name, farewellMessage } = queue; + await set(`queue:${id}`, { id, name, greetingMessage, farewellMessage }); + } loadSettings(); 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/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 528d1b1..a8acdf4 100644 --- a/backend/src/services/QueueService/CreateQueueService.ts +++ b/backend/src/services/QueueService/CreateQueueService.ts @@ -7,6 +7,8 @@ interface QueueData { name: string; color: string; greetingMessage?: string; + farewellMessage?: string; + cc?: string; } const CreateQueueService = async (queueData: QueueData): Promise => { @@ -63,8 +65,13 @@ const CreateQueueService = async (queueData: QueueData): Promise => { const queue = await Queue.create(queueData); - // const { id, greetingMessage } = queue; - // await set(`queue:${id}`, { id, name, greetingMessage }); + const { id, greetingMessage, farewellMessage } = queue; + await set(`queue:${id}`, { + id, + name, + greetingMessage, + farewellMessage + }); return queue; } catch (error: any) { 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 59a6077..c118950 100644 --- a/backend/src/services/QueueService/UpdateQueueService.ts +++ b/backend/src/services/QueueService/UpdateQueueService.ts @@ -3,12 +3,14 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; import ShowQueueService from "./ShowQueueService"; -import { set } from "../../helpers/RedisClient" +import { set } from "../../helpers/RedisClient"; interface QueueData { name?: string; color?: string; greetingMessage?: string; + farewellMessage?: string; + cc?: string; } const UpdateQueueService = async ( @@ -69,8 +71,13 @@ const UpdateQueueService = async ( await queue.update(queueData); - // const { id, greetingMessage } = queue; - // await set(`queue:${id}`, { id, name, greetingMessage }); + const { greetingMessage, farewellMessage } = queue; + await set(`queue:${queueId}`, { + id: queueId, + name, + greetingMessage, + farewellMessage + }); return queue; } catch (error: any) { 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 index 5baf96a..02c07dd 100644 --- a/backend/src/services/ReportServices/ReportByNumberQueueService.ts +++ b/backend/src/services/ReportServices/ReportByNumberQueueService.ts @@ -79,41 +79,44 @@ const ReportByNumberQueueService = async ({ { type: QueryTypes.SELECT } ); - // CHAT AVG WAINTING TIME + // CHAT WAINTING TIME const avgChatWaitingTime: any = await sequelize.query( - `SELECT SEC_TO_TIME( - AVG( - 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 - ) - ) - ) - ) AS AVG_AWAITING_TIME + ` + 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 = 'open' OR t.status = 'closed');`, + 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 } ); @@ -129,8 +132,8 @@ const ReportByNumberQueueService = async ({ AND w.number = ${number};`, { type: QueryTypes.SELECT } - ); - + ); + reportServiceData.push({ id, name, @@ -138,9 +141,7 @@ const ReportByNumberQueueService = async ({ startedByAgent: startedByAgent[0]?.ticket_count, startedByClient: startedByClient[0]?.ticket_count, closedChat: closedChat[0]?.ticket_count, - avgChatWaitingTime: avgChatWaitingTime[0]?.AVG_AWAITING_TIME - ? avgChatWaitingTime[0]?.AVG_AWAITING_TIME - : 0, + avgChatWaitingTime: avg(avgChatWaitingTime), pendingChat: pendingChat[0]?.ticket_count }); } @@ -205,31 +206,30 @@ const ReportByNumberQueueService = async ({ { type: QueryTypes.SELECT } ); - // CHAT AVG WAINTING TIME + // CHAT WAINTING TIME const avgChatWaitingTime: any = await sequelize.query( - `SELECT SEC_TO_TIME( - AVG( - 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 - ) - ) - ) - ) AS AVG_AWAITING_TIME + `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 @@ -238,7 +238,10 @@ const ReportByNumberQueueService = async ({ AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) AND m.fromMe = 0 AND q.id = ${q.id} - AND (t.status = 'open' OR t.status = 'closed');`, + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, { type: QueryTypes.SELECT } ); @@ -254,7 +257,7 @@ const ReportByNumberQueueService = async ({ AND q.id = ${q.id};`, { type: QueryTypes.SELECT } - ); + ); reportServiceData.push({ id, @@ -265,9 +268,7 @@ const ReportByNumberQueueService = async ({ startedByAgent: startedByAgent[0]?.ticket_count, startedByClient: startedByClient[0]?.ticket_count, closedChat: closedChat[0]?.ticket_count, - avgChatWaitingTime: avgChatWaitingTime[0]?.AVG_AWAITING_TIME - ? avgChatWaitingTime[0]?.AVG_AWAITING_TIME - : 0, + avgChatWaitingTime: avg(avgChatWaitingTime), pendingChat: pendingChat[0]?.ticket_count }); } @@ -278,3 +279,55 @@ const ReportByNumberQueueService = async ({ }; 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/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts new file mode 100644 index 0000000..bffbb5d --- /dev/null +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -0,0 +1,27 @@ +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 +) => { + + const countStatusChatEnd: any = 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/FindOrCreateTicketService.ts b/backend/src/services/TicketServices/FindOrCreateTicketService.ts index 29fe820..fb14349 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; @@ -106,7 +107,8 @@ const FindOrCreateTicketService = async ( queueId, unreadMessages, whatsappId, - phoneNumberId + phoneNumberId, + isRemote }); } diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 1ea2cc6..4d31e3f 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,20 +8,19 @@ 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 ListTicketServiceCache from "./ListTicketServiceCache"; -import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache' +import { searchTicketCache, loadTicketsCache } from "../../helpers/TicketCache"; import { getWbot } from "../../libs/wbot"; import User from "../../models/User"; - - - interface Request { searchParam?: string; pageNumber?: string; @@ -41,7 +40,6 @@ interface Response { hasMore: boolean; } - const ListTicketsService = async ({ searchParam = "", pageNumber = "1", @@ -51,64 +49,65 @@ const ListTicketsService = async ({ showAll, userId, withUnreadMessages, - unlimited = 'false', + unlimited = "false", searchParamContent = "" }: Request): Promise => { + console.log("----------> searchParamContent: ", searchParamContent); - 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); - searchParam = searchParam.replace(/\s+/g, ' ').trim().toLowerCase(); + searchParam = searchParam.replace(/\s+/g, " ").trim().toLowerCase(); - console.log('QUERY TICKET SEARCH PARAM FROM CACHE: ', searchParam) + console.log("QUERY TICKET SEARCH PARAM FROM CACHE: ", searchParam); let tickets: any = await searchTicketCache(searchParam, offset, 40); if (tickets) { - console.log('QUERY TICKET SEARCH PARAM FROM CACHE LENGTH...: ', tickets.length) + 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"]; - t['contact.number'] = t['contact_number'] - delete t['contact_number'] + return { ...["contact_number"] }; + }); - return { ...['contact_number'] } - }) + tickets = tickets.map((e: any) => unflatten(e)); - tickets = tickets.map((e: any) => unflatten(e)) - - return { tickets, count: tickets.length, hasMore: tickets.length > 0 ? true : false }; + 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( + "There was an error on search ListTicketservice.ts search cache: ", + error + ); } - console.log('QUERY TICKETS FROM DATABASE...') - + console.log("QUERY TICKETS FROM DATABASE..."); } - - - let includeCondition: Includeable[]; - - includeCondition = [ { model: Contact, @@ -127,10 +126,8 @@ const ListTicketsService = async ({ } if (status) { - whereCondition = { ...whereCondition, status }; - if (unlimited === "current" && status !== "pending") { whereCondition = { ...whereCondition, @@ -140,16 +137,15 @@ const ListTicketsService = async ({ } }; } - } if (searchParam) { const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); - const sanitizedSearchParamContent = searchParamContent.toLocaleLowerCase().trim(); - + const sanitizedSearchParamContent = searchParamContent + .toLocaleLowerCase() + .trim(); if (searchParamContent.length > 0) { - includeCondition = [ ...includeCondition, { @@ -157,7 +153,11 @@ const ListTicketsService = async ({ as: "messages", attributes: ["id", "body"], where: { - body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + body: where( + fn("LOWER", col("body")), + "LIKE", + `%${sanitizedSearchParamContent}%` + ) }, required: false, duplicating: false @@ -166,37 +166,38 @@ const ListTicketsService = async ({ whereCondition = { ...whereCondition, - "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + "$message.body$": where( + fn("LOWER", col("body")), + "LIKE", + `%${sanitizedSearchParamContent}%` + ) }; - } - - 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}%` } }, - - ], + { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } } + ] }; - - const userProfile: any = await User.findByPk(userId) + 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) { @@ -218,8 +219,14 @@ 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); @@ -234,8 +241,6 @@ const ListTicketsService = async ({ const hasMore = count > offset + tickets.length; - - return { tickets, count, diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index 8e75349..1c4b1ea 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -7,7 +7,7 @@ import Queue from "../../models/Queue"; import Message from "../../models/Message"; import { userInfo } from "os"; -import { Op, QueryTypes, where } from "sequelize"; +import { Op, QueryTypes, json, where } from "sequelize"; import { Sequelize } from "sequelize"; import moment from "moment"; @@ -17,7 +17,8 @@ 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 Query from "mysql2/typings/mysql/lib/protocol/sequences/Query"; +import { te } from "date-fns/locale"; interface Request { userId: string | number; @@ -43,45 +44,56 @@ const ShowTicketReport = async ({ createdOrUpdated = "created", queueId }: Request): Promise => { - let where_clause: any = {}; - let query = ""; + // let where_clause: any = {}; + // let query = ""; - if (userId !== "0") { - where_clause.userid = userId; - query = `AND t.userId = ${userId}`; - } + // if (userId !== "0") { + // where_clause.userid = userId; + // query = `AND t.userId = ${userId}`; + // } + + // if (queueId) { + // where_clause.queueId = queueId; + // query = `AND t.queueId = ${queueId}`; + // } + + const createdAtOrUpdatedAt = + createdOrUpdated == "created" ? "createdAt" : "updatedAt"; + + let where_clause = {}; if (queueId) { - where_clause.queueId = queueId; - query = `AND t.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 createdAtOrUpdatedAt = - createdOrUpdated == "created" ? "createdAt" : "updatedAt"; - - const _ticketsId = await sequelize.query( - `SELECT t.id - FROM Tickets t - INNER JOIN ( - SELECT DISTINCT ticketId - FROM Messages - WHERE ${createdAtOrUpdatedAt} >= '${startDate} 00:00:00' AND ${createdAtOrUpdatedAt} <= '${endDate} 23:59:59' - ) AS m ON m.ticketId = t.id ${query};`, - { type: QueryTypes.SELECT } - ); - - console.log('QUERY: ', query) - - const { count, rows: tickets } = await Ticket.findAndCountAll({ - where: { - id: { [Op.in]: _ticketsId.map((t: any) => t.id) } - }, + let { count, rows: tickets }: any = await Ticket.findAndCountAll({ + where: where_clause, limit, offset, - attributes: [ "id", "status", @@ -153,6 +165,56 @@ const ShowTicketReport = async ({ 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 1c3b10a..545e015 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -10,7 +10,7 @@ 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 { deleteObject } from "../../helpers/RedisClient"; var flatten = require("flat"); interface TicketData { @@ -18,8 +18,10 @@ interface TicketData { userId?: number; 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); @@ -73,7 +77,9 @@ const UpdateTicketService = async ({ userId, unreadMessages, statusChatEnd, - whatsappId + statusChatEndId, + whatsappId, + isRemote }); await ticket.reload(); diff --git a/backend/src/services/UserServices/CreateUserService.ts b/backend/src/services/UserServices/CreateUserService.ts index 9177289..f866a9b 100644 --- a/backend/src/services/UserServices/CreateUserService.ts +++ b/backend/src/services/UserServices/CreateUserService.ts @@ -3,12 +3,14 @@ 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; + positionId?: string; queueIds?: number[]; profile?: string; ignoreThrow?: boolean; @@ -18,6 +20,7 @@ interface Response { email: string; name: string; positionCompany: string; + positionId: string; id: number; profile: string; } @@ -27,6 +30,7 @@ const CreateUserService = async ({ password, name, positionCompany, + positionId, queueIds = [], profile = "master", ignoreThrow = false @@ -79,6 +83,7 @@ const CreateUserService = async ({ password, name, positionCompany, + positionId: !positionId ? null : positionId, profile }, { include: ["queues"] } @@ -88,7 +93,9 @@ 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) { diff --git a/backend/src/services/UserServices/ListUsersService.ts b/backend/src/services/UserServices/ListUsersService.ts index 5f2b07a..a55ca5c 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", + "positionCompany", + "profile", + "createdAt" + ], 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..1f3d272 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", + "positionCompany", + "positionId", + "tokenVersion" ], - 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 329186c..78a5fd2 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -9,6 +9,7 @@ interface UserData { password?: string; name?: string; positionCompany?: string; + positionId?: string; profile?: string; queueIds?: number[]; } @@ -73,6 +74,7 @@ const UpdateUserService = async ({ profile, name, positionCompany, + positionId, queueIds = [] } = userData; @@ -87,6 +89,7 @@ const UpdateUserService = async ({ password, profile, positionCompany, + positionId: !positionId ? null : positionId, name }); @@ -94,12 +97,27 @@ const UpdateUserService = async ({ 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 }; return serializedUser; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index f76c338..2d77520 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"; @@ -100,6 +101,9 @@ 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(); @@ -330,6 +334,14 @@ const verifyMessage = async ( 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 }); }; @@ -358,9 +370,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]; } } @@ -400,6 +413,8 @@ const verifyQueue = async ( } // + await assingContactByQueue(ticket, choosenQueue.id); + let whatsapp: any = await whatsappInfo(ticket?.whatsappId); const outService = await outOfService(whatsapp?.number); @@ -426,7 +441,7 @@ const verifyQueue = async ( //test del transfere o atendimento se entrar na ura infinita const repet: any = await mostRepeatedPhrase(ticket.id); - if (repet.occurrences > 10) { + if (repet.occurrences > 5) { await UpdateTicketService({ ticketData: { status: "pending", queueId: queues[0].id }, ticketId: ticket.id @@ -520,7 +535,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; @@ -565,8 +580,12 @@ const transferTicket = async ( queue = queues[queueName]; } - if (queue) await botTransferTicket(queue, ticket, sendGreetingMessage); - io.emit('notifyPeding', {data: {ticket, queue}}); + if (queue) { + await assingContactByQueue(ticket, queue.id); + + await botTransferTicket(queue, ticket, sendGreetingMessage); + } + io.emit("notifyPeding", { data: { ticket, queue } }); }; const botTransferTicket = async ( @@ -602,7 +621,7 @@ const botTransferTicketToUser = async ( }; const botSendMessage = (ticket: Ticket, msg: string) => { - const { phoneNumberId } = ticket; + const { phoneNumberId } = ticket; const debouncedSentMessage = debounce( async () => { @@ -660,9 +679,9 @@ const handleMessage = async ( let msgContact: any = wbot.msgContact; // let groupContact: Contact | undefined; - if (msg.fromMe) { + if (msg.fromMe) { // messages sent automatically by wbot have a special character in front of it - // if so, this message was already been stored in database; + // if so, this message was already been stored in database; 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" @@ -803,32 +822,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"); @@ -968,17 +963,9 @@ const handleMessage = async ( }, ticketId: ticket.id }); - // const menuMsg: any = await menu(msg.body, wbot.id, contact.id); - // await botSendMessage(ticket, menuMsg.value); - - // const data: any = await get({ key: "ura", parse: true }); - - // return await backUra(ticket.whatsappId, ticket.contactId, data); const menuMsg: any = await menu(msg.body, wbot.id, contact.id); - console.log("menuMsg: ", menuMsg); - await botSendMessage(ticket, menuMsg.value); // return; @@ -993,7 +980,7 @@ const handleMessage = async ( if (ticket?.queueId) { ticketHasQueue = true; } - + if (ticketHasQueue && ticket.status != "open") { let whatsapp: any = await whatsappInfo(ticket?.whatsappId); @@ -1099,7 +1086,7 @@ 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(" RESPONSE OPTION: ", response, " | OPTION: ", option); let history: any = await historyUra(whatsappId, contactId, response.id); @@ -1238,6 +1225,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); @@ -1267,6 +1261,74 @@ 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 uraOptionSelected = await findObject(whatsappId, contactId, "ura"); 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/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/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} + /> + ) + } +
+ + + + )} + + + + ) +} + +export default StatusChatEndModal diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index a88507b..abc5cd3 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -1,23 +1,23 @@ -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" -const drawerWidth = 320; +const drawerWidth = 320 const useStyles = makeStyles((theme) => ({ root: { @@ -71,88 +71,104 @@ 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 + }) } - }); + }) return () => { - socket.disconnect(); - }; - }, [ticketId, history]); + socket.disconnect() + } + }, [ticketId, history]) const handleDrawerOpen = () => { - setDrawerOpen(true); - }; + setDrawerOpen(true) + } const handleDrawerClose = () => { - setDrawerOpen(false); - }; + setDrawerOpen(false) + } return (
@@ -171,8 +187,8 @@ const Ticket = () => { onClick={handleDrawerOpen} />
-
- +
+
@@ -190,7 +206,7 @@ const Ticket = () => { 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..ab279cb 100644 --- a/frontend/src/components/TicketListItem/index.js +++ b/frontend/src/components/TicketListItem/index.js @@ -246,7 +246,17 @@ const TicketListItem = ({ ticket }) => { loading={loading} onClick={e => handleAcepptTicket(ticket.id)} > - {i18n.t("ticketsList.buttons.accept")} + {/* {i18n.t("ticketsList.buttons.accept")} */} + <> + {/* {i18n.t("ticketsList.buttons.accept")}
CAMPANHA */} + + {ticket?.isRemote ? ( + <>{i18n.t("ticketsList.buttons.accept")}
CAMPANHA + ) : ( + <>{i18n.t("ticketsList.buttons.accept")} + )} + + )} diff --git a/frontend/src/components/UserModal/index.js b/frontend/src/components/UserModal/index.js index cd568e8..51d660d 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,20 @@ 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" const useStyles = makeStyles(theme => ({ root: { @@ -61,7 +61,7 @@ const useStyles = makeStyles(theme => ({ margin: theme.spacing(1), minWidth: 120, }, -})); +})) const UserSchema = Yup.object().shape({ name: Yup.string() @@ -71,60 +71,79 @@ 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: "", 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('') 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('') + } 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) + } const handleSaveUser = async values => { - const userData = { ...values, queueIds: selectedQueueIds }; + const userData = { ...values, queueIds: selectedQueueIds, positionId: selectedPosition } 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 +168,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 +197,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 +227,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/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/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/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 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..ca6fe4e 100644 --- a/frontend/src/pages/Dashboard/index.js +++ b/frontend/src/pages/Dashboard/index.js @@ -15,6 +15,7 @@ import Info from "@material-ui/icons/Info" 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 +32,7 @@ const useStyles = makeStyles((theme) => ({ display: "flex", overflow: "auto", flexDirection: "column", - height: 240, + height: 280, }, customFixedHeightPaper: { padding: theme.spacing(2), @@ -108,7 +109,7 @@ const useStyles = makeStyles((theme) => ({ var _fifo -const sumOnlineTimeNow = (oldOnlineTimeSum) => { +const sumOnlineTimeNow = (oldOnlineTimeSum) => { let onlineTime = new Date() @@ -138,7 +139,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 +208,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])) { @@ -257,7 +258,7 @@ const Dashboard = () => { 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) useEffect(() => { @@ -283,15 +284,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) + }) dispatch({ type: "RESET" }) dispatch({ type: "LOAD_QUERY", payload: data.usersProfile }) + + const { data: ticketStatusChatEndData } = await api.get("/reports/count/statusChatEnd", { + params: { startDate: dateToday, endDate: dateToday }, + }) + + setTicketStatusChatEnd(ticketStatusChatEndData.reportStatusChatEnd) + } catch (err) { } @@ -319,7 +325,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 +362,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") { @@ -497,10 +503,17 @@ const Dashboard = () => { - - - - + + + + + + + + + + + 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 422131a..5e7d6fe 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -235,6 +235,7 @@ 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: `Espera`, field: 'waiting_time' }, { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, { title: `Link`, field: 'link', searchable: false, hidden: true, export: true }, ] @@ -250,6 +251,7 @@ 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: `Espera`, field: 'waiting_time' }, { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, { title: `Link`, field: 'link', searchable: false, hidden: true, export: true }, ] @@ -363,7 +365,7 @@ const Report = () => { if (reportOption === '1') { const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets, createdOrUpdated: selectedValue, queueId }, userQueues: userA.queues }) - + let ticketsQueue = data.tickets let userQueues = userA.queues let filterQueuesTickets = [] @@ -397,15 +399,13 @@ const Report = () => { else if (reportOption === '3') { const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate }, }) - console.log('DATA QUERY.data numbers: ', dataQuery.data) - 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 }, }) - console.log('DATA QUERY.data queues: ', dataQuery.data) + console.log(' dataQuery?.data?.reportService: ', dataQuery?.data?.reportService) dispatchQ({ type: "RESET" }) dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService }) @@ -462,8 +462,7 @@ const Report = () => { } // Get from report type option - const reportTypeValue = (data) => { - console.log('DATA: ', data) + const reportTypeValue = (data) => { let type = '1' if (data === '1') type = 'default' if (data === '2') type = 'synthetic' @@ -962,48 +961,11 @@ const Report = () => { imagem de perfil do whatsapp }, { title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, }, - - { - title: 'Conversas iniciadas', field: 'startedByAgent', - - // cellStyle: (e, rowData) => { - - // if (rowData['statusOnline'] && rowData['statusOnline'].status) { - - // if (rowData['statusOnline'].status === 'offline') { - - // return { color: "red" } - // } - // else if (rowData['statusOnline'].status === 'online') { - // return { color: "green" } - // } - // else if (rowData['statusOnline'].status === 'logout...') { - // return { color: "orange" } - // } - // else if (rowData['statusOnline'].status === 'waiting...') { - // return { color: "orange" } - // } - // } - - - // }, - - }, - + { title: 'Conversas iniciadas', field: 'startedByAgent', }, { title: 'Conversas recebidas', field: 'startedByClient' }, { title: `Conversas finalizadas`, field: 'closedChat' }, { title: `Tempo médio de espera`, field: 'avgChatWaitingTime' }, @@ -1013,29 +975,6 @@ const Report = () => { } data={dataRows} - // actions={[ - // (rowData) => { - - // if (rowData.statusOnline && - // rowData.statusOnline['status'] && - // rowData.statusOnline['status'] === 'online') { - - - // return { - // icon: LogoutIcon, - // tooltip: 'deslogar', - // disable: false, - // onClick: (event, rowData) => { - // handleLogouOnlineUser(rowData.id) - // } - // } - - - // } - // } - // ]} - - options={ { search: true, @@ -1066,49 +1005,23 @@ const Report = () => { imagem de perfil do whatsapp }, - { title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, }, - { title: 'Fila', field: 'queueName', cellStyle: { whiteSpace: 'nowrap' }, }, { - title: 'Conversas iniciadas', field: 'startedByAgent', - - // cellStyle: (e, rowData) => { - - // if (rowData['statusOnline'] && rowData['statusOnline'].status) { - - // if (rowData['statusOnline'].status === 'offline') { - - // return { color: "red" } - // } - // else if (rowData['statusOnline'].status === 'online') { - // return { color: "green" } - // } - // else if (rowData['statusOnline'].status === 'logout...') { - // return { color: "orange" } - // } - // else if (rowData['statusOnline'].status === 'waiting...') { - // return { color: "orange" } - // } - // } - - - // }, + title: 'Fila', field: 'queueName', + cellStyle: (evt, rowData) => { + 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' }, @@ -1118,29 +1031,6 @@ const Report = () => { } data={dataRows} - // actions={[ - // (rowData) => { - - // if (rowData.statusOnline && - // rowData.statusOnline['status'] && - // rowData.statusOnline['status'] === 'online') { - - - // return { - // icon: LogoutIcon, - // tooltip: 'deslogar', - // disable: false, - // onClick: (event, rowData) => { - // handleLogouOnlineUser(rowData.id) - // } - // } - - - // } - // } - // ]} - - options={ { search: true, @@ -1163,6 +1053,11 @@ const Report = () => { 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..0fe21e2 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -81,18 +81,36 @@ const Settings = () => { } }, []) - useEffect(() => { - console.log('------> settings: ', settings) - }, [settings]) + // useEffect(() => { + // console.log('------> settings: ', settings) + // }, [settings]) const handleChangeSetting = async (e) => { const selectedValue = e.target.value - const settingKey = e.target.name + const settingKey = e.target.name try { await api.put(`/settings/${settingKey}`, { value: selectedValue, }) + + 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) @@ -313,6 +331,121 @@ const Settings = () => { + +
+ + + + Respostas rápidas por fila + + + + + +
+ + +
+ + + + Mensagem de encerramento por fila + + + + + +
+ + +
+ + + + Mensagem de encerramento por status de fechamento + + + + + +
+ +
+ + + + Exibir contatos por fila + + + + + +
+ )} /> 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/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 /> + +