├── .env.example ├── .fossa.yml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .sonarcloud.properties ├── LICENSE ├── README.md ├── backend ├── .dockerignore ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .sequelizerc ├── Dockerfile ├── jest.config.js ├── package.json ├── prettier.config.js ├── public │ └── .gitkeep ├── src │ ├── @types │ │ ├── express.d.ts │ │ └── qrcode-terminal.d.ts │ ├── __tests__ │ │ ├── unit │ │ │ └── User │ │ │ │ ├── AuthUserService.spec.ts │ │ │ │ ├── CreateUserService.spec.ts │ │ │ │ ├── DeleteUserService.spec.ts │ │ │ │ ├── ListUserService.spec.ts │ │ │ │ ├── ShowUserService.spec.ts │ │ │ │ └── UpdateUserService.spec.ts │ │ └── utils │ │ │ └── database.ts │ ├── app.ts │ ├── bootstrap.ts │ ├── config │ │ ├── auth.ts │ │ ├── database.ts │ │ └── upload.ts │ ├── controllers │ │ ├── ApiController.ts │ │ ├── BulkMessageController.ts │ │ ├── ChatbotController.ts │ │ ├── ContactController.ts │ │ ├── ImportPhoneContactsController.ts │ │ ├── MessageController.ts │ │ ├── QueueController.ts │ │ ├── QuickAnswerController.ts │ │ ├── ScheduleController.ts │ │ ├── SessionController.ts │ │ ├── SettingController.ts │ │ ├── SettingMessageController.ts │ │ ├── TagController.ts │ │ ├── TicketController.ts │ │ ├── UserController.ts │ │ ├── WebHookController.ts │ │ ├── WhatsAppController.ts │ │ └── WhatsAppSessionController.ts │ ├── database │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 20200717133438-create-users.ts │ │ │ ├── 20200717144403-create-contacts.ts │ │ │ ├── 20200717145643-create-tickets.ts │ │ │ ├── 20200717151645-create-messages.ts │ │ │ ├── 20200717170223-create-whatsapps.ts │ │ │ ├── 20200723200315-create-contacts-custom-fields.ts │ │ │ ├── 20200723202116-add-email-field-to-contacts.ts │ │ │ ├── 20200730153237-remove-user-association-from-messages.ts │ │ │ ├── 20200730153545-add-fromMe-to-messages.ts │ │ │ ├── 20200813114236-change-message-body-type.ts │ │ │ ├── 20200813114236-change-ticket-lastMessage-column-type.ts │ │ │ ├── 20200901235509-add-profile-column-to-users.ts │ │ │ ├── 20200903215941-create-settings.ts │ │ │ ├── 20200904220257-add-name-to-whatsapp.ts │ │ │ ├── 20200906122228-add-name-default-field-to-whatsapp.ts │ │ │ ├── 20200906155658-add-whatsapp-field-to-tickets.ts │ │ │ ├── 20200919124112-update-default-column-name-on-whatsappp.ts │ │ │ ├── 20200927220708-add-isDeleted-column-to-messages.ts │ │ │ ├── 20200929145451-add-user-tokenVersion-column.ts │ │ │ ├── 20200930162323-add-isGroup-column-to-tickets.ts │ │ │ ├── 20200930194808-add-isGroup-column-to-contacts.ts │ │ │ ├── 20201004150008-add-contactId-column-to-messages.ts │ │ │ ├── 20201004155719-add-vcardContactId-column-to-messages.ts │ │ │ ├── 20201004955719-remove-vcardContactId-column-to-messages.ts │ │ │ ├── 20201026215410-add-retries-to-whatsapps.ts │ │ │ ├── 20201028124427-add-quoted-msg-to-messages.ts │ │ │ ├── 20210108001431-add-unreadMessages-to-tickets.ts │ │ │ ├── 20210108164404-create-queues.ts │ │ │ ├── 20210108164504-add-queueId-to-tickets.ts │ │ │ ├── 20210108174594-associate-whatsapp-queue.ts │ │ │ ├── 20210108204708-associate-users-queue.ts │ │ │ ├── 20210109192513-add-greetingMessage-to-whatsapp.ts │ │ │ ├── 20210818102605-create-quickAnswers.ts │ │ │ ├── 20211016014719-add-farewellMessage-to-whatsapp.ts │ │ │ ├── 20211016014719-add-isMultidevice-whatsapp.ts │ │ │ ├── 20211017014719-create-chatbots.ts │ │ │ ├── 20211017014721-create-dialog-chatbot.ts │ │ │ ├── 20211017014721-remove-index-name-chatbots.ts │ │ │ ├── 20211129145819-add-transferQueueMessage-to-queue.ts │ │ │ ├── 20211203161008-update-whatsapp.ts │ │ │ ├── 20211203174123-add-endworkhour-whatsapp.ts │ │ │ ├── 20211203174230-add-startWorkHourWeekend-whatsapp.ts │ │ │ ├── 20211203174350-add-endWorkHourWeekend-whatsapp.ts │ │ │ ├── 20211203174428-add-outOfWorkMessage-whatsapp.ts │ │ │ ├── 20211203174509-add-monday-whatsapp.ts │ │ │ ├── 20211203174613-add-tuesday-whatsapp.ts │ │ │ ├── 20211203174645-add-wednesday-whatsapp.ts │ │ │ ├── 20211203174732-add-thursday-whatsapp.ts │ │ │ ├── 20211203174813-add-friday-whatsapp.ts │ │ │ ├── 20211203174848-add-saturday-whatsapp.ts │ │ │ ├── 20211203175101-add-sunday-whatsapp.ts │ │ │ ├── 20211203175137-add-defineWorkHours-whatsapp.ts │ │ │ ├── 20211207202657-add-number-to-whatsapp.ts │ │ │ ├── 20211215230000-add-checkhourexpedient-to-user.ts │ │ │ ├── 20211220122519-add-transferTicketMessage-to-whatsapp.ts │ │ │ ├── 20211227010200-create-schedules.ts │ │ │ ├── 20212016014719-add-bot-ticket.ts │ │ │ ├── 20212016014719-add-queueId-dialog.ts │ │ │ ├── 20212016014719-add-remoteJid-messages.ts │ │ │ ├── 20220117130000-create-tags.ts │ │ │ ├── 20220117134400-associate-tickets-tags.ts │ │ │ ├── 20220122160900-add-status-to-schedules.ts │ │ │ ├── 20220223095932-add-whatsapp-to-user.ts │ │ │ ├── 20222016014719-add-channel-to-ticket.ts │ │ │ ├── 20222016014719-add-isAgent-chatbot.ts │ │ │ ├── 20222016014719-add-jsonMessage-messages.ts │ │ │ ├── 20222016014719-add-participant-messages.ts │ │ │ ├── 20222016014719-add-text-message.ts │ │ │ ├── 20222016014720-create-MassMessages.ts │ │ │ ├── 20222016014720-create-baileys-session.ts │ │ │ ├── 20222016014720-create-baileys.ts │ │ │ ├── 20222016014720-create-settingsMessage.ts │ │ │ └── 20222016014720-update-type-message-MassMessages.ts │ │ └── seeds │ │ │ ├── 20200904070001-create-chatbot-type.ts │ │ │ ├── 20200904070004-create-default-settings.ts │ │ │ ├── 20200904070004-create-default-users.ts │ │ │ ├── 20200904070006-create-apiToken-settings.ts │ │ │ ├── 20200904070006-create-call.ts │ │ │ ├── 20200904070006-create-checkMsgIsGroup.ts │ │ │ └── 20200904070006-create-timeCreateNewTicket.ts │ ├── errors │ │ └── AppError.ts │ ├── helpers │ │ ├── CheckContactOpenTickets.ts │ │ ├── CheckSettings.ts │ │ ├── CreateTokens.ts │ │ ├── Debounce.ts │ │ ├── GetDefaultWhatsApp.ts │ │ ├── GetDefaultWhatsAppByUser.ts │ │ ├── GetRandWhatsApp.ts │ │ ├── GetTicketWbot.ts │ │ ├── GetWbotMessage.ts │ │ ├── GetWhatsappWbot.ts │ │ ├── Mustache.ts │ │ ├── SendMessage.ts │ │ ├── SendRefreshToken.ts │ │ ├── SerializeUser.ts │ │ ├── SerializeWbotMsgId.ts │ │ ├── SetTicketMessagesAsRead.ts │ │ ├── UpdateDeletedUserOpenTicketsStatus.ts │ │ ├── authState.ts │ │ └── useMultiFileAuthState.ts │ ├── libs │ │ ├── socket.ts │ │ ├── store.ts │ │ └── wbot.ts │ ├── middleware │ │ ├── isAuth.ts │ │ └── isAuthApi.ts │ ├── models │ │ ├── Baileys.ts │ │ ├── BaileysSessions.ts │ │ ├── Chatbot.ts │ │ ├── Contact.ts │ │ ├── ContactCustomField.ts │ │ ├── DialogChatBots.ts │ │ ├── MassMessages.ts │ │ ├── Message.ts │ │ ├── Queue.ts │ │ ├── QuickAnswer.ts │ │ ├── Schedule.ts │ │ ├── Setting.ts │ │ ├── SettingMessage.ts │ │ ├── Tag.ts │ │ ├── Ticket.ts │ │ ├── TicketTag.ts │ │ ├── User.ts │ │ ├── UserQueue.ts │ │ ├── Whatsapp.ts │ │ └── WhatsappQueue.ts │ ├── queues.ts │ ├── routes │ │ ├── apiRoutes.ts │ │ ├── authRoutes.ts │ │ ├── bulkMessageRoutes.ts │ │ ├── chatBotRoutes.ts │ │ ├── contactRoutes.ts │ │ ├── index.ts │ │ ├── messageRoutes.ts │ │ ├── queueRoutes.ts │ │ ├── quickAnswerRoutes.ts │ │ ├── scheduleRoutes.ts │ │ ├── settingMessageRoutes.ts │ │ ├── settingRoutes.ts │ │ ├── tagRoutes.ts │ │ ├── ticketRoutes.ts │ │ ├── userRoutes.ts │ │ ├── webHookRoutes.ts │ │ ├── whatsappRoutes.ts │ │ └── whatsappSessionRoutes.ts │ ├── sendWork.ts │ ├── server.ts │ ├── services │ │ ├── AuthServices │ │ │ └── RefreshTokenService.ts │ │ ├── BaileysServices │ │ │ ├── CreateOrUpdateBaileysService.ts │ │ │ ├── DeleteBaileysService.ts │ │ │ └── ShowBaileysService.ts │ │ ├── ChatBotServices │ │ │ ├── CreateChatBotServices.ts │ │ │ ├── DeleteChatBotServices.ts │ │ │ ├── ListChatBotServices.ts │ │ │ ├── ShowChatBotByChatbotIdServices.ts │ │ │ ├── ShowChatBotServices.ts │ │ │ └── UpdateChatBotServices.ts │ │ ├── ContactServices │ │ │ ├── CreateContactService.ts │ │ │ ├── CreateOrUpdateContactService.ts │ │ │ ├── DeleteContactService.ts │ │ │ ├── GetContactService.ts │ │ │ ├── ListContactsService.ts │ │ │ ├── ShowContactService.ts │ │ │ ├── SimpleListService.ts │ │ │ └── UpdateContactService.ts │ │ ├── DialogChatBotsServices │ │ │ ├── CreateDialogChatBotsServices.ts │ │ │ ├── DeleteDialogChatBotsServices.ts │ │ │ ├── ListDialogChatBotsServices.ts │ │ │ ├── ShowDialogChatBotsServices.ts │ │ │ └── UpdateDialogChatBotsServices.ts │ │ ├── FacebookServices │ │ │ ├── facebookMessageListener.ts │ │ │ ├── graphAPI.ts │ │ │ ├── sendFacebookMessage.ts │ │ │ └── sendFacebookMessageMedia.ts │ │ ├── MassMessage │ │ │ ├── CleanMassMessageervices.ts │ │ │ ├── CreateMassMessageService.ts │ │ │ ├── DeleteMassMessageService.ts │ │ │ └── ListMassMessageService.ts │ │ ├── MessageServices │ │ │ ├── CreateMessageService.ts │ │ │ ├── GetMessagesService.ts │ │ │ └── ListMessagesService.ts │ │ ├── QueueService │ │ │ ├── CreateQueueService.ts │ │ │ ├── DeleteQueueService.ts │ │ │ ├── ListQueuesService.ts │ │ │ ├── ShowQueueService.ts │ │ │ └── UpdateQueueService.ts │ │ ├── QuickAnswerService │ │ │ ├── CreateQuickAnswerService.ts │ │ │ ├── DeleteQuickAnswerService.ts │ │ │ ├── ListQuickAnswerService.ts │ │ │ ├── ShowQuickAnswerService.ts │ │ │ └── UpdateQuickAnswerService.ts │ │ ├── ScheduleServices │ │ │ ├── CreateService.ts │ │ │ ├── DeleteService.ts │ │ │ ├── ListService.ts │ │ │ ├── ShowService.ts │ │ │ └── UpdateService.ts │ │ ├── SettingMensageServices │ │ │ ├── CreateSettingService.ts │ │ │ ├── ListSettingByValueService.ts │ │ │ ├── ListSettingsService.ts │ │ │ ├── ShowSettingsService.ts │ │ │ └── UpdateSettingService.ts │ │ ├── SettingServices │ │ │ ├── ListSettingByValueService.ts │ │ │ ├── ListSettingsService.ts │ │ │ └── UpdateSettingService.ts │ │ ├── TagServices │ │ │ ├── CreateService.ts │ │ │ ├── DeleteService.ts │ │ │ ├── ListService.ts │ │ │ ├── ShowService.ts │ │ │ ├── SimpleListService.ts │ │ │ ├── SyncTagsService.ts │ │ │ └── UpdateService.ts │ │ ├── TicketServices │ │ │ ├── CreateTicketService.ts │ │ │ ├── DeleteTicketService.ts │ │ │ ├── FindOrCreateTicketService.ts │ │ │ ├── ListTicketsService.ts │ │ │ ├── ShowTicketService.ts │ │ │ └── UpdateTicketService.ts │ │ ├── UserServices │ │ │ ├── AuthUserService.ts │ │ │ ├── CreateUserService.ts │ │ │ ├── DeleteUserService.ts │ │ │ ├── ListUsersService.ts │ │ │ ├── ShowUserService.ts │ │ │ └── UpdateUserService.ts │ │ ├── WbotServices │ │ │ ├── ChatBotListener.ts │ │ │ ├── CheckIsValidContact.ts │ │ │ ├── CheckNumber.ts │ │ │ ├── DeleteWhatsAppMessage.ts │ │ │ ├── GetProfilePicUrl.ts │ │ │ ├── ImportContactsService.ts │ │ │ ├── SendWhatsAppMedia.ts │ │ │ ├── SendWhatsAppMessage.ts │ │ │ ├── StartAllWhatsAppsSessions.ts │ │ │ ├── StartWhatsAppSession.ts │ │ │ ├── hourExpedient.ts │ │ │ ├── wbotMessageListener.ts │ │ │ └── wbotMonitor.ts │ │ └── WhatsappService │ │ │ ├── AssociateWhatsappQueue.ts │ │ │ ├── CreateWhatsAppService.ts │ │ │ ├── DeleteWhatsAppService.ts │ │ │ ├── ListWhatsAppsService.ts │ │ │ ├── ShowWhatsAppService.ts │ │ │ └── UpdateWhatsAppService.ts │ └── utils │ │ └── logger.ts └── tsconfig.json ├── docker-compose.phpmyadmin.yaml ├── docker-compose.yaml └── frontend ├── .docker ├── add-env-vars.sh └── nginx │ ├── conf.d │ └── default.conf │ ├── include.d │ ├── allcache.conf │ ├── letsencrypt.conf │ ├── nocache.conf │ ├── spa.conf │ ├── ssl-redirect.conf │ └── ssl.conf │ └── sites.d │ ├── backend.conf │ └── frontend.conf ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── package.json ├── public ├── android-chrome-192x192.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── manifest.json └── mstile-150x150.png ├── server.js └── src ├── App.js ├── assets ├── facebook.png ├── instagram.png ├── mensageiro.png ├── sound.mp3 ├── sound.ogg ├── wa-background-dark.png ├── wa-background.png └── whatsapp.png ├── components ├── BackdropLoading │ └── index.js ├── ButtonWithSpinner │ └── index.js ├── Can │ └── index.js ├── ChatBots │ └── options.js ├── ColorPicker │ └── index.js ├── ConfirmationModal │ └── index.js ├── ContactDrawer │ └── index.js ├── ContactDrawerSkeleton │ └── index.js ├── ContactModal │ └── index.js ├── LinearBuffer │ └── index.js ├── LocationPreview │ └── index.js ├── MainContainer │ └── index.js ├── MainHeader │ └── index.js ├── MainHeaderButtonsWrapper │ └── index.js ├── MarkdownWrapper │ └── index.js ├── MessageInput │ ├── RecordingTimer.js │ └── index.js ├── MessageOptionsMenu │ └── index.js ├── MessagesList │ └── index.js ├── ModalImageCors │ └── index.js ├── NewTicketModal │ └── index.js ├── NotificationsPopOver │ └── index.js ├── PreviewMessage │ └── index.js ├── QrcodeModal │ └── index.js ├── QueueModal │ └── index.js ├── QueueSelect │ └── index.js ├── QuickAnswersModal │ └── index.js ├── ScheduleModal │ └── index.js ├── SelectConection │ └── index.js ├── TabPanel │ └── index.js ├── TableRowSkeleton │ └── index.js ├── TagModal │ └── index.js ├── TagsContainer │ └── index.js ├── TagsFilter │ └── index.js ├── Ticket │ └── index.js ├── TicketActionButtons │ └── index.js ├── TicketHeader │ └── index.js ├── TicketHeaderSkeleton │ └── index.js ├── TicketInfo │ └── index.js ├── TicketListItem │ └── index.js ├── TicketOptionsMenu │ └── index.js ├── TicketsList │ └── index.js ├── TicketsListSkeleton │ └── index.js ├── TicketsManager │ └── index.js ├── TicketsQueueSelect │ └── index.js ├── Title │ └── index.js ├── ToolTips │ └── index.js ├── TransferTicketModal │ └── index.js ├── UserModal │ └── index.js ├── VcardPreview │ └── index.js └── WhatsAppModal │ └── index.js ├── config.js ├── context ├── Auth │ └── AuthContext.js ├── ReplyingMessage │ └── ReplyingMessageContext.js └── WhatsApp │ └── WhatsAppsContext.js ├── errors └── toastError.js ├── hooks ├── useAuth.js │ └── index.js ├── useLocalStorage │ └── index.js ├── useQueues │ └── index.js ├── useSocket │ └── index.js ├── useTickets │ └── index.js └── useWhatsApps │ └── index.js ├── index.js ├── layout ├── MainListItems.js └── index.js ├── pages ├── Connections │ └── index.js ├── Contacts │ └── index.js ├── Dashboard │ ├── Chart.js │ ├── Title.js │ └── index.js ├── Login │ └── index.js ├── Queues │ └── index.js ├── QuickAnswers │ └── index.js ├── Schedules │ └── index.js ├── SendMassMessage │ └── index.js ├── SettingMessage │ └── index.js ├── Settings │ └── index.js ├── ShippingReport │ └── index.js ├── Signup │ └── index.js ├── Tags │ └── index.js ├── Tickets │ └── index.js └── Users │ └── index.js ├── routes ├── Route.js └── index.js ├── rules.js ├── services ├── api.js └── socket-io.js └── translate ├── i18n.js └── languages ├── en.js ├── es.js ├── index.js └── pt.js /.env.example: -------------------------------------------------------------------------------- 1 | # MYSQL 2 | MYSQL_ENGINE= 3 | MYSQL_VERSION= 4 | MYSQL_ROOT_PASSWORD= 5 | MYSQL_DATABASE=whaticket 6 | MYSQL_PORT= 7 | TZ= 8 | 9 | # BACKEND 10 | BACKEND_PORT= 11 | BACKEND_SERVER_NAME=api.mydomain.com 12 | BACKEND_URL=https://api.mydomain.com 13 | PROXY_PORT=443 14 | JWT_SECRET= 15 | JWT_REFRESH_SECRET= 16 | 17 | # FRONTEND 18 | FRONTEND_PORT=80 19 | FRONTEND_SSL_PORT=443 20 | FRONTEND_SERVER_NAME=myapp.mydomain.com 21 | FRONTEND_URL=https://myapp.mydomain.com 22 | 23 | # BROWSERLESS 24 | MAX_CONCURRENT_SESSIONS= 25 | 26 | # PHPMYADMIN 27 | PMA_PORT= -------------------------------------------------------------------------------- /.fossa.yml: -------------------------------------------------------------------------------- 1 | # Generated by FOSSA CLI (https://github.com/fossas/fossa-cli) 2 | # Visit https://fossa.com to learn more 3 | 4 | version: 2 5 | cli: 6 | server: https://app.fossa.com 7 | fetcher: custom 8 | project: https://github.com/canove/whaticket.git 9 | analyze: 10 | modules: 11 | - name: backend 12 | type: npm 13 | target: backend 14 | path: backend 15 | - name: backend 16 | type: npm 17 | target: backend 18 | path: backend 19 | - name: frontend 20 | type: npm 21 | target: frontend 22 | path: frontend 23 | - name: frontend 24 | type: npm 25 | target: frontend 26 | path: frontend 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | path: backend 13 | - name: Use Node.js 16.x 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: '16.x' 17 | - name: Install dependencies 18 | run: npm install 19 | working-directory: backend 20 | - name: Build 21 | run: npm run build 22 | working-directory: backend 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .docker/data/ 2 | ssl/ 3 | .env -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | sonar.exclusions=frontend/src/translate/languages/*,**/__tests__/**/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 canove 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | *Dockerfile* 3 | *docker-compose* 4 | node_modules 5 | dist -------------------------------------------------------------------------------- /backend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV= 2 | BACKEND_URL=http://localhost 3 | FRONTEND_URL=http://localhost:3000 4 | PROXY_PORT=8080 5 | PORT=8080 6 | 7 | DB_DIALECT= 8 | DB_HOST= 9 | DB_USER= 10 | DB_PASS= 11 | DB_NAME= 12 | 13 | JWT_SECRET= 14 | JWT_REFRESH_SECRET= 15 | 16 | REDIS_URI= 17 | REDIS_OPT_LIMITER_MAX=1 18 | REDIS_OPT_LIMITER_DURATION=3000 19 | -------------------------------------------------------------------------------- /backend/.eslintignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "airbnb-base", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/@typescript-eslint", 11 | "plugin:prettier/recommended" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 12, 16 | "sourceType": "module" 17 | }, 18 | "plugins": ["@typescript-eslint", "prettier"], 19 | "rules": { 20 | "consistent-return": "off", 21 | "@typescript-eslint/no-non-null-assertion": "off", 22 | "@typescript-eslint/no-unused-vars": [ 23 | "error", 24 | { "argsIgnorePattern": "_" } 25 | ], 26 | "import/prefer-default-export": "off", 27 | "no-console": "off", 28 | "no-param-reassign": "off", 29 | "prettier/prettier": "error", 30 | "import/extensions": [ 31 | "error", 32 | "ignorePackages", 33 | { 34 | "ts": "never" 35 | } 36 | ], 37 | "quotes": [ 38 | 1, 39 | "double", 40 | { 41 | "avoidEscape": true 42 | } 43 | ] 44 | }, 45 | "settings": { 46 | "import/resolver": { 47 | "typescript": {} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/* 3 | dist 4 | !public/.gitkeep 5 | .env 6 | .env.test 7 | 8 | package-lock.json 9 | yarn.lock 10 | yarn-error.log 11 | 12 | /src/config/sentry.js 13 | 14 | # Ignore test-related files 15 | /coverage.data 16 | /coverage/ 17 | -------------------------------------------------------------------------------- /backend/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require("path"); 2 | 3 | module.exports = { 4 | "config": resolve(__dirname, "dist", "config", "database.js"), 5 | "modules-path": resolve(__dirname, "dist", "models"), 6 | "migrations-path": resolve(__dirname, "dist", "database", "migrations"), 7 | "seeders-path": resolve(__dirname, "dist", "database", "seeds") 8 | }; 9 | -------------------------------------------------------------------------------- /backend/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: false, 3 | trailingComma: "none", 4 | arrowParens: "avoid" 5 | }; 6 | -------------------------------------------------------------------------------- /backend/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unkbot/whaticket-free/7f260e470dd6295d13bbb630f80d902790729436/backend/public/.gitkeep -------------------------------------------------------------------------------- /backend/src/@types/express.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | export interface Request { 3 | user: { id: string; profile: string }; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/@types/qrcode-terminal.d.ts: -------------------------------------------------------------------------------- 1 | declare module "qrcode-terminal"; 2 | -------------------------------------------------------------------------------- /backend/src/__tests__/unit/User/CreateUserService.spec.ts: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | import AppError from "../../../errors/AppError"; 3 | import CreateUserService from "../../../services/UserServices/CreateUserService"; 4 | import { disconnect, truncate } from "../../utils/database"; 5 | 6 | describe("User", () => { 7 | beforeEach(async () => { 8 | await truncate(); 9 | }); 10 | 11 | afterEach(async () => { 12 | await truncate(); 13 | }); 14 | 15 | afterAll(async () => { 16 | await disconnect(); 17 | }); 18 | 19 | it("should be able to create a new user", async () => { 20 | const user = await CreateUserService({ 21 | name: faker.name.findName(), 22 | email: faker.internet.email(), 23 | password: faker.internet.password() 24 | }); 25 | 26 | expect(user).toHaveProperty("id"); 27 | }); 28 | 29 | it("should not be able to create a user with duplicated email", async () => { 30 | await CreateUserService({ 31 | name: faker.name.findName(), 32 | email: "teste@sameemail.com", 33 | password: faker.internet.password() 34 | }); 35 | 36 | try { 37 | await CreateUserService({ 38 | name: faker.name.findName(), 39 | email: "teste@sameemail.com", 40 | password: faker.internet.password() 41 | }); 42 | } catch (err) { 43 | expect(err).toBeInstanceOf(AppError); 44 | expect(err.statusCode).toBe(400); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /backend/src/__tests__/unit/User/DeleteUserService.spec.ts: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | import AppError from "../../../errors/AppError"; 3 | import CreateUserService from "../../../services/UserServices/CreateUserService"; 4 | import DeleteUserService from "../../../services/UserServices/DeleteUserService"; 5 | import { disconnect, truncate } from "../../utils/database"; 6 | 7 | describe("User", () => { 8 | beforeEach(async () => { 9 | await truncate(); 10 | }); 11 | 12 | afterEach(async () => { 13 | await truncate(); 14 | }); 15 | 16 | afterAll(async () => { 17 | await disconnect(); 18 | }); 19 | 20 | it("should be delete a existing user", async () => { 21 | const { id } = await CreateUserService({ 22 | name: faker.name.findName(), 23 | email: faker.internet.email(), 24 | password: faker.internet.password() 25 | }); 26 | 27 | expect(DeleteUserService(id)).resolves.not.toThrow(); 28 | }); 29 | 30 | it("to throw an error if tries to delete a non existing user", async () => { 31 | expect(DeleteUserService(faker.random.number())).rejects.toBeInstanceOf( 32 | AppError 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /backend/src/__tests__/unit/User/ListUserService.spec.ts: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | import User from "../../../models/User"; 3 | import CreateUserService from "../../../services/UserServices/CreateUserService"; 4 | import ListUsersService from "../../../services/UserServices/ListUsersService"; 5 | import { disconnect, truncate } from "../../utils/database"; 6 | 7 | describe("User", () => { 8 | beforeEach(async () => { 9 | await truncate(); 10 | }); 11 | 12 | afterEach(async () => { 13 | await truncate(); 14 | }); 15 | 16 | afterAll(async () => { 17 | await disconnect(); 18 | }); 19 | 20 | it("should be able to list users", async () => { 21 | await CreateUserService({ 22 | name: faker.name.findName(), 23 | email: faker.internet.email(), 24 | password: faker.internet.password() 25 | }); 26 | 27 | const response = await ListUsersService({ 28 | pageNumber: 1 29 | }); 30 | 31 | expect(response).toHaveProperty("users"); 32 | expect(response.users[0]).toBeInstanceOf(User); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /backend/src/__tests__/unit/User/ShowUserService.spec.ts: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | import AppError from "../../../errors/AppError"; 3 | import User from "../../../models/User"; 4 | import CreateUserService from "../../../services/UserServices/CreateUserService"; 5 | import ShowUserService from "../../../services/UserServices/ShowUserService"; 6 | import { disconnect, truncate } from "../../utils/database"; 7 | 8 | describe("User", () => { 9 | beforeEach(async () => { 10 | await truncate(); 11 | }); 12 | 13 | afterEach(async () => { 14 | await truncate(); 15 | }); 16 | 17 | afterAll(async () => { 18 | await disconnect(); 19 | }); 20 | 21 | it("should be able to find a user", async () => { 22 | const newUser = await CreateUserService({ 23 | name: faker.name.findName(), 24 | email: faker.internet.email(), 25 | password: faker.internet.password() 26 | }); 27 | 28 | const user = await ShowUserService(newUser.id); 29 | 30 | expect(user).toHaveProperty("id"); 31 | expect(user).toBeInstanceOf(User); 32 | }); 33 | 34 | it("should not be able to find a inexisting user", async () => { 35 | expect(ShowUserService(faker.random.number())).rejects.toBeInstanceOf( 36 | AppError 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /backend/src/__tests__/utils/database.ts: -------------------------------------------------------------------------------- 1 | import database from "../../database"; 2 | 3 | const truncate = async (): Promise => { 4 | await database.truncate({ force: true, cascade: true }); 5 | }; 6 | 7 | const disconnect = async (): Promise => { 8 | return database.connectionManager.close(); 9 | }; 10 | 11 | export { truncate, disconnect }; 12 | -------------------------------------------------------------------------------- /backend/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config({ 4 | path: process.env.NODE_ENV === "test" ? ".env.test" : ".env" 5 | }); 6 | -------------------------------------------------------------------------------- /backend/src/config/auth.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | secret: process.env.JWT_SECRET || "mysecret", 3 | expiresIn: "11m", 4 | refreshSecret: process.env.JWT_REFRESH_SECRET || "myanothersecret", 5 | refreshExpiresIn: "7d" 6 | }; 7 | -------------------------------------------------------------------------------- /backend/src/config/database.ts: -------------------------------------------------------------------------------- 1 | require("../bootstrap"); 2 | 3 | module.exports = { 4 | define: { 5 | charset: "utf8mb4", 6 | collate: "utf8mb4_bin" 7 | }, 8 | dialect: process.env.DB_DIALECT || "mysql", 9 | timezone: "-03:00", 10 | host: process.env.DB_HOST, 11 | database: process.env.DB_NAME, 12 | username: process.env.DB_USER, 13 | password: process.env.DB_PASS, 14 | logging: false 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/config/upload.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import multer from "multer"; 3 | 4 | const publicFolder = path.resolve(__dirname, "..", "..", "public"); 5 | export default { 6 | directory: publicFolder, 7 | 8 | storage: multer.diskStorage({ 9 | destination: publicFolder, 10 | filename(req, file, cb) { 11 | const fileName = new Date().getTime() + path.extname(file.originalname); 12 | 13 | return cb(null, fileName); 14 | } 15 | }) 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/controllers/ImportPhoneContactsController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import ImportContactsService from "../services/WbotServices/ImportContactsService"; 3 | 4 | export const store = async (req: Request, res: Response): Promise => { 5 | const userId: number = parseInt(req.user.id, 10); 6 | await ImportContactsService(userId); 7 | 8 | return res.status(200).json({ message: "contacts imported" }); 9 | }; 10 | -------------------------------------------------------------------------------- /backend/src/controllers/SessionController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import AppError from "../errors/AppError"; 3 | 4 | import AuthUserService from "../services/UserServices/AuthUserService"; 5 | import { SendRefreshToken } from "../helpers/SendRefreshToken"; 6 | import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService"; 7 | 8 | export const store = async (req: Request, res: Response): Promise => { 9 | const { email, password } = req.body; 10 | 11 | const { token, serializedUser, refreshToken } = await AuthUserService({ 12 | email, 13 | password 14 | }); 15 | 16 | SendRefreshToken(res, refreshToken); 17 | 18 | return res.status(200).json({ 19 | token, 20 | user: serializedUser 21 | }); 22 | }; 23 | 24 | export const update = async ( 25 | req: Request, 26 | res: Response 27 | ): Promise => { 28 | const token: string = req.cookies.jrt; 29 | 30 | if (!token) { 31 | throw new AppError("ERR_SESSION_EXPIRED", 401); 32 | } 33 | 34 | const { user, newToken, refreshToken } = await RefreshTokenService( 35 | res, 36 | token 37 | ); 38 | 39 | SendRefreshToken(res, refreshToken); 40 | 41 | return res.json({ token: newToken, user }); 42 | }; 43 | 44 | export const remove = async ( 45 | req: Request, 46 | res: Response 47 | ): Promise => { 48 | res.clearCookie("jrt"); 49 | 50 | return res.send(); 51 | }; 52 | -------------------------------------------------------------------------------- /backend/src/controllers/SettingController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | import { getIO } from "../libs/socket"; 4 | import AppError from "../errors/AppError"; 5 | 6 | import UpdateSettingService from "../services/SettingServices/UpdateSettingService"; 7 | import ListSettingsService from "../services/SettingServices/ListSettingsService"; 8 | 9 | export const index = async (req: Request, res: Response): Promise => { 10 | if (req.user.profile !== "admin") { 11 | throw new AppError("ERR_NO_PERMISSION", 403); 12 | } 13 | 14 | const settings = await ListSettingsService(); 15 | 16 | return res.status(200).json(settings); 17 | }; 18 | 19 | export const update = async ( 20 | req: Request, 21 | res: Response 22 | ): Promise => { 23 | if (req.user.profile !== "admin") { 24 | throw new AppError("ERR_NO_PERMISSION", 403); 25 | } 26 | const { settingKey: key } = req.params; 27 | const { value } = req.body; 28 | 29 | const setting = await UpdateSettingService({ 30 | key, 31 | value 32 | }); 33 | 34 | const io = getIO(); 35 | io.emit("settings", { 36 | action: "update", 37 | setting 38 | }); 39 | 40 | return res.status(200).json(setting); 41 | }; 42 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200717133438-create-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Users", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false 15 | }, 16 | email: { 17 | type: DataTypes.STRING, 18 | allowNull: false, 19 | unique: true 20 | }, 21 | passwordHash: { 22 | type: DataTypes.STRING, 23 | allowNull: false 24 | }, 25 | createdAt: { 26 | type: DataTypes.DATE, 27 | allowNull: false 28 | }, 29 | updatedAt: { 30 | type: DataTypes.DATE, 31 | allowNull: false 32 | } 33 | }); 34 | }, 35 | 36 | down: (queryInterface: QueryInterface) => { 37 | return queryInterface.dropTable("Users"); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200717144403-create-contacts.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Contacts", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false 15 | }, 16 | number: { 17 | type: DataTypes.STRING, 18 | allowNull: false, 19 | unique: true 20 | }, 21 | profilePicUrl: { 22 | type: DataTypes.STRING 23 | }, 24 | createdAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | }, 28 | updatedAt: { 29 | type: DataTypes.DATE, 30 | allowNull: false 31 | } 32 | }); 33 | }, 34 | 35 | down: (queryInterface: QueryInterface) => { 36 | return queryInterface.dropTable("Contacts"); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200717145643-create-tickets.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Tickets", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | status: { 13 | type: DataTypes.STRING, 14 | defaultValue: "pending", 15 | allowNull: false 16 | }, 17 | lastMessage: { 18 | type: DataTypes.STRING 19 | }, 20 | contactId: { 21 | type: DataTypes.INTEGER, 22 | references: { model: "Contacts", key: "id" }, 23 | onUpdate: "CASCADE", 24 | onDelete: "CASCADE" 25 | }, 26 | userId: { 27 | type: DataTypes.INTEGER, 28 | references: { model: "Users", key: "id" }, 29 | onUpdate: "CASCADE", 30 | onDelete: "SET NULL" 31 | }, 32 | createdAt: { 33 | type: DataTypes.DATE(6), 34 | allowNull: false 35 | }, 36 | updatedAt: { 37 | type: DataTypes.DATE(6), 38 | allowNull: false 39 | } 40 | }); 41 | }, 42 | 43 | down: (queryInterface: QueryInterface) => { 44 | return queryInterface.dropTable("Tickets"); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200717170223-create-whatsapps.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Whatsapps", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | session: { 13 | type: DataTypes.TEXT 14 | }, 15 | qrcode: { 16 | type: DataTypes.TEXT 17 | }, 18 | status: { 19 | type: DataTypes.STRING 20 | }, 21 | battery: { 22 | type: DataTypes.STRING 23 | }, 24 | plugged: { 25 | type: DataTypes.BOOLEAN 26 | }, 27 | createdAt: { 28 | type: DataTypes.DATE, 29 | allowNull: false 30 | }, 31 | updatedAt: { 32 | type: DataTypes.DATE, 33 | allowNull: false 34 | } 35 | }); 36 | }, 37 | 38 | down: (queryInterface: QueryInterface) => { 39 | return queryInterface.dropTable("Whatsapps"); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200723200315-create-contacts-custom-fields.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("ContactCustomFields", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false 15 | }, 16 | value: { 17 | type: DataTypes.STRING, 18 | allowNull: false 19 | }, 20 | contactId: { 21 | type: DataTypes.INTEGER, 22 | references: { model: "Contacts", key: "id" }, 23 | onUpdate: "CASCADE", 24 | onDelete: "CASCADE", 25 | allowNull: false 26 | }, 27 | createdAt: { 28 | type: DataTypes.DATE, 29 | allowNull: false 30 | }, 31 | updatedAt: { 32 | type: DataTypes.DATE, 33 | allowNull: false 34 | } 35 | }); 36 | }, 37 | 38 | down: (queryInterface: QueryInterface) => { 39 | return queryInterface.dropTable("ContactCustomFields"); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200723202116-add-email-field-to-contacts.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Contacts", "email", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Contacts", "email"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200730153237-remove-user-association-from-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.removeColumn("Messages", "userId"); 6 | }, 7 | 8 | down: (queryInterface: QueryInterface) => { 9 | return queryInterface.addColumn("Messages", "userId", { 10 | type: DataTypes.INTEGER, 11 | references: { model: "Users", key: "id" }, 12 | onUpdate: "CASCADE", 13 | onDelete: "SET NULL" 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200730153545-add-fromMe-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "fromMe", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Messages", "fromMe"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200813114236-change-message-body-type.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.changeColumn("Messages", "body", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.changeColumn("Messages", "body", { 12 | type: DataTypes.TEXT 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200813114236-change-ticket-lastMessage-column-type.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.changeColumn("Tickets", "lastMessage", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.changeColumn("Tickets", "lastMessage", { 12 | type: DataTypes.TEXT 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200901235509-add-profile-column-to-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "profile", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "admin" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "profile"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200903215941-create-settings.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Settings", { 6 | key: { 7 | type: DataTypes.STRING, 8 | primaryKey: true, 9 | allowNull: false 10 | }, 11 | value: { 12 | type: DataTypes.TEXT, 13 | allowNull: false 14 | }, 15 | createdAt: { 16 | type: DataTypes.DATE, 17 | allowNull: false 18 | }, 19 | updatedAt: { 20 | type: DataTypes.DATE, 21 | allowNull: false 22 | } 23 | }); 24 | }, 25 | 26 | down: (queryInterface: QueryInterface) => { 27 | return queryInterface.dropTable("Settings"); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200904220257-add-name-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "name", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | unique: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "name"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200906122228-add-name-default-field-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "default", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "default"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200906155658-add-whatsapp-field-to-tickets.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "whatsappId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Whatsapps", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "SET NULL" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("Tickets", "whatsappId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200919124112-update-default-column-name-on-whatsappp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.renameColumn("Whatsapps", "default", "isDefault"); 6 | }, 7 | 8 | down: (queryInterface: QueryInterface) => { 9 | return queryInterface.renameColumn("Whatsapps", "isDefault", "default"); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200927220708-add-isDeleted-column-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "isDeleted", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Messages", "isDeleted"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200929145451-add-user-tokenVersion-column.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "tokenVersion", { 6 | type: DataTypes.INTEGER, 7 | allowNull: false, 8 | defaultValue: 0 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "tokenVersion"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "isGroup", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Tickets", "isGroup"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Contacts", "isGroup", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Contacts", "isGroup"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20201004150008-add-contactId-column-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "contactId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Contacts", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "CASCADE" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("Messages", "contactId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20201004155719-add-vcardContactId-column-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "vcardContactId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Contacts", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "CASCADE" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("Messages", "vcardContactId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20201004955719-remove-vcardContactId-column-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.removeColumn("Messages", "vcardContactId"); 6 | }, 7 | 8 | down: (queryInterface: QueryInterface) => { 9 | return queryInterface.addColumn("Messages", "vcardContactId", { 10 | type: DataTypes.INTEGER, 11 | references: { model: "Contacts", key: "id" }, 12 | onUpdate: "CASCADE", 13 | onDelete: "CASCADE" 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20201026215410-add-retries-to-whatsapps.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "retries", { 6 | type: DataTypes.INTEGER, 7 | defaultValue: 0, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "retries"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20201028124427-add-quoted-msg-to-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "quotedMsgId", { 6 | type: DataTypes.STRING, 7 | references: { model: "Messages", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "SET NULL" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("Messages", "quotedMsgId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210108001431-add-unreadMessages-to-tickets.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "unreadMessages", { 6 | type: DataTypes.INTEGER 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Tickets", "unreadMessages"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210108164404-create-queues.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Queues", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false, 15 | unique: true 16 | }, 17 | color: { 18 | type: DataTypes.STRING, 19 | allowNull: false, 20 | unique: true 21 | }, 22 | greetingMessage: { 23 | type: DataTypes.TEXT 24 | }, 25 | createdAt: { 26 | type: DataTypes.DATE, 27 | allowNull: false 28 | }, 29 | updatedAt: { 30 | type: DataTypes.DATE, 31 | allowNull: false 32 | } 33 | }); 34 | }, 35 | 36 | down: (queryInterface: QueryInterface) => { 37 | return queryInterface.dropTable("Queues"); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210108164504-add-queueId-to-tickets.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "queueId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Queues", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "SET NULL" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("Tickets", "queueId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("WhatsappQueues", { 6 | whatsappId: { 7 | type: DataTypes.INTEGER, 8 | primaryKey: true 9 | }, 10 | queueId: { 11 | type: DataTypes.INTEGER, 12 | primaryKey: true 13 | }, 14 | createdAt: { 15 | type: DataTypes.DATE, 16 | allowNull: false 17 | }, 18 | updatedAt: { 19 | type: DataTypes.DATE, 20 | allowNull: false 21 | } 22 | }); 23 | }, 24 | 25 | down: (queryInterface: QueryInterface) => { 26 | return queryInterface.dropTable("WhatsappQueues"); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210108204708-associate-users-queue.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("UserQueues", { 6 | userId: { 7 | type: DataTypes.INTEGER, 8 | primaryKey: true 9 | }, 10 | queueId: { 11 | type: DataTypes.INTEGER, 12 | primaryKey: true 13 | }, 14 | createdAt: { 15 | type: DataTypes.DATE, 16 | allowNull: false 17 | }, 18 | updatedAt: { 19 | type: DataTypes.DATE, 20 | allowNull: false 21 | } 22 | }); 23 | }, 24 | 25 | down: (queryInterface: QueryInterface) => { 26 | return queryInterface.dropTable("UserQueues"); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "greetingMessage", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Whatsapps", "greetingMessage"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210818102605-create-quickAnswers.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("QuickAnswers", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | shortcut: { 13 | type: DataTypes.TEXT, 14 | allowNull: false 15 | }, 16 | message: { 17 | type: DataTypes.TEXT, 18 | allowNull: false 19 | }, 20 | createdAt: { 21 | type: DataTypes.DATE, 22 | allowNull: false 23 | }, 24 | updatedAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | } 28 | }); 29 | }, 30 | 31 | down: (queryInterface: QueryInterface) => { 32 | return queryInterface.dropTable("QuickAnswers"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211016014719-add-farewellMessage-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "farewellMessage", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Whatsapps", "farewellMessage"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211016014719-add-isMultidevice-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "isMultidevice", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "isMultidevice"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211017014719-create-chatbots.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Chatbots", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false, 15 | unique: true 16 | }, 17 | queueId: { 18 | type: DataTypes.INTEGER, 19 | references: { model: "Queues", key: "id" }, 20 | onUpdate: "CASCADE", 21 | onDelete: "CASCADE" 22 | }, 23 | chatbotId: { 24 | type: DataTypes.INTEGER, 25 | references: { model: "Chatbots", key: "id" }, 26 | onUpdate: "CASCADE", 27 | onDelete: "CASCADE" 28 | }, 29 | greetingMessage: { 30 | type: DataTypes.TEXT 31 | }, 32 | createdAt: { 33 | type: DataTypes.DATE, 34 | allowNull: false 35 | }, 36 | updatedAt: { 37 | type: DataTypes.DATE, 38 | allowNull: false 39 | } 40 | }); 41 | }, 42 | 43 | down: (queryInterface: QueryInterface) => { 44 | return queryInterface.dropTable("Chatbots"); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211017014721-create-dialog-chatbot.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("DialogChatBots", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | awaiting: { 13 | type: DataTypes.INTEGER, 14 | defaultValue: 0, 15 | allowNull: false 16 | }, 17 | contactId: { 18 | type: DataTypes.INTEGER, 19 | references: { model: "Contacts", key: "id" }, 20 | onUpdate: "CASCADE", 21 | onDelete: "CASCADE" 22 | }, 23 | chatbotId: { 24 | type: DataTypes.INTEGER, 25 | references: { model: "Chatbots", key: "id" }, 26 | onUpdate: "CASCADE", 27 | onDelete: "SET NULL" 28 | }, 29 | createdAt: { 30 | type: DataTypes.DATE, 31 | allowNull: false 32 | }, 33 | updatedAt: { 34 | type: DataTypes.DATE, 35 | allowNull: false 36 | }, 37 | }, 38 | ); 39 | }, 40 | 41 | down: (queryInterface: QueryInterface) => { 42 | return queryInterface.dropTable("DialogChatBots"); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211017014721-remove-index-name-chatbots.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: async (queryInterface: QueryInterface) => { 5 | await queryInterface.removeConstraint("Chatbots", "Chatbots_name_key"); 6 | return queryInterface.removeIndex("Chatbots", "name"); 7 | }, 8 | down: async (queryInterface: QueryInterface) => { 9 | await queryInterface.removeConstraint("Chatbots", "Chatbots_name_key"); 10 | return queryInterface.removeIndex("Chatbots", "name"); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211129145819-add-transferQueueMessage-to-queue.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Queues", "transferQueueMessage", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Queues", "transferQueueMessage"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203161008-update-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "startWorkHour", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "startWorkHour"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174123-add-endworkhour-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "endWorkHour", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "endWorkHour"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174230-add-startWorkHourWeekend-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "startWorkHourWeekend", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "startWorkHourWeekend"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174350-add-endWorkHourWeekend-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "endWorkHourWeekend", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "endWorkHourWeekend"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174428-add-outOfWorkMessage-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "outOfWorkMessage", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "outOfWorkMessage"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174509-add-monday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "monday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "monday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174613-add-tuesday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "tuesday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "tuesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174645-add-wednesday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "wednesday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "wednesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174732-add-thursday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "thursday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "thursday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174813-add-friday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "friday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "friday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203174848-add-saturday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "saturday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "saturday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203175101-add-sunday-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "sunday", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "sunday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211203175137-add-defineWorkHours-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "defineWorkHours", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: true, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "defineWorkHours"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211207202657-add-number-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "number", { 6 | type: DataTypes.STRING, 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Whatsapps", "number"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211215230000-add-checkhourexpedient-to-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "checkHourExpedient", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Users", "checkHourExpedient"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20211220122519-add-transferTicketMessage-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "transferTicketMessage", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Whatsapps", "transferTicketMessage"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20212016014719-add-bot-ticket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "isBot", { 6 | type: DataTypes.BOOLEAN 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Tickets", "isBot"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20212016014719-add-queueId-dialog.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("DialogChatBots", "queueId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Queues", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "SET NULL" 10 | }); 11 | }, 12 | 13 | down: (queryInterface: QueryInterface) => { 14 | return queryInterface.removeColumn("DialogChatBots", "queueId"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20212016014719-add-remoteJid-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "remoteJid", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Messages", "remoteJid"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20220117130000-create-tags.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Tags", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false 15 | }, 16 | color: { 17 | type: DataTypes.STRING, 18 | allowNull: true 19 | }, 20 | createdAt: { 21 | type: DataTypes.DATE, 22 | allowNull: false 23 | }, 24 | updatedAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | } 28 | }); 29 | }, 30 | 31 | down: (queryInterface: QueryInterface) => { 32 | return queryInterface.dropTable("Tags"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20220117134400-associate-tickets-tags.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("TicketTags", { 6 | ticketId: { 7 | type: DataTypes.INTEGER, 8 | references: { model: "Tickets", key: "id" }, 9 | onUpdate: "CASCADE", 10 | onDelete: "CASCADE", 11 | allowNull: false 12 | }, 13 | tagId: { 14 | type: DataTypes.INTEGER, 15 | references: { model: "Tags", key: "id" }, 16 | onUpdate: "CASCADE", 17 | onDelete: "CASCADE", 18 | allowNull: false 19 | }, 20 | createdAt: { 21 | type: DataTypes.DATE, 22 | allowNull: false 23 | }, 24 | updatedAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | } 28 | }); 29 | }, 30 | 31 | down: (queryInterface: QueryInterface) => { 32 | return queryInterface.dropTable("TicketTags"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20220122160900-add-status-to-schedules.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Schedules", "status", { 6 | type: DataTypes.STRING, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Schedules", "status"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20220223095932-add-whatsapp-to-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "whatsappId", { 6 | type: DataTypes.INTEGER, 7 | references: { model: "Whatsapps", key: "id" }, 8 | onUpdate: "CASCADE", 9 | onDelete: "SET NULL", 10 | allowNull: true 11 | },); 12 | }, 13 | 14 | down: (queryInterface: QueryInterface) => { 15 | return queryInterface.removeColumn("Users", "whatsappId"); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-channel-to-ticket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "channel", { 6 | type: DataTypes.TEXT, 7 | defaultValue: "whatsapp", 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Tickets", "channel"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-isAgent-chatbot.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Chatbots", "isAgent", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Chatbots", "isAgent"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-jsonMessage-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "dataJson", { 6 | type: DataTypes.JSON 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Messages", "dataJson"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-participant-messages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "participant", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.removeColumn("Messages", "participant"); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-text-message.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Messages", "textMassMessage", { 6 | type: DataTypes.TEXT, 7 | allowNull: true 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Messages", "textMassMessage"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014720-create-MassMessages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("MassMessages", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | whatsappId: { 13 | type: DataTypes.INTEGER, 14 | primaryKey: true 15 | }, 16 | message: { 17 | type: DataTypes.STRING, 18 | defaultValue: false 19 | }, 20 | phone: { 21 | type: DataTypes.STRING, 22 | defaultValue: false 23 | }, 24 | status: { 25 | type: DataTypes.STRING, 26 | defaultValue: "pending" 27 | }, 28 | createdAt: { 29 | type: DataTypes.DATE, 30 | allowNull: false 31 | }, 32 | updatedAt: { 33 | type: DataTypes.DATE, 34 | allowNull: false 35 | } 36 | }); 37 | }, 38 | 39 | down: (queryInterface: QueryInterface) => { 40 | return queryInterface.dropTable("SettingMessage"); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014720-create-baileys-session.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("BaileysSessions", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | whatsappId: { 13 | type: DataTypes.INTEGER, 14 | primaryKey: true 15 | }, 16 | value: { 17 | type: DataTypes.JSON, 18 | allowNull: true 19 | }, 20 | name: { 21 | type: DataTypes.STRING, 22 | allowNull: true 23 | }, 24 | createdAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | }, 28 | updatedAt: { 29 | type: DataTypes.DATE, 30 | allowNull: false 31 | } 32 | }); 33 | }, 34 | 35 | down: (queryInterface: QueryInterface) => { 36 | return queryInterface.dropTable("Baileys"); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014720-create-baileys.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("Baileys", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | whatsappId: { 13 | type: DataTypes.INTEGER, 14 | primaryKey: true 15 | }, 16 | contacts: { 17 | type: DataTypes.JSON, 18 | allowNull: true 19 | }, 20 | chats: { 21 | type: DataTypes.JSON, 22 | allowNull: true 23 | }, 24 | createdAt: { 25 | type: DataTypes.DATE, 26 | allowNull: false 27 | }, 28 | updatedAt: { 29 | type: DataTypes.DATE, 30 | allowNull: false 31 | } 32 | }); 33 | }, 34 | 35 | down: (queryInterface: QueryInterface) => { 36 | return queryInterface.dropTable("Baileys"); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014720-update-type-message-MassMessages.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.changeColumn("MassMessages", "message", { 6 | type: DataTypes.TEXT 7 | }); 8 | }, 9 | 10 | down: (queryInterface: QueryInterface) => { 11 | return queryInterface.changeColumn("MassMessages", "message", { 12 | type: DataTypes.TEXT 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070001-create-chatbot-type.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Settings", 7 | [ 8 | { 9 | key: "chatBotType", 10 | value: "text", 11 | createdAt: new Date(), 12 | updatedAt: new Date() 13 | } 14 | ], 15 | {} 16 | ); 17 | }, 18 | 19 | down: (queryInterface: QueryInterface) => { 20 | return queryInterface.bulkDelete("Settings", {}); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070004-create-default-settings.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Settings", 7 | [ 8 | { 9 | key: "userCreation", 10 | value: "enabled", 11 | createdAt: new Date(), 12 | updatedAt: new Date() 13 | } 14 | ], 15 | {} 16 | ); 17 | }, 18 | 19 | down: (queryInterface: QueryInterface) => { 20 | return queryInterface.bulkDelete("Settings", {}); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070004-create-default-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Users", 7 | [ 8 | { 9 | name: "Administrador", 10 | email: "admin@whaticket.com", 11 | passwordHash: "$2a$08$WaEmpmFDD/XkDqorkpQ42eUZozOqRCPkPcTkmHHMyuTGUOkI8dHsq", 12 | profile: "admin", 13 | tokenVersion: 0, 14 | createdAt: new Date(), 15 | updatedAt: new Date() 16 | } 17 | ], 18 | {} 19 | ); 20 | }, 21 | 22 | down: (queryInterface: QueryInterface) => { 23 | return queryInterface.bulkDelete("Users", {}); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070006-create-apiToken-settings.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | module.exports = { 5 | up: (queryInterface: QueryInterface) => { 6 | return queryInterface.bulkInsert( 7 | "Settings", 8 | [ 9 | { 10 | key: "userApiToken", 11 | value: uuidv4(), 12 | createdAt: new Date(), 13 | updatedAt: new Date() 14 | } 15 | ], 16 | {} 17 | ); 18 | }, 19 | 20 | down: (queryInterface: QueryInterface) => { 21 | return queryInterface.bulkDelete("Settings", {}); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070006-create-call.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Settings", 7 | [ 8 | { 9 | key: "call", 10 | value: "enabled", 11 | createdAt: new Date(), 12 | updatedAt: new Date() 13 | } 14 | ], 15 | {} 16 | ); 17 | }, 18 | 19 | down: (queryInterface: QueryInterface) => { 20 | return queryInterface.bulkDelete("Settings", {}); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070006-create-checkMsgIsGroup.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Settings", 7 | [ 8 | { 9 | key: "CheckMsgIsGroup", 10 | value: "enabled", 11 | createdAt: new Date(), 12 | updatedAt: new Date() 13 | } 14 | ], 15 | {} 16 | ); 17 | }, 18 | 19 | down: (queryInterface: QueryInterface) => { 20 | return queryInterface.bulkDelete("Settings", {}); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/database/seeds/20200904070006-create-timeCreateNewTicket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.bulkInsert( 6 | "Settings", 7 | [ 8 | { 9 | key: "timeCreateNewTicket", 10 | value: "43200", 11 | createdAt: new Date(), 12 | updatedAt: new Date() 13 | } 14 | ], 15 | {} 16 | ); 17 | }, 18 | 19 | down: (queryInterface: QueryInterface) => { 20 | return queryInterface.bulkDelete("Settings", {}); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/src/errors/AppError.ts: -------------------------------------------------------------------------------- 1 | class AppError { 2 | public readonly message: string; 3 | 4 | public readonly statusCode: number; 5 | 6 | constructor(message: string, statusCode = 400) { 7 | this.message = message; 8 | this.statusCode = statusCode; 9 | } 10 | } 11 | 12 | export default AppError; 13 | -------------------------------------------------------------------------------- /backend/src/helpers/CheckContactOpenTickets.ts: -------------------------------------------------------------------------------- 1 | import { Op } from "sequelize"; 2 | import AppError from "../errors/AppError"; 3 | import Ticket from "../models/Ticket"; 4 | 5 | const CheckContactOpenTickets = async ( 6 | contactId: number, 7 | whatsappId: number 8 | ): Promise => { 9 | const ticket = await Ticket.findOne({ 10 | where: { contactId, whatsappId, status: { [Op.or]: ["open", "pending"] } } 11 | }); 12 | 13 | if (ticket) { 14 | throw new AppError("ERR_OTHER_OPEN_TICKET"); 15 | } 16 | }; 17 | 18 | export default CheckContactOpenTickets; 19 | -------------------------------------------------------------------------------- /backend/src/helpers/CheckSettings.ts: -------------------------------------------------------------------------------- 1 | import Setting from "../models/Setting"; 2 | import AppError from "../errors/AppError"; 3 | 4 | const CheckSettings = async (key: string): Promise => { 5 | const setting = await Setting.findOne({ 6 | where: { key } 7 | }); 8 | 9 | if (!setting) { 10 | throw new AppError("ERR_NO_SETTING_FOUND", 404); 11 | } 12 | 13 | return setting.value; 14 | }; 15 | 16 | export default CheckSettings; 17 | -------------------------------------------------------------------------------- /backend/src/helpers/CreateTokens.ts: -------------------------------------------------------------------------------- 1 | import { sign } from "jsonwebtoken"; 2 | import authConfig from "../config/auth"; 3 | import User from "../models/User"; 4 | 5 | export const createAccessToken = (user: User): string => { 6 | const { secret, expiresIn } = authConfig; 7 | 8 | return sign( 9 | { usarname: user.name, profile: user.profile, id: user.id }, 10 | secret, 11 | { 12 | expiresIn 13 | } 14 | ); 15 | }; 16 | 17 | export const createRefreshToken = (user: User): string => { 18 | const { refreshSecret, refreshExpiresIn } = authConfig; 19 | 20 | return sign({ id: user.id, tokenVersion: user.tokenVersion }, refreshSecret, { 21 | expiresIn: refreshExpiresIn 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /backend/src/helpers/Debounce.ts: -------------------------------------------------------------------------------- 1 | interface Timeout { 2 | id: number; 3 | timeout: NodeJS.Timeout; 4 | } 5 | 6 | const timeouts: Timeout[] = []; 7 | 8 | const findAndClearTimeout = (ticketId: number) => { 9 | if (timeouts.length > 0) { 10 | const timeoutIndex = timeouts.findIndex(timeout => timeout.id === ticketId); 11 | 12 | if (timeoutIndex !== -1) { 13 | clearTimeout(timeouts[timeoutIndex].timeout); 14 | timeouts.splice(timeoutIndex, 1); 15 | } 16 | } 17 | }; 18 | 19 | const debounce = ( 20 | func: { (): Promise; (...args: never[]): void }, 21 | wait: number, 22 | ticketId: number 23 | ) => { 24 | return function executedFunction(...args: never[]): void { 25 | const later = () => { 26 | findAndClearTimeout(ticketId); 27 | func(...args); 28 | }; 29 | 30 | findAndClearTimeout(ticketId); 31 | 32 | const newTimeout = { 33 | id: ticketId, 34 | timeout: setTimeout(later, wait) 35 | }; 36 | 37 | timeouts.push(newTimeout); 38 | }; 39 | }; 40 | 41 | export { debounce }; 42 | -------------------------------------------------------------------------------- /backend/src/helpers/GetDefaultWhatsApp.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../errors/AppError"; 2 | import Whatsapp from "../models/Whatsapp"; 3 | import GetDefaultWhatsAppByUser from "./GetDefaultWhatsAppByUser"; 4 | 5 | const GetDefaultWhatsApp = async (userId?: number): Promise => { 6 | if (userId) { 7 | const whatsappByUser = await GetDefaultWhatsAppByUser(userId); 8 | if (whatsappByUser !== null) { 9 | return whatsappByUser; 10 | } 11 | } 12 | 13 | const defaultWhatsapp = await Whatsapp.findOne({ 14 | where: { isDefault: true } 15 | }); 16 | 17 | if (!defaultWhatsapp) { 18 | throw new AppError("ERR_NO_DEF_WAPP_FOUND"); 19 | } 20 | 21 | return defaultWhatsapp; 22 | }; 23 | 24 | export default GetDefaultWhatsApp; 25 | -------------------------------------------------------------------------------- /backend/src/helpers/GetDefaultWhatsAppByUser.ts: -------------------------------------------------------------------------------- 1 | import User from "../models/User"; 2 | import Whatsapp from "../models/Whatsapp"; 3 | import { logger } from "../utils/logger"; 4 | 5 | const GetDefaultWhatsAppByUser = async ( 6 | userId: number 7 | ): Promise => { 8 | const user = await User.findByPk(userId, {include: ["whatsapp"]}); 9 | if( user === null ) { 10 | return null; 11 | } 12 | 13 | if(user.whatsapp !== null) { 14 | logger.info(`Found whatsapp linked to user '${user.name}' is '${user.whatsapp.name}'.`); 15 | } 16 | 17 | return user.whatsapp; 18 | }; 19 | 20 | export default GetDefaultWhatsAppByUser; 21 | -------------------------------------------------------------------------------- /backend/src/helpers/GetRandWhatsApp.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../errors/AppError"; 2 | import Whatsapp from "../models/Whatsapp"; 3 | import GetDefaultWhatsAppByUser from "./GetDefaultWhatsAppByUser"; 4 | 5 | const GetDefaultWhatsApp = async (userId?: number): Promise => { 6 | if (userId) { 7 | const whatsappByUser = await GetDefaultWhatsAppByUser(userId); 8 | if (whatsappByUser !== null) { 9 | return whatsappByUser; 10 | } 11 | } 12 | 13 | const defaultWhatsapp = await Whatsapp.findOne({ 14 | where: { isDefault: true } 15 | }); 16 | 17 | if (!defaultWhatsapp) { 18 | throw new AppError("ERR_NO_DEF_WAPP_FOUND"); 19 | } 20 | 21 | return defaultWhatsapp; 22 | }; 23 | 24 | export default GetDefaultWhatsApp; 25 | -------------------------------------------------------------------------------- /backend/src/helpers/GetTicketWbot.ts: -------------------------------------------------------------------------------- 1 | import { WASocket } from "@whiskeysockets/baileys"; 2 | import { getWbot } from "../libs/wbot"; 3 | import GetDefaultWhatsApp from "./GetDefaultWhatsApp"; 4 | import Ticket from "../models/Ticket"; 5 | import { Store } from "../libs/store"; 6 | 7 | type Session = WASocket & { 8 | id?: number; 9 | store?: Store; 10 | }; 11 | 12 | const GetTicketWbot = async (ticket: Ticket): Promise => { 13 | if (!ticket.whatsappId) { 14 | const defaultWhatsapp = await GetDefaultWhatsApp(ticket.user.id); 15 | 16 | await ticket.$set("whatsapp", defaultWhatsapp); 17 | } 18 | 19 | const wbot = getWbot(ticket.whatsappId); 20 | 21 | return wbot; 22 | }; 23 | 24 | export default GetTicketWbot; 25 | -------------------------------------------------------------------------------- /backend/src/helpers/GetWbotMessage.ts: -------------------------------------------------------------------------------- 1 | import { proto } from "@whiskeysockets/baileys"; 2 | import Ticket from "../models/Ticket"; 3 | import AppError from "../errors/AppError"; 4 | import GetMessageService from "../services/MessageServices/GetMessagesService"; 5 | import Message from "../models/Message"; 6 | 7 | export const GetWbotMessage = async ( 8 | ticket: Ticket, 9 | messageId: string 10 | ): Promise => { 11 | const fetchWbotMessagesGradually = async (): Promise< 12 | proto.WebMessageInfo | Message 13 | > => { 14 | const msgFound = await GetMessageService({ 15 | id: messageId 16 | }); 17 | 18 | return msgFound; 19 | }; 20 | 21 | try { 22 | const msgFound = await fetchWbotMessagesGradually(); 23 | 24 | if (!msgFound) { 25 | throw new Error("Cannot found message within 100 last messages"); 26 | } 27 | 28 | return msgFound; 29 | } catch (err) { 30 | console.log(err); 31 | throw new AppError("ERR_FETCH_WAPP_MSG"); 32 | } 33 | }; 34 | 35 | export default GetWbotMessage; 36 | -------------------------------------------------------------------------------- /backend/src/helpers/GetWhatsappWbot.ts: -------------------------------------------------------------------------------- 1 | import { WASocket } from "@whiskeysockets/baileys"; 2 | import { Store } from "../libs/store"; 3 | import { getWbot } from "../libs/wbot"; 4 | import Whatsapp from "../models/Whatsapp"; 5 | 6 | type Session = WASocket & { 7 | id?: number; 8 | store?: Store; 9 | }; 10 | const GetWhatsappWbot = async (whatsapp: Whatsapp): Promise => { 11 | const wbot = getWbot(whatsapp.id); 12 | return wbot; 13 | }; 14 | 15 | export default GetWhatsappWbot; 16 | -------------------------------------------------------------------------------- /backend/src/helpers/Mustache.ts: -------------------------------------------------------------------------------- 1 | import Mustache from "mustache"; 2 | import Contact from "../models/Contact"; 3 | 4 | export const greeting = (): string => { 5 | const greetings = ["Boa madrugada", "Bom dia", "Boa tarde", "Boa noite"]; 6 | const h = new Date().getHours(); 7 | // eslint-disable-next-line no-bitwise 8 | return greetings[(h / 6) >> 0]; 9 | }; 10 | 11 | export default (body: string, contact: Contact): string => { 12 | let ms = ""; 13 | 14 | const Hr = new Date(); 15 | 16 | const dd: string = `0${Hr.getDate()}`.slice(-2); 17 | const mm: string = `0${Hr.getMonth() + 1}`.slice(-2); 18 | const yy: string = Hr.getFullYear().toString(); 19 | const hh: number = Hr.getHours(); 20 | const min: string = `0${Hr.getMinutes()}`.slice(-2); 21 | const ss: string = `0${Hr.getSeconds()}`.slice(-2); 22 | 23 | if (hh >= 6) { 24 | ms = "Bom dia"; 25 | } 26 | if (hh > 11) { 27 | ms = "Boa tarde"; 28 | } 29 | if (hh > 17) { 30 | ms = "Boa noite"; 31 | } 32 | if (hh > 23 || hh < 6) { 33 | ms = "Boa madrugada"; 34 | } 35 | 36 | const protocol = yy + mm + dd + String(hh) + min + ss; 37 | 38 | const hora = `${hh}:${min}:${ss}`; 39 | 40 | const view = { 41 | name: contact ? contact.name : "", 42 | gretting: greeting(), 43 | ms, 44 | protocol, 45 | hora 46 | }; 47 | return Mustache.render(body, view); 48 | }; 49 | -------------------------------------------------------------------------------- /backend/src/helpers/SendRefreshToken.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | 3 | export const SendRefreshToken = (res: Response, token: string): void => { 4 | res.cookie("jrt", token, { httpOnly: true }); 5 | }; 6 | -------------------------------------------------------------------------------- /backend/src/helpers/SerializeUser.ts: -------------------------------------------------------------------------------- 1 | import Queue from "../models/Queue"; 2 | import User from "../models/User"; 3 | import Whatsapp from "../models/Whatsapp"; 4 | 5 | interface SerializedUser { 6 | id: number; 7 | name: string; 8 | email: string; 9 | profile: string; 10 | queues: Queue[]; 11 | whatsapp: Whatsapp; 12 | } 13 | 14 | export const SerializeUser = (user: User): SerializedUser => { 15 | return { 16 | id: user.id, 17 | name: user.name, 18 | email: user.email, 19 | profile: user.profile, 20 | queues: user.queues, 21 | whatsapp: user.whatsapp 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /backend/src/helpers/SerializeWbotMsgId.ts: -------------------------------------------------------------------------------- 1 | import Message from "../models/Message"; 2 | import Ticket from "../models/Ticket"; 3 | 4 | const SerializeWbotMsgId = (ticket: Ticket, message: Message): string => { 5 | const serializedMsgId = `${message.fromMe}_${ticket.contact.number}@${ 6 | ticket.isGroup ? "g" : "c" 7 | }.us_${message.id}`; 8 | 9 | return serializedMsgId; 10 | }; 11 | 12 | export default SerializeWbotMsgId; 13 | -------------------------------------------------------------------------------- /backend/src/helpers/SetTicketMessagesAsRead.ts: -------------------------------------------------------------------------------- 1 | import {} from "@whiskeysockets/baileys"; 2 | import { getIO } from "../libs/socket"; 3 | import Message from "../models/Message"; 4 | import Ticket from "../models/Ticket"; 5 | import { logger } from "../utils/logger"; 6 | // import GetTicketWbot from "./GetTicketWbot"; 7 | 8 | const SetTicketMessagesAsRead = async (ticket: Ticket): Promise => { 9 | await ticket.update({ unreadMessages: 0 }); 10 | 11 | try { 12 | if (ticket.channel === "whatsapp") { 13 | // const wbot = await GetTicketWbot(ticket); 14 | 15 | logger.info(`Ticket ${ticket.id} messages read`); 16 | } 17 | 18 | await Message.update( 19 | { read: true }, 20 | { 21 | where: { 22 | ticketId: ticket.id, 23 | read: false 24 | } 25 | } 26 | ); 27 | } catch (err) { 28 | console.log(err); 29 | logger.warn( 30 | `Could not mark messages as read. Maybe whatsapp session disconnected? Err: ${err}` 31 | ); 32 | } 33 | 34 | const io = getIO(); 35 | io.to(ticket.status) 36 | .to("notification") 37 | .to(`queue-${ticket.queueId}-notification`) 38 | .emit("ticket", { 39 | action: "updateUnread", 40 | ticketId: ticket.id 41 | }); 42 | }; 43 | 44 | export default SetTicketMessagesAsRead; 45 | -------------------------------------------------------------------------------- /backend/src/helpers/UpdateDeletedUserOpenTicketsStatus.ts: -------------------------------------------------------------------------------- 1 | import Ticket from "../models/Ticket"; 2 | import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; 3 | 4 | const UpdateDeletedUserOpenTicketsStatus = async ( 5 | tickets: Ticket[] 6 | ): Promise => { 7 | tickets.forEach(async t => { 8 | const ticketId = t.id.toString(); 9 | 10 | await UpdateTicketService({ 11 | ticketData: { status: "pending" }, 12 | ticketId 13 | }); 14 | }); 15 | }; 16 | 17 | export default UpdateDeletedUserOpenTicketsStatus; 18 | -------------------------------------------------------------------------------- /backend/src/middleware/isAuth.ts: -------------------------------------------------------------------------------- 1 | import { verify } from "jsonwebtoken"; 2 | import { Request, Response, NextFunction } from "express"; 3 | 4 | import AppError from "../errors/AppError"; 5 | import authConfig from "../config/auth"; 6 | 7 | interface TokenPayload { 8 | id: string; 9 | username: string; 10 | profile: string; 11 | iat: number; 12 | exp: number; 13 | } 14 | 15 | const isAuth = (req: Request, res: Response, next: NextFunction): void => { 16 | const authHeader = req.headers.authorization; 17 | 18 | if (!authHeader) { 19 | throw new AppError("ERR_SESSION_EXPIRED", 401); 20 | } 21 | 22 | const [, token] = authHeader.split(" "); 23 | 24 | try { 25 | const decoded = verify(token, authConfig.secret); 26 | const { id, profile } = decoded as TokenPayload; 27 | 28 | req.user = { 29 | id, 30 | profile 31 | }; 32 | } catch (err) { 33 | throw new AppError( 34 | "Invalid token. We'll try to assign a new one on next request", 35 | 403 36 | ); 37 | } 38 | 39 | return next(); 40 | }; 41 | 42 | export default isAuth; 43 | -------------------------------------------------------------------------------- /backend/src/middleware/isAuthApi.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | 3 | import AppError from "../errors/AppError"; 4 | import ListSettingByValueService from "../services/SettingServices/ListSettingByValueService"; 5 | 6 | const isAuthApi = async ( 7 | req: Request, 8 | res: Response, 9 | next: NextFunction 10 | ): Promise => { 11 | const authHeader = req.headers.authorization; 12 | 13 | if (!authHeader) { 14 | throw new AppError("ERR_SESSION_EXPIRED", 401); 15 | } 16 | 17 | const [, token] = authHeader.split(" "); 18 | 19 | try { 20 | const getToken = await ListSettingByValueService(token); 21 | if (!getToken) { 22 | throw new AppError("ERR_SESSION_EXPIRED", 401); 23 | } 24 | 25 | if (getToken.value !== token) { 26 | throw new AppError("ERR_SESSION_EXPIRED", 401); 27 | } 28 | } catch (err) { 29 | console.log(err); 30 | throw new AppError( 31 | "Invalid token. We'll try to assign a new one on next request", 32 | 403 33 | ); 34 | } 35 | 36 | return next(); 37 | }; 38 | 39 | export default isAuthApi; 40 | -------------------------------------------------------------------------------- /backend/src/models/Baileys.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | Default, 10 | ForeignKey 11 | } from "sequelize-typescript"; 12 | import Whatsapp from "./Whatsapp"; 13 | 14 | @Table 15 | class Baileys extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Default(null) 22 | @Column 23 | contacts: string; 24 | 25 | @Default(null) 26 | @Column 27 | chats: string; 28 | 29 | @CreatedAt 30 | createdAt: Date; 31 | 32 | @UpdatedAt 33 | updatedAt: Date; 34 | 35 | @ForeignKey(() => Whatsapp) 36 | @Column 37 | whatsappId: number; 38 | } 39 | 40 | export default Baileys; 41 | -------------------------------------------------------------------------------- /backend/src/models/BaileysSessions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | Default, 10 | ForeignKey 11 | } from "sequelize-typescript"; 12 | import Whatsapp from "./Whatsapp"; 13 | 14 | @Table 15 | class BaileysSessions extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Default(null) 22 | @Column 23 | value: string; 24 | 25 | @Default(null) 26 | @Column 27 | name: string; 28 | 29 | @CreatedAt 30 | createdAt: Date; 31 | 32 | @UpdatedAt 33 | updatedAt: Date; 34 | 35 | @ForeignKey(() => Whatsapp) 36 | @Column 37 | whatsappId: number; 38 | } 39 | 40 | export default BaileysSessions; 41 | -------------------------------------------------------------------------------- /backend/src/models/Chatbot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | AllowNull, 10 | ForeignKey, 11 | BelongsTo, 12 | HasMany 13 | } from "sequelize-typescript"; 14 | import Queue from "./Queue"; 15 | 16 | @Table 17 | class Chatbot extends Model { 18 | @PrimaryKey 19 | @AutoIncrement 20 | @Column 21 | id: number; 22 | 23 | @AllowNull(false) 24 | @Column 25 | name: string; 26 | 27 | @Column 28 | greetingMessage: string; 29 | 30 | @ForeignKey(() => Queue) 31 | @Column 32 | queueId: number; 33 | 34 | @BelongsTo(() => Queue) 35 | queue: Queue; 36 | 37 | @ForeignKey(() => Chatbot) 38 | @Column 39 | chatbotId: number; 40 | 41 | @Column 42 | isAgent: boolean; 43 | 44 | @BelongsTo(() => Chatbot) 45 | mainChatbot: Chatbot; 46 | 47 | @HasMany(() => Chatbot) 48 | options: Chatbot[]; 49 | 50 | @CreatedAt 51 | createdAt: Date; 52 | 53 | @UpdatedAt 54 | updatedAt: Date; 55 | } 56 | 57 | export default Chatbot; 58 | -------------------------------------------------------------------------------- /backend/src/models/Contact.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | AllowNull, 10 | Unique, 11 | Default, 12 | HasMany 13 | } from "sequelize-typescript"; 14 | import ContactCustomField from "./ContactCustomField"; 15 | import Schedule from "./Schedule"; 16 | import Ticket from "./Ticket"; 17 | 18 | @Table 19 | class Contact extends Model { 20 | @PrimaryKey 21 | @AutoIncrement 22 | @Column 23 | id: number; 24 | 25 | @Column 26 | name: string; 27 | 28 | @AllowNull(false) 29 | @Unique 30 | @Column 31 | number: string; 32 | 33 | @AllowNull(false) 34 | @Default("") 35 | @Column 36 | email: string; 37 | 38 | @Column 39 | profilePicUrl: string; 40 | 41 | @Default(false) 42 | @Column 43 | isGroup: boolean; 44 | 45 | @CreatedAt 46 | createdAt: Date; 47 | 48 | @UpdatedAt 49 | updatedAt: Date; 50 | 51 | @HasMany(() => Ticket) 52 | tickets: Ticket[]; 53 | 54 | @HasMany(() => ContactCustomField) 55 | extraInfo: ContactCustomField[]; 56 | 57 | @HasMany(() => Schedule, { 58 | onUpdate: "CASCADE", 59 | onDelete: "CASCADE", 60 | hooks: true 61 | }) 62 | schedules: Schedule[]; 63 | 64 | } 65 | 66 | export default Contact; 67 | -------------------------------------------------------------------------------- /backend/src/models/ContactCustomField.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | ForeignKey, 10 | BelongsTo 11 | } from "sequelize-typescript"; 12 | import Contact from "./Contact"; 13 | 14 | @Table 15 | class ContactCustomField extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Column 22 | name: string; 23 | 24 | @Column 25 | value: string; 26 | 27 | @ForeignKey(() => Contact) 28 | @Column 29 | contactId: number; 30 | 31 | @BelongsTo(() => Contact) 32 | contact: Contact; 33 | 34 | @CreatedAt 35 | createdAt: Date; 36 | 37 | @UpdatedAt 38 | updatedAt: Date; 39 | } 40 | 41 | export default ContactCustomField; 42 | -------------------------------------------------------------------------------- /backend/src/models/DialogChatBots.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | ForeignKey, 10 | BelongsTo 11 | } from "sequelize-typescript"; 12 | import Chatbot from "./Chatbot"; 13 | import Contact from "./Contact"; 14 | import Queue from "./Queue"; 15 | 16 | @Table 17 | class DialogChatBots extends Model { 18 | @PrimaryKey 19 | @AutoIncrement 20 | @Column 21 | id: number; 22 | 23 | @Column 24 | awaiting: number; 25 | 26 | @ForeignKey(() => Contact) 27 | @Column 28 | contactId: number; 29 | 30 | @ForeignKey(() => Queue) 31 | @Column 32 | queueId: number; 33 | 34 | @ForeignKey(() => Chatbot) 35 | @Column 36 | chatbotId: number; 37 | 38 | @BelongsTo(() => Chatbot) 39 | chatbots: Chatbot; 40 | 41 | @CreatedAt 42 | createdAt: Date; 43 | 44 | @UpdatedAt 45 | updatedAt: Date; 46 | } 47 | 48 | export default DialogChatBots; 49 | -------------------------------------------------------------------------------- /backend/src/models/MassMessages.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | ForeignKey, 9 | AutoIncrement, 10 | BelongsTo 11 | } from "sequelize-typescript"; 12 | import Whatsapp from "./Whatsapp"; 13 | 14 | @Table 15 | class MassMessages extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Column 22 | message: string; 23 | 24 | @Column 25 | phone: string; 26 | 27 | @Column 28 | status: string; 29 | 30 | @CreatedAt 31 | createdAt: Date; 32 | 33 | @UpdatedAt 34 | updatedAt: Date; 35 | 36 | @ForeignKey(() => Whatsapp) 37 | @Column 38 | whatsappId: number; 39 | 40 | @BelongsTo(() => Whatsapp, "whatsappId") 41 | whatsapp: Whatsapp; 42 | } 43 | 44 | export default MassMessages; 45 | -------------------------------------------------------------------------------- /backend/src/models/Queue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | AllowNull, 10 | Unique, 11 | BelongsToMany, 12 | HasMany 13 | } from "sequelize-typescript"; 14 | import Chatbot from "./Chatbot"; 15 | import User from "./User"; 16 | import UserQueue from "./UserQueue"; 17 | 18 | import Whatsapp from "./Whatsapp"; 19 | import WhatsappQueue from "./WhatsappQueue"; 20 | 21 | @Table 22 | class Queue extends Model { 23 | @PrimaryKey 24 | @AutoIncrement 25 | @Column 26 | id: number; 27 | 28 | @AllowNull(false) 29 | @Unique 30 | @Column 31 | name: string; 32 | 33 | @AllowNull(false) 34 | @Unique 35 | @Column 36 | color: string; 37 | 38 | @Column 39 | greetingMessage: string; 40 | 41 | @CreatedAt 42 | createdAt: Date; 43 | 44 | @UpdatedAt 45 | updatedAt: Date; 46 | 47 | @BelongsToMany(() => Whatsapp, () => WhatsappQueue) 48 | whatsapps: Array; 49 | 50 | @BelongsToMany(() => User, () => UserQueue) 51 | users: Array; 52 | 53 | @HasMany(() => Chatbot) 54 | chatbots: Chatbot[]; 55 | } 56 | 57 | export default Queue; 58 | -------------------------------------------------------------------------------- /backend/src/models/QuickAnswer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | DataType, 5 | CreatedAt, 6 | UpdatedAt, 7 | Model, 8 | PrimaryKey, 9 | AutoIncrement 10 | } from "sequelize-typescript"; 11 | 12 | @Table 13 | class QuickAnswer extends Model { 14 | @PrimaryKey 15 | @AutoIncrement 16 | @Column 17 | id: number; 18 | 19 | @Column(DataType.TEXT) 20 | shortcut: string; 21 | 22 | @Column(DataType.TEXT) 23 | message: string; 24 | 25 | @CreatedAt 26 | createdAt: Date; 27 | 28 | @UpdatedAt 29 | updatedAt: Date; 30 | } 31 | 32 | export default QuickAnswer; 33 | -------------------------------------------------------------------------------- /backend/src/models/Schedule.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | DataType, 10 | BelongsTo, 11 | ForeignKey 12 | } from "sequelize-typescript"; 13 | import Contact from "./Contact"; 14 | import Ticket from "./Ticket"; 15 | import User from "./User"; 16 | 17 | @Table 18 | class Schedule extends Model { 19 | @PrimaryKey 20 | @AutoIncrement 21 | @Column 22 | id: number; 23 | 24 | @Column(DataType.TEXT) 25 | body: string; 26 | 27 | @Column 28 | sendAt: Date; 29 | 30 | @Column 31 | sentAt: Date; 32 | 33 | @ForeignKey(() => Contact) 34 | @Column 35 | contactId: number; 36 | 37 | @ForeignKey(() => Ticket) 38 | @Column 39 | ticketId: number; 40 | 41 | @ForeignKey(() => User) 42 | @Column 43 | userId: number; 44 | 45 | @Column(DataType.STRING) 46 | status: string; 47 | 48 | @CreatedAt 49 | createdAt: Date; 50 | 51 | @UpdatedAt 52 | updatedAt: Date; 53 | 54 | @BelongsTo(() => Contact, "contactId") 55 | contact: Contact; 56 | 57 | @BelongsTo(() => Ticket) 58 | ticket: Ticket; 59 | 60 | @BelongsTo(() => User) 61 | user: User; 62 | } 63 | 64 | export default Schedule; 65 | -------------------------------------------------------------------------------- /backend/src/models/Setting.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey 8 | } from "sequelize-typescript"; 9 | 10 | @Table 11 | class Setting extends Model { 12 | @PrimaryKey 13 | @Column 14 | key: string; 15 | 16 | @Column 17 | value: string; 18 | 19 | @CreatedAt 20 | createdAt: Date; 21 | 22 | @UpdatedAt 23 | updatedAt: Date; 24 | } 25 | 26 | export default Setting; 27 | -------------------------------------------------------------------------------- /backend/src/models/SettingMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | ForeignKey, 9 | AutoIncrement 10 | } from "sequelize-typescript"; 11 | import Whatsapp from "./Whatsapp"; 12 | 13 | @Table 14 | class SettingMessage extends Model { 15 | @PrimaryKey 16 | @AutoIncrement 17 | @Column 18 | id: number; 19 | 20 | @Column 21 | contacts: boolean; 22 | 23 | @Column 24 | photo: boolean; 25 | 26 | @Column 27 | random: boolean; 28 | 29 | @Column 30 | limit: number; 31 | 32 | @Column 33 | minutes: number; 34 | 35 | @Column 36 | seconds: number; 37 | 38 | @Column 39 | sendToday: number; 40 | 41 | @Column 42 | status: string; 43 | 44 | @CreatedAt 45 | createdAt: Date; 46 | 47 | @UpdatedAt 48 | updatedAt: Date; 49 | 50 | @ForeignKey(() => Whatsapp) 51 | @Column 52 | whatsappId: number; 53 | } 54 | 55 | export default SettingMessage; 56 | -------------------------------------------------------------------------------- /backend/src/models/Tag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | BelongsToMany 10 | } from "sequelize-typescript"; 11 | import Ticket from "./Ticket"; 12 | import TicketTag from "./TicketTag"; 13 | 14 | @Table 15 | class Tag extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Column 22 | name: string; 23 | 24 | @Column 25 | color: string; 26 | 27 | @BelongsToMany(() => Ticket, () => TicketTag) 28 | tickets: Ticket[]; 29 | 30 | @CreatedAt 31 | createdAt: Date; 32 | 33 | @UpdatedAt 34 | updatedAt: Date; 35 | } 36 | 37 | export default Tag; 38 | -------------------------------------------------------------------------------- /backend/src/models/TicketTag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | ForeignKey, 8 | BelongsTo 9 | } from "sequelize-typescript"; 10 | import Tag from "./Tag"; 11 | import Ticket from "./Ticket"; 12 | 13 | @Table({ 14 | tableName: 'TicketTags' 15 | }) 16 | class TicketTag extends Model { 17 | @ForeignKey(() => Ticket) 18 | @Column 19 | ticketId: number; 20 | 21 | @ForeignKey(() => Tag) 22 | @Column 23 | tagId: number; 24 | 25 | @CreatedAt 26 | createdAt: Date; 27 | 28 | @UpdatedAt 29 | updatedAt: Date; 30 | 31 | @BelongsTo(() => Ticket) 32 | ticket: Ticket; 33 | 34 | @BelongsTo(() => Tag) 35 | tag: Tag; 36 | } 37 | 38 | export default TicketTag; 39 | -------------------------------------------------------------------------------- /backend/src/models/UserQueue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | ForeignKey 8 | } from "sequelize-typescript"; 9 | import Queue from "./Queue"; 10 | import User from "./User"; 11 | 12 | @Table 13 | class UserQueue extends Model { 14 | @ForeignKey(() => User) 15 | @Column 16 | userId: number; 17 | 18 | @ForeignKey(() => Queue) 19 | @Column 20 | queueId: number; 21 | 22 | @CreatedAt 23 | createdAt: Date; 24 | 25 | @UpdatedAt 26 | updatedAt: Date; 27 | } 28 | 29 | export default UserQueue; 30 | -------------------------------------------------------------------------------- /backend/src/models/WhatsappQueue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | ForeignKey, 8 | BelongsTo 9 | } from "sequelize-typescript"; 10 | import Queue from "./Queue"; 11 | import Whatsapp from "./Whatsapp"; 12 | 13 | @Table 14 | class WhatsappQueue extends Model { 15 | @ForeignKey(() => Whatsapp) 16 | @Column 17 | whatsappId: number; 18 | 19 | @ForeignKey(() => Queue) 20 | @Column 21 | queueId: number; 22 | 23 | @CreatedAt 24 | createdAt: Date; 25 | 26 | @UpdatedAt 27 | updatedAt: Date; 28 | 29 | @BelongsTo(() => Queue) 30 | queue: Queue; 31 | } 32 | 33 | export default WhatsappQueue; 34 | -------------------------------------------------------------------------------- /backend/src/routes/apiRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import multer from "multer"; 3 | import uploadConfig from "../config/upload"; 4 | 5 | import * as ApiController from "../controllers/ApiController"; 6 | import isAuthApi from "../middleware/isAuthApi"; 7 | 8 | const upload = multer(uploadConfig); 9 | 10 | const ApiRoutes = express.Router(); 11 | 12 | ApiRoutes.post("/send", isAuthApi, upload.array("medias"), ApiController.index); 13 | 14 | export default ApiRoutes; 15 | -------------------------------------------------------------------------------- /backend/src/routes/authRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as SessionController from "../controllers/SessionController"; 3 | import * as UserController from "../controllers/UserController"; 4 | import isAuth from "../middleware/isAuth"; 5 | 6 | const authRoutes = Router(); 7 | 8 | authRoutes.post("/signup", UserController.store); 9 | 10 | authRoutes.post("/login", SessionController.store); 11 | 12 | authRoutes.post("/refresh_token", SessionController.update); 13 | 14 | authRoutes.delete("/logout", isAuth, SessionController.remove); 15 | 16 | export default authRoutes; 17 | -------------------------------------------------------------------------------- /backend/src/routes/bulkMessageRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import multer from "multer"; 3 | import uploadConfig from "../config/upload"; 4 | 5 | import * as BulkMessageController from "../controllers/BulkMessageController"; 6 | import isAuth from "../middleware/isAuth"; 7 | 8 | const upload = multer(uploadConfig); 9 | 10 | const bulkMessageRoutes = express.Router(); 11 | 12 | bulkMessageRoutes.post( 13 | "/send", 14 | isAuth, 15 | upload.array("medias"), 16 | BulkMessageController.index 17 | ); 18 | 19 | bulkMessageRoutes.post("/", isAuth, BulkMessageController.store); 20 | 21 | bulkMessageRoutes.get("/report", isAuth, BulkMessageController.show); 22 | 23 | bulkMessageRoutes.delete( 24 | "/d/:massMessageId", 25 | isAuth, 26 | BulkMessageController.remove 27 | ); 28 | 29 | bulkMessageRoutes.delete("/clean", isAuth, BulkMessageController.clean); 30 | 31 | export default bulkMessageRoutes; 32 | -------------------------------------------------------------------------------- /backend/src/routes/chatBotRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as ChatbotController from "../controllers/ChatbotController"; 5 | 6 | const chatBotRoutes = Router(); 7 | 8 | chatBotRoutes.get("/chatbot", isAuth, ChatbotController.index); 9 | 10 | chatBotRoutes.post("/chatbot", isAuth, ChatbotController.store); 11 | 12 | chatBotRoutes.get("/chatbot/:chatbotId", isAuth, ChatbotController.show); 13 | 14 | chatBotRoutes.put("/chatbot/:chatbotId", isAuth, ChatbotController.update); 15 | 16 | chatBotRoutes.delete("/chatbot/:chatbotId", isAuth, ChatbotController.remove); 17 | 18 | export default chatBotRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/contactRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as ContactController from "../controllers/ContactController"; 5 | import * as ImportPhoneContactsController from "../controllers/ImportPhoneContactsController"; 6 | 7 | const contactRoutes = express.Router(); 8 | 9 | contactRoutes.post( 10 | "/contacts/import", 11 | isAuth, 12 | ImportPhoneContactsController.store 13 | ); 14 | 15 | contactRoutes.get("/contacts", isAuth, ContactController.index); 16 | 17 | contactRoutes.get("/contacts/list", isAuth, ContactController.list); 18 | 19 | contactRoutes.get("/contacts/:contactId", isAuth, ContactController.show); 20 | 21 | contactRoutes.post("/contacts", isAuth, ContactController.store); 22 | 23 | contactRoutes.post("/contact", isAuth, ContactController.getContact); 24 | 25 | contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update); 26 | 27 | contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove); 28 | 29 | 30 | export default contactRoutes; 31 | -------------------------------------------------------------------------------- /backend/src/routes/messageRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import multer from "multer"; 3 | import isAuth from "../middleware/isAuth"; 4 | import uploadConfig from "../config/upload"; 5 | 6 | import * as MessageController from "../controllers/MessageController"; 7 | 8 | const messageRoutes = Router(); 9 | 10 | const upload = multer(uploadConfig); 11 | 12 | messageRoutes.get("/messages/:ticketId", isAuth, MessageController.index); 13 | 14 | messageRoutes.post( 15 | "/messages/:ticketId", 16 | isAuth, 17 | upload.array("medias"), 18 | MessageController.store 19 | ); 20 | 21 | messageRoutes.delete("/messages/:messageId", isAuth, MessageController.remove); 22 | 23 | export default messageRoutes; 24 | -------------------------------------------------------------------------------- /backend/src/routes/queueRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as QueueController from "../controllers/QueueController"; 5 | 6 | const queueRoutes = Router(); 7 | 8 | queueRoutes.get("/queue", isAuth, QueueController.index); 9 | 10 | queueRoutes.post("/queue", isAuth, QueueController.store); 11 | 12 | queueRoutes.get("/queue/:queueId", isAuth, QueueController.show); 13 | 14 | queueRoutes.put("/queue/:queueId", isAuth, QueueController.update); 15 | 16 | queueRoutes.delete("/queue/:queueId", isAuth, QueueController.remove); 17 | 18 | export default queueRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/quickAnswerRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as QuickAnswerController from "../controllers/QuickAnswerController"; 5 | 6 | const quickAnswerRoutes = express.Router(); 7 | 8 | quickAnswerRoutes.get("/quickAnswers", isAuth, QuickAnswerController.index); 9 | 10 | quickAnswerRoutes.get( 11 | "/quickAnswers/:quickAnswerId", 12 | isAuth, 13 | QuickAnswerController.show 14 | ); 15 | 16 | quickAnswerRoutes.post("/quickAnswers", isAuth, QuickAnswerController.store); 17 | 18 | quickAnswerRoutes.put( 19 | "/quickAnswers/:quickAnswerId", 20 | isAuth, 21 | QuickAnswerController.update 22 | ); 23 | 24 | quickAnswerRoutes.delete( 25 | "/quickAnswers/:quickAnswerId", 26 | isAuth, 27 | QuickAnswerController.remove 28 | ); 29 | 30 | export default quickAnswerRoutes; 31 | -------------------------------------------------------------------------------- /backend/src/routes/scheduleRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as ScheduleController from "../controllers/ScheduleController"; 5 | 6 | const scheduleRoutes = express.Router(); 7 | 8 | scheduleRoutes.get("/schedules", isAuth, ScheduleController.index); 9 | 10 | scheduleRoutes.post("/schedules", isAuth, ScheduleController.store); 11 | 12 | scheduleRoutes.put("/schedules/:scheduleId", isAuth, ScheduleController.update); 13 | 14 | scheduleRoutes.get("/schedules/:scheduleId", isAuth, ScheduleController.show); 15 | 16 | scheduleRoutes.delete("/schedules/:scheduleId", isAuth, ScheduleController.remove); 17 | 18 | export default scheduleRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/settingMessageRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as SettingMessageController from "../controllers/SettingMessageController"; 5 | 6 | const settingRoutes = Router(); 7 | 8 | settingRoutes.get("/settingsMessage", isAuth, SettingMessageController.index); 9 | settingRoutes.get( 10 | "/settingsMessage/:whatsappId", 11 | isAuth, 12 | SettingMessageController.show 13 | ); 14 | 15 | settingRoutes.post("/settingsMessage", isAuth, SettingMessageController.store); 16 | 17 | settingRoutes.put("/settingsMessage", isAuth, SettingMessageController.update); 18 | 19 | export default settingRoutes; 20 | -------------------------------------------------------------------------------- /backend/src/routes/settingRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as SettingController from "../controllers/SettingController"; 5 | 6 | const settingRoutes = Router(); 7 | 8 | settingRoutes.get("/settings", isAuth, SettingController.index); 9 | 10 | // routes.get("/settings/:settingKey", isAuth, SettingsController.show); 11 | 12 | // change setting key to key in future 13 | settingRoutes.put("/settings/:settingKey", isAuth, SettingController.update); 14 | 15 | export default settingRoutes; 16 | -------------------------------------------------------------------------------- /backend/src/routes/tagRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as TagController from "../controllers/TagController"; 5 | 6 | const tagRoutes = express.Router(); 7 | 8 | tagRoutes.get("/tags/list", isAuth, TagController.list); 9 | 10 | tagRoutes.get("/tags", isAuth, TagController.index); 11 | 12 | tagRoutes.post("/tags", isAuth, TagController.store); 13 | 14 | tagRoutes.put("/tags/:tagId", isAuth, TagController.update); 15 | 16 | tagRoutes.get("/tags/:tagId", isAuth, TagController.show); 17 | 18 | tagRoutes.delete("/tags/:tagId", isAuth, TagController.remove); 19 | 20 | tagRoutes.post("/tags/sync", isAuth, TagController.syncTags); 21 | 22 | export default tagRoutes; 23 | -------------------------------------------------------------------------------- /backend/src/routes/ticketRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as TicketController from "../controllers/TicketController"; 5 | 6 | const ticketRoutes = express.Router(); 7 | 8 | ticketRoutes.get("/tickets", isAuth, TicketController.index); 9 | 10 | ticketRoutes.get("/tickets/:ticketId", isAuth, TicketController.show); 11 | 12 | ticketRoutes.post("/tickets", isAuth, TicketController.store); 13 | 14 | ticketRoutes.put("/tickets/:ticketId", isAuth, TicketController.update); 15 | 16 | ticketRoutes.delete("/tickets/:ticketId", isAuth, TicketController.remove); 17 | 18 | export default ticketRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/userRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import isAuth from "../middleware/isAuth"; 4 | import * as UserController from "../controllers/UserController"; 5 | 6 | const userRoutes = Router(); 7 | 8 | userRoutes.get("/users", isAuth, UserController.index); 9 | 10 | userRoutes.post("/users", isAuth, UserController.store); 11 | 12 | userRoutes.put("/users/:userId", isAuth, UserController.update); 13 | 14 | userRoutes.get("/users/:userId", isAuth, UserController.show); 15 | 16 | userRoutes.delete("/users/:userId", isAuth, UserController.remove); 17 | 18 | export default userRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/webHookRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as WebHooksController from "../controllers/WebHookController"; 3 | const webHooksRoutes = Router(); 4 | 5 | webHooksRoutes.get("/", WebHooksController.index); 6 | webHooksRoutes.post("/", WebHooksController.webHook); 7 | export default webHooksRoutes; 8 | -------------------------------------------------------------------------------- /backend/src/routes/whatsappRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as WhatsAppController from "../controllers/WhatsAppController"; 5 | 6 | const whatsappRoutes = express.Router(); 7 | 8 | whatsappRoutes.get("/whatsapp/", isAuth, WhatsAppController.index); 9 | 10 | whatsappRoutes.post("/whatsapp/", isAuth, WhatsAppController.store); 11 | 12 | whatsappRoutes.get("/whatsapp/:whatsappId", isAuth, WhatsAppController.show); 13 | 14 | whatsappRoutes.put("/whatsapp/:whatsappId", isAuth, WhatsAppController.update); 15 | 16 | whatsappRoutes.delete( 17 | "/whatsapp/:whatsappId", 18 | isAuth, 19 | WhatsAppController.remove 20 | ); 21 | 22 | export default whatsappRoutes; 23 | -------------------------------------------------------------------------------- /backend/src/routes/whatsappSessionRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import WhatsAppSessionController from "../controllers/WhatsAppSessionController"; 5 | 6 | const whatsappSessionRoutes = Router(); 7 | 8 | whatsappSessionRoutes.post( 9 | "/whatsappsession/:whatsappId", 10 | isAuth, 11 | WhatsAppSessionController.store 12 | ); 13 | 14 | whatsappSessionRoutes.put( 15 | "/whatsappsession/:whatsappId", 16 | isAuth, 17 | WhatsAppSessionController.update 18 | ); 19 | 20 | whatsappSessionRoutes.delete( 21 | "/whatsappsession/:whatsappId", 22 | isAuth, 23 | WhatsAppSessionController.remove 24 | ); 25 | 26 | export default whatsappSessionRoutes; 27 | -------------------------------------------------------------------------------- /backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import gracefulShutdown from "http-graceful-shutdown"; 2 | import app from "./app"; 3 | import { initIO } from "./libs/socket"; 4 | import { logger } from "./utils/logger"; 5 | import { StartAllWhatsAppsSessions } from "./services/WbotServices/StartAllWhatsAppsSessions"; 6 | 7 | const server = app.listen(process.env.PORT, () => { 8 | logger.info(`Server started on port: ${process.env.PORT}`); 9 | }); 10 | 11 | initIO(server); 12 | StartAllWhatsAppsSessions(); 13 | gracefulShutdown(server); 14 | -------------------------------------------------------------------------------- /backend/src/services/AuthServices/RefreshTokenService.ts: -------------------------------------------------------------------------------- 1 | import { verify } from "jsonwebtoken"; 2 | import { Response as Res } from "express"; 3 | 4 | import User from "../../models/User"; 5 | import AppError from "../../errors/AppError"; 6 | import ShowUserService from "../UserServices/ShowUserService"; 7 | import authConfig from "../../config/auth"; 8 | import { 9 | createAccessToken, 10 | createRefreshToken 11 | } from "../../helpers/CreateTokens"; 12 | 13 | interface RefreshTokenPayload { 14 | id: string; 15 | tokenVersion: number; 16 | } 17 | 18 | interface Response { 19 | user: User; 20 | newToken: string; 21 | refreshToken: string; 22 | } 23 | 24 | export const RefreshTokenService = async ( 25 | res: Res, 26 | token: string 27 | ): Promise => { 28 | try { 29 | const decoded = verify(token, authConfig.refreshSecret); 30 | const { id, tokenVersion } = decoded as RefreshTokenPayload; 31 | 32 | const user = await ShowUserService(id); 33 | 34 | if (user.tokenVersion !== tokenVersion) { 35 | res.clearCookie("jrt"); 36 | throw new AppError("ERR_SESSION_EXPIRED", 401); 37 | } 38 | 39 | const newToken = createAccessToken(user); 40 | const refreshToken = createRefreshToken(user); 41 | 42 | return { user, newToken, refreshToken }; 43 | } catch (err) { 44 | res.clearCookie("jrt"); 45 | throw new AppError("ERR_SESSION_EXPIRED", 401); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /backend/src/services/BaileysServices/DeleteBaileysService.ts: -------------------------------------------------------------------------------- 1 | import Baileys from "../../models/Baileys"; 2 | 3 | const DeleteBaileysService = async (id: string | number): Promise => { 4 | const baileysData = await Baileys.findOne({ 5 | where: { 6 | whatsappId: id 7 | } 8 | }); 9 | 10 | if (baileysData) { 11 | await baileysData.destroy(); 12 | } 13 | }; 14 | 15 | export default DeleteBaileysService; 16 | -------------------------------------------------------------------------------- /backend/src/services/BaileysServices/ShowBaileysService.ts: -------------------------------------------------------------------------------- 1 | import Baileys from "../../models/Baileys"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const ShowBaileysService = async (id: string | number): Promise => { 5 | const baileysData = await Baileys.findOne({ 6 | where: { 7 | whatsappId: id 8 | } 9 | }); 10 | 11 | if (!baileysData) { 12 | throw new AppError("ERR_NO_BAILEYS_DATA_FOUND", 404); 13 | } 14 | 15 | return baileysData; 16 | }; 17 | 18 | export default ShowBaileysService; 19 | -------------------------------------------------------------------------------- /backend/src/services/ChatBotServices/CreateChatBotServices.ts: -------------------------------------------------------------------------------- 1 | import Chatbot from "../../models/Chatbot"; 2 | 3 | interface ChatbotData { 4 | name: string; 5 | color: string; 6 | greetingMessage?: string; 7 | } 8 | 9 | const CreateChatBotServices = async ( 10 | chatBotData: ChatbotData 11 | ): Promise => { 12 | const chatBot = await Chatbot.create(chatBotData); 13 | return chatBot; 14 | }; 15 | 16 | export default CreateChatBotServices; 17 | -------------------------------------------------------------------------------- /backend/src/services/ChatBotServices/DeleteChatBotServices.ts: -------------------------------------------------------------------------------- 1 | import ShowChatBotServices from "./ShowChatBotServices"; 2 | 3 | const DeleteChatBotServices = async ( 4 | chatbotId: number | string, 5 | ): Promise => { 6 | const chatbot = await ShowChatBotServices(chatbotId); 7 | 8 | await chatbot.destroy(); 9 | }; 10 | 11 | export default DeleteChatBotServices; 12 | -------------------------------------------------------------------------------- /backend/src/services/ChatBotServices/ListChatBotServices.ts: -------------------------------------------------------------------------------- 1 | import { Op } from "sequelize"; 2 | import Chatbot from "../../models/Chatbot"; 3 | 4 | const ListChatBotService = async (): Promise => { 5 | const chatBot = await Chatbot.findAll({ 6 | where: { 7 | queueId: { 8 | [Op.or]: [null] 9 | } 10 | }, 11 | order: [["id", "ASC"]] 12 | }); 13 | 14 | return chatBot; 15 | }; 16 | 17 | export default ListChatBotService; 18 | -------------------------------------------------------------------------------- /backend/src/services/ChatBotServices/ShowChatBotByChatbotIdServices.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Chatbot from "../../models/Chatbot"; 3 | 4 | const ShowChatBotByChatbotIdServices = async ( 5 | chatbotId: number | string 6 | ): Promise => { 7 | const queue = await Chatbot.findOne({ 8 | where: { chatbotId }, 9 | include: [ 10 | { 11 | model: Chatbot, 12 | as: "mainChatbot", 13 | attributes: ["id", "name", "greetingMessage"], 14 | order: [[{ model: Chatbot, as: "mainChatbot" }, "id", "ASC"]] 15 | }, 16 | { 17 | model: Chatbot, 18 | as: "options", 19 | order: [[{ model: Chatbot, as: "options" }, "id", "ASC"]], 20 | attributes: ["id", "name", "greetingMessage"] 21 | } 22 | ], 23 | order: [["id", "asc"]] 24 | }); 25 | 26 | if (!queue) { 27 | throw new AppError("ERR_CHATBOT_NOT_FOUND_SERVICE", 404); 28 | } 29 | 30 | return queue; 31 | }; 32 | 33 | export default ShowChatBotByChatbotIdServices; 34 | -------------------------------------------------------------------------------- /backend/src/services/ChatBotServices/ShowChatBotServices.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Chatbot from "../../models/Chatbot"; 3 | 4 | const ShowChatBotServices = async (id: number | string): Promise => { 5 | const queue = await Chatbot.findOne({ 6 | where: { 7 | id 8 | }, 9 | order: [ 10 | [{ model: Chatbot, as: "mainChatbot" }, "id", "ASC"], 11 | [{ model: Chatbot, as: "options" }, "id", "ASC"], 12 | ["id", "asc"] 13 | ], 14 | include: [ 15 | { 16 | model: Chatbot, 17 | as: "mainChatbot" 18 | }, 19 | { 20 | model: Chatbot, 21 | as: "options" 22 | } 23 | ] 24 | }); 25 | 26 | if (!queue) { 27 | throw new AppError("Chatbot not found", 404); 28 | } 29 | 30 | return queue; 31 | }; 32 | 33 | export default ShowChatBotServices; 34 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/CreateContactService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Contact from "../../models/Contact"; 3 | 4 | interface ExtraInfo { 5 | name: string; 6 | value: string; 7 | } 8 | 9 | interface Request { 10 | name: string; 11 | number: string; 12 | email?: string; 13 | profilePicUrl?: string; 14 | extraInfo?: ExtraInfo[]; 15 | } 16 | 17 | const CreateContactService = async ({ 18 | name, 19 | number, 20 | email = "", 21 | extraInfo = [] 22 | }: Request): Promise => { 23 | const numberExists = await Contact.findOne({ 24 | where: { number } 25 | }); 26 | 27 | if (numberExists) { 28 | throw new AppError("ERR_DUPLICATED_CONTACT"); 29 | } 30 | 31 | const contact = await Contact.create( 32 | { 33 | name, 34 | number, 35 | email, 36 | extraInfo 37 | }, 38 | { 39 | include: ["extraInfo"] 40 | } 41 | ); 42 | 43 | return contact; 44 | }; 45 | 46 | export default CreateContactService; 47 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/DeleteContactService.ts: -------------------------------------------------------------------------------- 1 | import Contact from "../../models/Contact"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteContactService = async (id: string): Promise => { 5 | const contact = await Contact.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!contact) { 10 | throw new AppError("ERR_NO_CONTACT_FOUND", 404); 11 | } 12 | 13 | await contact.destroy(); 14 | }; 15 | 16 | export default DeleteContactService; 17 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/GetContactService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Contact from "../../models/Contact"; 3 | import CreateContactService from "./CreateContactService"; 4 | 5 | interface ExtraInfo { 6 | name: string; 7 | value: string; 8 | } 9 | 10 | interface Request { 11 | name: string; 12 | number: string; 13 | email?: string; 14 | profilePicUrl?: string; 15 | extraInfo?: ExtraInfo[]; 16 | } 17 | 18 | const GetContactService = async ({ name, number }: Request): Promise => { 19 | const numberExists = await Contact.findOne({ 20 | where: { number } 21 | }); 22 | 23 | if (!numberExists) { 24 | const contact = await CreateContactService({ 25 | name, 26 | number, 27 | }) 28 | 29 | if (contact == null) 30 | throw new AppError("CONTACT_NOT_FIND") 31 | else 32 | return contact 33 | } 34 | 35 | return numberExists 36 | }; 37 | 38 | export default GetContactService; -------------------------------------------------------------------------------- /backend/src/services/ContactServices/ListContactsService.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, Op } from "sequelize"; 2 | import Contact from "../../models/Contact"; 3 | 4 | interface Request { 5 | searchParam?: string; 6 | pageNumber?: string; 7 | } 8 | 9 | interface Response { 10 | contacts: Contact[]; 11 | count: number; 12 | hasMore: boolean; 13 | } 14 | 15 | const ListContactsService = async ({ 16 | searchParam = "", 17 | pageNumber = "1" 18 | }: Request): Promise => { 19 | const whereCondition = { 20 | [Op.or]: [ 21 | { 22 | name: Sequelize.where( 23 | Sequelize.fn("LOWER", Sequelize.col("name")), 24 | "LIKE", 25 | `%${searchParam.toLowerCase().trim()}%` 26 | ) 27 | }, 28 | { number: { [Op.like]: `%${searchParam.toLowerCase().trim()}%` } } 29 | ] 30 | }; 31 | const limit = 20; 32 | const offset = limit * (+pageNumber - 1); 33 | 34 | const { count, rows: contacts } = await Contact.findAndCountAll({ 35 | where: whereCondition, 36 | limit, 37 | offset, 38 | order: [["name", "ASC"]] 39 | }); 40 | 41 | const hasMore = count > offset + contacts.length; 42 | 43 | return { 44 | contacts, 45 | count, 46 | hasMore 47 | }; 48 | }; 49 | 50 | export default ListContactsService; 51 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/ShowContactService.ts: -------------------------------------------------------------------------------- 1 | import Contact from "../../models/Contact"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const ShowContactService = async (id: string | number): Promise => { 5 | const contact = await Contact.findByPk(id, { include: ["extraInfo"] }); 6 | 7 | if (!contact) { 8 | throw new AppError("ERR_NO_CONTACT_FOUND", 404); 9 | } 10 | 11 | return contact; 12 | }; 13 | 14 | export default ShowContactService; 15 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/SimpleListService.ts: -------------------------------------------------------------------------------- 1 | import Contact from "../../models/Contact"; 2 | import AppError from "../../errors/AppError"; 3 | import { FindOptions, Op } from "sequelize"; 4 | 5 | export interface SearchContactParams { 6 | name?: string; 7 | } 8 | 9 | const SimpleListService = async (params: SearchContactParams): Promise => { 10 | let options: FindOptions = { 11 | order: [ 12 | ['name', 'ASC'] 13 | ] 14 | } 15 | const { name } = params 16 | 17 | if (name) { 18 | options.where = { 19 | name: { 20 | [Op.like]: `%${name}%` 21 | } 22 | } 23 | } 24 | 25 | const contacts = await Contact.findAll(options); 26 | 27 | if (!contacts) { 28 | throw new AppError("ERR_NO_CONTACT_FOUND", 404); 29 | } 30 | 31 | return contacts; 32 | }; 33 | 34 | export default SimpleListService; 35 | -------------------------------------------------------------------------------- /backend/src/services/DialogChatBotsServices/CreateDialogChatBotsServices.ts: -------------------------------------------------------------------------------- 1 | import DialogChatBots from "../../models/DialogChatBots"; 2 | 3 | interface Request { 4 | awaiting: number; 5 | contactId: number; 6 | chatbotId: number; 7 | queueId: number; 8 | } 9 | 10 | const CreateDialogChatBotsServices = async ({ 11 | awaiting, 12 | contactId, 13 | chatbotId, 14 | queueId 15 | }: Request): Promise => { 16 | const quickAnswer = await DialogChatBots.create({ 17 | awaiting, 18 | contactId, 19 | chatbotId, 20 | queueId 21 | }); 22 | 23 | return quickAnswer; 24 | }; 25 | 26 | export default CreateDialogChatBotsServices; 27 | -------------------------------------------------------------------------------- /backend/src/services/DialogChatBotsServices/DeleteDialogChatBotsServices.ts: -------------------------------------------------------------------------------- 1 | import DialogChatBots from "../../models/DialogChatBots"; 2 | 3 | const DeleteDialogChatBotsServices = async ( 4 | contactId: number | string 5 | ): Promise => { 6 | const queue = await DialogChatBots.findOne({ 7 | where: { 8 | contactId 9 | } 10 | }); 11 | 12 | if (queue) { 13 | await queue.destroy(); 14 | } 15 | }; 16 | 17 | export default DeleteDialogChatBotsServices; 18 | -------------------------------------------------------------------------------- /backend/src/services/DialogChatBotsServices/ListDialogChatBotsServices.ts: -------------------------------------------------------------------------------- 1 | import { Op } from "sequelize"; 2 | import DialogChatBots from "../../models/DialogChatBots"; 3 | 4 | const ListDialogChatBotsServices = async (): Promise => { 5 | const chatBot = await DialogChatBots.findAll({ 6 | where: { 7 | queueId: { 8 | [Op.or]: [null] 9 | } 10 | }, 11 | order: [["name", "ASC"]] 12 | }); 13 | 14 | return chatBot; 15 | }; 16 | 17 | export default ListDialogChatBotsServices; 18 | -------------------------------------------------------------------------------- /backend/src/services/DialogChatBotsServices/ShowDialogChatBotsServices.ts: -------------------------------------------------------------------------------- 1 | import Chatbot from "../../models/Chatbot"; 2 | import DialogChatBots from "../../models/DialogChatBots"; 3 | 4 | const ShowDialogChatBotsServices = async ( 5 | contactId: number | string 6 | ): Promise => { 7 | const dialog = await DialogChatBots.findOne({ 8 | where: { 9 | contactId 10 | }, 11 | include: [ 12 | { 13 | model: Chatbot, 14 | as: "chatbots", 15 | order: [[{ model: Chatbot, as: "chatbots" }, "id", "ASC"]] 16 | } 17 | ] 18 | }); 19 | 20 | return dialog; 21 | }; 22 | 23 | export default ShowDialogChatBotsServices; 24 | -------------------------------------------------------------------------------- /backend/src/services/DialogChatBotsServices/UpdateDialogChatBotsServices.ts: -------------------------------------------------------------------------------- 1 | import DialogChatBots from "../../models/DialogChatBots"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | interface QuickAnswerData { 5 | awaiting?: number; 6 | contactId?: number; 7 | chatbotId?: number; 8 | } 9 | 10 | interface Request { 11 | quickAnswerData: QuickAnswerData; 12 | quickAnswerId: string; 13 | } 14 | 15 | const UpdateDialogChatBotsServices = async ({ 16 | quickAnswerData, 17 | quickAnswerId 18 | }: Request): Promise => { 19 | const { awaiting, contactId, chatbotId } = quickAnswerData; 20 | 21 | const quickAnswer = await DialogChatBots.findOne({ 22 | where: { id: quickAnswerId }, 23 | attributes: ["id", "awaitingt", "contactId", "chatbotId"] 24 | }); 25 | 26 | if (!quickAnswer) { 27 | throw new AppError("ERR_NO_DIALOG_CHATBOT_FOUND", 404); 28 | } 29 | await quickAnswer.update({ 30 | awaiting, 31 | contactId, 32 | chatbotId 33 | }); 34 | 35 | await quickAnswer.reload({ 36 | attributes: ["id", "awaitingt", "contactId", "chatbotId"] 37 | }); 38 | 39 | return quickAnswer; 40 | }; 41 | 42 | export default UpdateDialogChatBotsServices; 43 | -------------------------------------------------------------------------------- /backend/src/services/FacebookServices/sendFacebookMessage.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Message from "../../models/Message"; 3 | import Ticket from "../../models/Ticket"; 4 | import { sendText } from "./graphAPI"; 5 | import formatBody from "../../helpers/Mustache"; 6 | 7 | interface Request { 8 | body: string; 9 | ticket: Ticket; 10 | quotedMsg?: Message; 11 | } 12 | 13 | const SendWhatsAppMessage = async ({ body, ticket }: Request): Promise => { 14 | const { number } = ticket.contact; 15 | 16 | try { 17 | await ticket.update({ lastMessage: body }); 18 | sendText(number, formatBody(body, ticket.contact)); 19 | } catch (err) { 20 | console.log(err); 21 | throw new AppError("ERR_SENDING_FACEBOOK_MSG"); 22 | } 23 | }; 24 | 25 | export default SendWhatsAppMessage; 26 | -------------------------------------------------------------------------------- /backend/src/services/MassMessage/CleanMassMessageervices.ts: -------------------------------------------------------------------------------- 1 | import MassMessages from "../../models/MassMessages"; 2 | 3 | const CleanMassMessageService = async (): Promise => { 4 | await MassMessages.truncate(); 5 | }; 6 | 7 | export default CleanMassMessageService; 8 | -------------------------------------------------------------------------------- /backend/src/services/MassMessage/CreateMassMessageService.ts: -------------------------------------------------------------------------------- 1 | import MassMessages from "../../models/MassMessages"; 2 | 3 | interface ChatbotData { 4 | message: string; 5 | phone: string; 6 | whatsappId?: string; 7 | } 8 | 9 | const CreateChatBotServices = async ( 10 | chatBotData: ChatbotData 11 | ): Promise => { 12 | const chatBot = await MassMessages.create(chatBotData); 13 | 14 | return chatBot; 15 | }; 16 | 17 | export default CreateChatBotServices; 18 | -------------------------------------------------------------------------------- /backend/src/services/MassMessage/DeleteMassMessageService.ts: -------------------------------------------------------------------------------- 1 | import MassMessages from "../../models/MassMessages"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteMassMessageService = async (id: string): Promise => { 5 | const massMessage = await MassMessages.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!massMessage) { 10 | throw new AppError("ERR_NO_MASS_MESSAGE_FOUND", 404); 11 | } 12 | 13 | await massMessage.destroy(); 14 | }; 15 | 16 | export default DeleteMassMessageService; 17 | -------------------------------------------------------------------------------- /backend/src/services/MassMessage/ListMassMessageService.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from "sequelize"; 2 | import MassMessages from "../../models/MassMessages"; 3 | import Whatsapp from "../../models/Whatsapp"; 4 | 5 | interface Request { 6 | searchParam?: string; 7 | pageNumber?: string; 8 | } 9 | 10 | interface Response { 11 | messages: MassMessages[]; 12 | count: number; 13 | hasMore: boolean; 14 | } 15 | 16 | const ListMassMessageService = async ({ 17 | searchParam = "", 18 | pageNumber = "1" 19 | }: Request): Promise => { 20 | const whereCondition = { 21 | phone: Sequelize.where( 22 | Sequelize.fn("LOWER", Sequelize.col("phone")), 23 | "LIKE", 24 | `%${searchParam.toLowerCase().trim()}%` 25 | ) 26 | }; 27 | const limit = 20; 28 | const offset = limit * (+pageNumber - 1); 29 | 30 | const { count, rows: messages } = await MassMessages.findAndCountAll({ 31 | where: whereCondition, 32 | limit, 33 | offset, 34 | order: [["message", "ASC"]], 35 | include: [ 36 | { 37 | model: Whatsapp, 38 | as: "whatsapp", 39 | attributes: ["name"] 40 | } 41 | ] 42 | }); 43 | 44 | const hasMore = count > offset + messages.length; 45 | 46 | return { 47 | messages, 48 | count, 49 | hasMore 50 | }; 51 | }; 52 | 53 | export default ListMassMessageService; 54 | -------------------------------------------------------------------------------- /backend/src/services/MessageServices/GetMessagesService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Message from "../../models/Message"; 3 | 4 | interface Request { 5 | id: string; 6 | } 7 | 8 | const GetMessageService = async ({ id }: Request): Promise => { 9 | const messageExists = await Message.findOne({ 10 | where: { id } 11 | }); 12 | 13 | if (!messageExists) { 14 | throw new AppError("MESSAGE_NOT_FIND"); 15 | } 16 | 17 | return messageExists; 18 | }; 19 | 20 | export default GetMessageService; 21 | -------------------------------------------------------------------------------- /backend/src/services/QueueService/DeleteQueueService.ts: -------------------------------------------------------------------------------- 1 | import ShowQueueService from "./ShowQueueService"; 2 | 3 | const DeleteQueueService = async (queueId: number | string): Promise => { 4 | const queue = await ShowQueueService(queueId); 5 | 6 | await queue.destroy(); 7 | }; 8 | 9 | export default DeleteQueueService; 10 | -------------------------------------------------------------------------------- /backend/src/services/QueueService/ListQueuesService.ts: -------------------------------------------------------------------------------- 1 | import Queue from "../../models/Queue"; 2 | 3 | const ListQueuesService = async (): Promise => { 4 | const queues = await Queue.findAll({ 5 | order: [["id", "ASC"]], 6 | }); 7 | 8 | return queues; 9 | }; 10 | 11 | export default ListQueuesService; 12 | -------------------------------------------------------------------------------- /backend/src/services/QueueService/ShowQueueService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Chatbot from "../../models/Chatbot"; 3 | import Queue from "../../models/Queue"; 4 | 5 | const ShowQueueService = async (queueId: number | string): Promise => { 6 | const queue = await Queue.findByPk(queueId, { 7 | include: ["chatbots"], 8 | order: [ 9 | [{ model: Chatbot, as: "chatbots" }, "id", "asc"], 10 | ["id", "ASC"] 11 | ] 12 | }); 13 | 14 | if (!queue) { 15 | throw new AppError("ERR_QUEUE_NOT_FOUND"); 16 | } 17 | 18 | return queue; 19 | }; 20 | 21 | export default ShowQueueService; 22 | -------------------------------------------------------------------------------- /backend/src/services/QuickAnswerService/CreateQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import QuickAnswer from "../../models/QuickAnswer"; 3 | 4 | interface Request { 5 | shortcut: string; 6 | message: string; 7 | } 8 | 9 | const CreateQuickAnswerService = async ({ 10 | shortcut, 11 | message 12 | }: Request): Promise => { 13 | const nameExists = await QuickAnswer.findOne({ 14 | where: { shortcut } 15 | }); 16 | 17 | if (nameExists) { 18 | throw new AppError("ERR__SHORTCUT_DUPLICATED"); 19 | } 20 | 21 | const quickAnswer = await QuickAnswer.create({ shortcut, message }); 22 | 23 | return quickAnswer; 24 | }; 25 | 26 | export default CreateQuickAnswerService; 27 | -------------------------------------------------------------------------------- /backend/src/services/QuickAnswerService/DeleteQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import QuickAnswer from "../../models/QuickAnswer"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteQuickAnswerService = async (id: string): Promise => { 5 | const quickAnswer = await QuickAnswer.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!quickAnswer) { 10 | throw new AppError("ERR_NO_QUICK_ANSWER_FOUND", 404); 11 | } 12 | 13 | await quickAnswer.destroy(); 14 | }; 15 | 16 | export default DeleteQuickAnswerService; 17 | -------------------------------------------------------------------------------- /backend/src/services/QuickAnswerService/ListQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from "sequelize"; 2 | import QuickAnswer from "../../models/QuickAnswer"; 3 | 4 | interface Request { 5 | searchParam?: string; 6 | pageNumber?: string; 7 | } 8 | 9 | interface Response { 10 | quickAnswers: QuickAnswer[]; 11 | count: number; 12 | hasMore: boolean; 13 | } 14 | 15 | const ListQuickAnswerService = async ({ 16 | searchParam = "", 17 | pageNumber = "1" 18 | }: Request): Promise => { 19 | const whereCondition = { 20 | message: Sequelize.where( 21 | Sequelize.fn("LOWER", Sequelize.col("message")), 22 | "LIKE", 23 | `%${searchParam.toLowerCase().trim()}%` 24 | ) 25 | }; 26 | const limit = 20; 27 | const offset = limit * (+pageNumber - 1); 28 | 29 | const { count, rows: quickAnswers } = await QuickAnswer.findAndCountAll({ 30 | where: whereCondition, 31 | limit, 32 | offset, 33 | order: [["message", "ASC"]] 34 | }); 35 | 36 | const hasMore = count > offset + quickAnswers.length; 37 | 38 | return { 39 | quickAnswers, 40 | count, 41 | hasMore 42 | }; 43 | }; 44 | 45 | export default ListQuickAnswerService; 46 | -------------------------------------------------------------------------------- /backend/src/services/QuickAnswerService/ShowQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import QuickAnswer from "../../models/QuickAnswer"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const ShowQuickAnswerService = async (id: string): Promise => { 5 | const quickAnswer = await QuickAnswer.findByPk(id); 6 | 7 | if (!quickAnswer) { 8 | throw new AppError("ERR_NO_QUICK_ANSWERS_FOUND", 404); 9 | } 10 | 11 | return quickAnswer; 12 | }; 13 | 14 | export default ShowQuickAnswerService; 15 | -------------------------------------------------------------------------------- /backend/src/services/QuickAnswerService/UpdateQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import QuickAnswer from "../../models/QuickAnswer"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | interface QuickAnswerData { 5 | shortcut?: string; 6 | message?: string; 7 | } 8 | 9 | interface Request { 10 | quickAnswerData: QuickAnswerData; 11 | quickAnswerId: string; 12 | } 13 | 14 | const UpdateQuickAnswerService = async ({ 15 | quickAnswerData, 16 | quickAnswerId 17 | }: Request): Promise => { 18 | const { shortcut, message } = quickAnswerData; 19 | 20 | const quickAnswer = await QuickAnswer.findOne({ 21 | where: { id: quickAnswerId }, 22 | attributes: ["id", "shortcut", "message"] 23 | }); 24 | 25 | if (!quickAnswer) { 26 | throw new AppError("ERR_NO_QUICK_ANSWERS_FOUND", 404); 27 | } 28 | await quickAnswer.update({ 29 | shortcut, 30 | message 31 | }); 32 | 33 | await quickAnswer.reload({ 34 | attributes: ["id", "shortcut", "message"] 35 | }); 36 | 37 | return quickAnswer; 38 | }; 39 | 40 | export default UpdateQuickAnswerService; 41 | -------------------------------------------------------------------------------- /backend/src/services/ScheduleServices/CreateService.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | 3 | import AppError from "../../errors/AppError"; 4 | import Schedule from "../../models/Schedule"; 5 | 6 | interface Request { 7 | body: string; 8 | sendAt: string; 9 | contactId: number | string; 10 | userId?: number | string; 11 | } 12 | 13 | const CreateService = async ({ 14 | body, 15 | sendAt, 16 | contactId, 17 | userId 18 | }: Request): Promise => { 19 | const schema = Yup.object().shape({ 20 | body: Yup.string().required().min(5), 21 | sendAt: Yup.string().required() 22 | }); 23 | 24 | try { 25 | await schema.validate({ body, sendAt }); 26 | } catch (err: any) { 27 | throw new AppError(err.message); 28 | } 29 | 30 | const schedule = await Schedule.create( 31 | { 32 | body, 33 | sendAt, 34 | contactId, 35 | userId, 36 | status: 'PENDENTE' 37 | } 38 | ); 39 | 40 | await schedule.reload(); 41 | 42 | return schedule; 43 | }; 44 | 45 | export default CreateService; 46 | -------------------------------------------------------------------------------- /backend/src/services/ScheduleServices/DeleteService.ts: -------------------------------------------------------------------------------- 1 | import Schedule from "../../models/Schedule"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteService = async (id: string | number): Promise => { 5 | const schedule = await Schedule.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!schedule) { 10 | throw new AppError("ERR_NO_SCHEDULE_FOUND", 404); 11 | } 12 | 13 | await schedule.destroy(); 14 | }; 15 | 16 | export default DeleteService; 17 | -------------------------------------------------------------------------------- /backend/src/services/ScheduleServices/ShowService.ts: -------------------------------------------------------------------------------- 1 | import Schedule from "../../models/Schedule"; 2 | import AppError from "../../errors/AppError"; 3 | import Contact from "../../models/Contact"; 4 | import User from "../../models/User"; 5 | 6 | const ScheduleService = async (id: string | number): Promise => { 7 | const schedule = await Schedule.findByPk(id, { 8 | include: [ 9 | { model: Contact, as: "contact", attributes: ["id", "name"] }, 10 | { model: User, as: "user", attributes: ["id", "name"] }, 11 | ] 12 | }); 13 | 14 | if (!schedule) { 15 | throw new AppError("ERR_NO_SCHEDULE_FOUND", 404); 16 | } 17 | 18 | return schedule; 19 | }; 20 | 21 | export default ScheduleService; 22 | -------------------------------------------------------------------------------- /backend/src/services/ScheduleServices/UpdateService.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | 3 | import AppError from "../../errors/AppError"; 4 | import Schedule from "../../models/Schedule"; 5 | import ShowService from "./ShowService"; 6 | 7 | interface ScheduleData { 8 | id?: number; 9 | body?: string; 10 | sendAt?: string; 11 | sentAt?: string; 12 | contactId?: number; 13 | ticketId?: number; 14 | userId?: number; 15 | } 16 | 17 | interface Request { 18 | scheduleData: ScheduleData; 19 | id: string | number; 20 | } 21 | 22 | const UpdateUserService = async ({ 23 | scheduleData, 24 | id 25 | }: Request): Promise => { 26 | const schedule = await ShowService(id); 27 | 28 | const schema = Yup.object().shape({ 29 | body: Yup.string().min(5) 30 | }); 31 | 32 | const { 33 | body, 34 | sendAt, 35 | sentAt, 36 | contactId, 37 | ticketId, 38 | userId, 39 | } = scheduleData; 40 | 41 | try { 42 | await schema.validate({ body }); 43 | } catch (err: any) { 44 | throw new AppError(err.message); 45 | } 46 | 47 | await schedule.update({ 48 | body, 49 | sendAt, 50 | sentAt, 51 | contactId, 52 | ticketId, 53 | userId, 54 | }); 55 | 56 | await schedule.reload(); 57 | return schedule; 58 | }; 59 | 60 | export default UpdateUserService; 61 | -------------------------------------------------------------------------------- /backend/src/services/SettingMensageServices/CreateSettingService.ts: -------------------------------------------------------------------------------- 1 | import SettingMessage from "../../models/SettingMessage"; 2 | 3 | interface Request { 4 | contact: boolean; 5 | limit: string | number; 6 | minutes: string | number; 7 | optOut: string; 8 | photo: boolean; 9 | random: boolean; 10 | seconds: string | number; 11 | whatsappId: string | number; 12 | } 13 | 14 | const CreateSettingService = async ( 15 | settingsData: Request 16 | ): Promise => { 17 | const checkExist = await SettingMessage.findOne({ 18 | where: { 19 | whatsappId: settingsData.whatsappId 20 | } 21 | }); 22 | 23 | if (checkExist) { 24 | await SettingMessage.update(settingsData, { 25 | where: { 26 | whatsappId: settingsData.whatsappId 27 | } 28 | }); 29 | 30 | const find = await SettingMessage.findOne({ 31 | where: { 32 | whatsappId: settingsData.whatsappId 33 | } 34 | }); 35 | 36 | return find; 37 | } 38 | const settings = await SettingMessage.create(settingsData); 39 | return settings; 40 | }; 41 | 42 | export default CreateSettingService; 43 | -------------------------------------------------------------------------------- /backend/src/services/SettingMensageServices/ListSettingByValueService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Setting from "../../models/Setting"; 3 | 4 | interface Response { 5 | key: string; 6 | value: string; 7 | } 8 | const ListSettingByKeyService = async ( 9 | value: string 10 | ): Promise => { 11 | const settings = await Setting.findOne({ 12 | where: { value } 13 | }); 14 | 15 | if (!settings) { 16 | throw new AppError("ERR_NO_KEY_FOUND", 404); 17 | } 18 | 19 | return { key: settings.key, value: settings.value }; 20 | }; 21 | 22 | export default ListSettingByKeyService; 23 | -------------------------------------------------------------------------------- /backend/src/services/SettingMensageServices/ListSettingsService.ts: -------------------------------------------------------------------------------- 1 | import Setting from "../../models/Setting"; 2 | 3 | const ListSettingsService = async (): Promise => { 4 | const settings = await Setting.findAll(); 5 | 6 | return settings; 7 | }; 8 | 9 | export default ListSettingsService; 10 | -------------------------------------------------------------------------------- /backend/src/services/SettingMensageServices/ShowSettingsService.ts: -------------------------------------------------------------------------------- 1 | import SettingMessage from "../../models/SettingMessage"; 2 | 3 | const ShowSettingsService = async ( 4 | whatsappId: number | string 5 | ): Promise => { 6 | const queue = await SettingMessage.findOne({ 7 | where: { 8 | whatsappId 9 | } 10 | }); 11 | 12 | if (queue) { 13 | return queue; 14 | } 15 | }; 16 | 17 | export default ShowSettingsService; 18 | -------------------------------------------------------------------------------- /backend/src/services/SettingMensageServices/UpdateSettingService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import SettingMessage from "../../models/SettingMessage"; 3 | 4 | interface Request { 5 | key: string; 6 | value: string; 7 | } 8 | 9 | const UpdateSettingService = async ({ 10 | key, 11 | value 12 | }: Request): Promise => { 13 | const setting = await SettingMessage.findOne({ 14 | where: { key } 15 | }); 16 | 17 | if (!setting) { 18 | throw new AppError("ERR_NO_SETTING_FOUND", 404); 19 | } 20 | 21 | await setting.update({ value }); 22 | 23 | return setting; 24 | }; 25 | 26 | export default UpdateSettingService; 27 | -------------------------------------------------------------------------------- /backend/src/services/SettingServices/ListSettingByValueService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Setting from "../../models/Setting"; 3 | 4 | interface Response { 5 | key: string; 6 | value: string; 7 | } 8 | const ListSettingByKeyService = async ( 9 | value: string 10 | ): Promise => { 11 | const settings = await Setting.findOne({ 12 | where: { value } 13 | }); 14 | 15 | if (!settings) { 16 | throw new AppError("ERR_NO_KEY_FOUND", 404); 17 | } 18 | 19 | return { key: settings.key, value: settings.value }; 20 | }; 21 | 22 | export default ListSettingByKeyService; 23 | -------------------------------------------------------------------------------- /backend/src/services/SettingServices/ListSettingsService.ts: -------------------------------------------------------------------------------- 1 | import Setting from "../../models/Setting"; 2 | 3 | const ListSettingsService = async (): Promise => { 4 | const settings = await Setting.findAll(); 5 | 6 | return settings; 7 | }; 8 | 9 | export default ListSettingsService; 10 | -------------------------------------------------------------------------------- /backend/src/services/SettingServices/UpdateSettingService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Setting from "../../models/Setting"; 3 | 4 | interface Request { 5 | key: string; 6 | value: string; 7 | } 8 | 9 | const UpdateSettingService = async ({ 10 | key, 11 | value 12 | }: Request): Promise => { 13 | const setting = await Setting.findOne({ 14 | where: { key } 15 | }); 16 | 17 | if (!setting) { 18 | throw new AppError("ERR_NO_SETTING_FOUND", 404); 19 | } 20 | 21 | await setting.update({ value }); 22 | 23 | return setting; 24 | }; 25 | 26 | export default UpdateSettingService; 27 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/CreateService.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | 3 | import AppError from "../../errors/AppError"; 4 | import Tag from "../../models/Tag"; 5 | 6 | interface Request { 7 | name: string; 8 | color: string; 9 | } 10 | 11 | const CreateService = async ({ 12 | name, 13 | color = '#eee' 14 | }: Request): Promise => { 15 | const schema = Yup.object().shape({ 16 | name: Yup.string().required().min(3) 17 | .test( 18 | "Check-unique-name", 19 | "ERR_QUEUE_NAME_ALREADY_EXISTS", 20 | async value => { 21 | if (value) { 22 | const tagWithSameName = await Tag.findOne({ 23 | where: { name: value } 24 | }); 25 | 26 | return !tagWithSameName; 27 | } 28 | return false; 29 | } 30 | ), 31 | }); 32 | 33 | try { 34 | await schema.validate({ name }); 35 | } catch (err: any) { 36 | throw new AppError(err.message); 37 | } 38 | 39 | const [tag,] = await Tag.findOrCreate({ 40 | where: { name, color }, 41 | defaults: { name, color } 42 | }); 43 | 44 | await tag.reload(); 45 | 46 | return tag; 47 | }; 48 | 49 | export default CreateService; 50 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/DeleteService.ts: -------------------------------------------------------------------------------- 1 | import Tag from "../../models/Tag"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteService = async (id: string | number): Promise => { 5 | const tag = await Tag.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!tag) { 10 | throw new AppError("ERR_NO_TAG_FOUND", 404); 11 | } 12 | 13 | await tag.destroy(); 14 | }; 15 | 16 | export default DeleteService; 17 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/ShowService.ts: -------------------------------------------------------------------------------- 1 | import Tag from "../../models/Tag"; 2 | import AppError from "../../errors/AppError"; 3 | import { Sequelize } from "sequelize"; 4 | import Ticket from "../../models/Ticket"; 5 | 6 | const TagService = async (id: string | number): Promise => { 7 | const tag = await Tag.findByPk(id, { 8 | attributes: { 9 | include: [[Sequelize.fn("COUNT", Sequelize.col("tickets.id")), "ticketsCount"]] 10 | }, 11 | group: [ 12 | "Tag.id", 13 | "tickets.TicketTag.tagId", 14 | "tickets.TicketTag.ticketId", 15 | "tickets.TicketTag.createdAt", 16 | "tickets.TicketTag.updatedAt", 17 | ], 18 | include: [ 19 | { 20 | model: Ticket, as: "tickets", attributes: [] 21 | }, 22 | ] 23 | }); 24 | 25 | if (!tag) { 26 | throw new AppError("ERR_NO_TAG_FOUND", 404); 27 | } 28 | 29 | return tag; 30 | }; 31 | 32 | export default TagService; 33 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/SimpleListService.ts: -------------------------------------------------------------------------------- 1 | import { Op, Sequelize } from "sequelize"; 2 | import Tag from "../../models/Tag"; 3 | import Ticket from "../../models/Ticket"; 4 | 5 | interface Request { 6 | searchParam?: string; 7 | } 8 | 9 | const ListService = async ({ 10 | searchParam 11 | }: Request): Promise => { 12 | let whereCondition = {}; 13 | 14 | if (searchParam) { 15 | whereCondition = { 16 | [Op.or]: [ 17 | { name: {[Op.like]: `%${searchParam}%`} }, 18 | { color: {[Op.like]: `%${searchParam}%`} } 19 | ] 20 | } 21 | } 22 | 23 | const tags = await Tag.findAll({ 24 | where: whereCondition, 25 | order: [["name", "ASC"]], 26 | include: [{ 27 | model: Ticket, 28 | as: 'tickets', 29 | }], 30 | attributes: { 31 | exclude: ['createdAt', 'updatedAt'], 32 | include: [[Sequelize.fn("COUNT", Sequelize.col("tickets.id")), "ticketsCount"]] 33 | }, 34 | group: [ 35 | "Tag.id", 36 | "tickets.TicketTag.tagId", 37 | "tickets.TicketTag.ticketId", 38 | "tickets.TicketTag.createdAt", 39 | "tickets.TicketTag.updatedAt", 40 | "tickets.id" 41 | ] 42 | }); 43 | 44 | return tags; 45 | }; 46 | 47 | export default ListService; 48 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/SyncTagsService.ts: -------------------------------------------------------------------------------- 1 | import Tag from "../../models/Tag"; 2 | import Ticket from "../../models/Ticket"; 3 | import TicketTag from "../../models/TicketTag"; 4 | 5 | interface Request { 6 | tags: Tag[]; 7 | ticketId: number; 8 | } 9 | 10 | const SyncTags = async ({ 11 | tags, 12 | ticketId 13 | }: Request): Promise => { 14 | const ticket = await Ticket.findByPk(ticketId, { include: [Tag] }); 15 | 16 | const tagList = tags.map((t) => ({ tagId: t.id, ticketId })); 17 | 18 | await TicketTag.destroy({ where: { ticketId } }); 19 | await TicketTag.bulkCreate(tagList); 20 | 21 | ticket?.reload(); 22 | 23 | return ticket; 24 | }; 25 | 26 | export default SyncTags; 27 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/UpdateService.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | 3 | import AppError from "../../errors/AppError"; 4 | import Tag from "../../models/Tag"; 5 | import ShowService from "./ShowService"; 6 | 7 | interface TagData { 8 | id?: number; 9 | name?: string; 10 | color?: string; 11 | } 12 | 13 | interface Request { 14 | tagData: TagData; 15 | id: string | number; 16 | } 17 | 18 | const UpdateUserService = async ({ 19 | tagData, 20 | id 21 | }: Request): Promise => { 22 | const tag = await ShowService(id); 23 | 24 | const schema = Yup.object().shape({ 25 | name: Yup.string().min(3) 26 | }); 27 | 28 | const { 29 | name, 30 | color 31 | } = tagData; 32 | 33 | try { 34 | await schema.validate({ name }); 35 | } catch (err: any) { 36 | throw new AppError(err.message); 37 | } 38 | 39 | await tag.update({ 40 | name, 41 | color, 42 | }); 43 | 44 | await tag.reload(); 45 | return tag; 46 | }; 47 | 48 | export default UpdateUserService; 49 | -------------------------------------------------------------------------------- /backend/src/services/TicketServices/DeleteTicketService.ts: -------------------------------------------------------------------------------- 1 | import Ticket from "../../models/Ticket"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteTicketService = async (id: string): Promise => { 5 | const ticket = await Ticket.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!ticket) { 10 | throw new AppError("ERR_NO_TICKET_FOUND", 404); 11 | } 12 | 13 | await ticket.destroy(); 14 | 15 | return ticket; 16 | }; 17 | 18 | export default DeleteTicketService; 19 | -------------------------------------------------------------------------------- /backend/src/services/TicketServices/ShowTicketService.ts: -------------------------------------------------------------------------------- 1 | import Ticket from "../../models/Ticket"; 2 | import AppError from "../../errors/AppError"; 3 | import Contact from "../../models/Contact"; 4 | import User from "../../models/User"; 5 | import Queue from "../../models/Queue"; 6 | import Whatsapp from "../../models/Whatsapp"; 7 | import Tag from "../../models/Tag"; 8 | 9 | const ShowTicketService = async (id: string | number): Promise => { 10 | const ticket = await Ticket.findByPk(id, { 11 | include: [ 12 | { 13 | model: Contact, 14 | as: "contact", 15 | attributes: ["id", "name", "number", "profilePicUrl"], 16 | include: ["extraInfo"] 17 | }, 18 | { 19 | model: User, 20 | as: "user", 21 | attributes: ["id", "name"] 22 | }, 23 | { 24 | model: Queue, 25 | as: "queue", 26 | attributes: ["id", "name", "color"] 27 | }, 28 | { 29 | model: Whatsapp, 30 | as: "whatsapp", 31 | attributes: ["name"] 32 | }, 33 | { 34 | model: Tag, 35 | as: "tags", 36 | attributes: ["id", "name", "color"] 37 | } 38 | ] 39 | }); 40 | 41 | if (!ticket) { 42 | throw new AppError("ERR_NO_TICKET_FOUND", 404); 43 | } 44 | 45 | return ticket; 46 | }; 47 | 48 | export default ShowTicketService; 49 | -------------------------------------------------------------------------------- /backend/src/services/UserServices/AuthUserService.ts: -------------------------------------------------------------------------------- 1 | import User from "../../models/User"; 2 | import AppError from "../../errors/AppError"; 3 | import { 4 | createAccessToken, 5 | createRefreshToken 6 | } from "../../helpers/CreateTokens"; 7 | import { SerializeUser } from "../../helpers/SerializeUser"; 8 | import Queue from "../../models/Queue"; 9 | 10 | interface SerializedUser { 11 | id: number; 12 | name: string; 13 | email: string; 14 | profile: string; 15 | queues: Queue[]; 16 | } 17 | 18 | interface Request { 19 | email: string; 20 | password: string; 21 | } 22 | 23 | interface Response { 24 | serializedUser: SerializedUser; 25 | token: string; 26 | refreshToken: string; 27 | } 28 | 29 | const AuthUserService = async ({ 30 | email, 31 | password 32 | }: Request): Promise => { 33 | const user = await User.findOne({ 34 | where: { email }, 35 | include: ["queues"] 36 | }); 37 | 38 | if (!user) { 39 | throw new AppError("ERR_INVALID_CREDENTIALS", 401); 40 | } 41 | 42 | if (!(await user.checkPassword(password))) { 43 | throw new AppError("ERR_INVALID_CREDENTIALS", 401); 44 | } 45 | 46 | const token = createAccessToken(user); 47 | const refreshToken = createRefreshToken(user); 48 | 49 | const serializedUser = SerializeUser(user); 50 | 51 | return { 52 | serializedUser, 53 | token, 54 | refreshToken 55 | }; 56 | }; 57 | 58 | export default AuthUserService; 59 | -------------------------------------------------------------------------------- /backend/src/services/UserServices/DeleteUserService.ts: -------------------------------------------------------------------------------- 1 | import User from "../../models/User"; 2 | import AppError from "../../errors/AppError"; 3 | import Ticket from "../../models/Ticket"; 4 | import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; 5 | 6 | const DeleteUserService = async (id: string | number): Promise => { 7 | const user = await User.findOne({ 8 | where: { id } 9 | }); 10 | 11 | if (!user) { 12 | throw new AppError("ERR_NO_USER_FOUND", 404); 13 | } 14 | 15 | const userOpenTickets: Ticket[] = await user.$get("tickets", { 16 | where: { status: "open" } 17 | }); 18 | 19 | if (userOpenTickets.length > 0) { 20 | UpdateDeletedUserOpenTicketsStatus(userOpenTickets); 21 | } 22 | 23 | await user.destroy(); 24 | }; 25 | 26 | export default DeleteUserService; 27 | -------------------------------------------------------------------------------- /backend/src/services/UserServices/ShowUserService.ts: -------------------------------------------------------------------------------- 1 | import User from "../../models/User"; 2 | import AppError from "../../errors/AppError"; 3 | import Queue from "../../models/Queue"; 4 | import Whatsapp from "../../models/Whatsapp"; 5 | 6 | const ShowUserService = async (id: string | number): Promise => { 7 | const user = await User.findByPk(id, { 8 | attributes: ["name", "id", "email", "profile", "tokenVersion", "whatsappId"], 9 | include: [ 10 | { model: Queue, as: "queues", attributes: ["id", "name", "color"] }, 11 | { model: Whatsapp, as: "whatsapp", attributes: ["id", "name"] }, 12 | ], 13 | order: [ [ { model: Queue, as: "queues"}, 'name', 'asc' ] ] 14 | }); 15 | if (!user) { 16 | throw new AppError("ERR_NO_USER_FOUND", 404); 17 | } 18 | 19 | return user; 20 | }; 21 | 22 | export default ShowUserService; 23 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/CheckIsValidContact.ts: -------------------------------------------------------------------------------- 1 | import { WASocket } from "@whiskeysockets/baileys"; 2 | import AppError from "../../errors/AppError"; 3 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 4 | import { getWbot } from "../../libs/wbot"; 5 | 6 | const CheckIsValidContact = async (number: string): Promise => { 7 | const defaultWhatsapp = await GetDefaultWhatsApp(); 8 | 9 | const wbot = getWbot(defaultWhatsapp.id); 10 | 11 | try { 12 | const [result] = await (wbot as WASocket).onWhatsApp( 13 | `${number}@s.whatsapp.net` 14 | ); 15 | 16 | if (!result.exists) { 17 | throw new AppError("invalidNumber"); 18 | } 19 | } catch (err) { 20 | if (err.message === "invalidNumber") { 21 | throw new AppError("ERR_WAPP_INVALID_CONTACT"); 22 | } 23 | throw new AppError("ERR_WAPP_CHECK_CONTACT"); 24 | } 25 | }; 26 | 27 | export default CheckIsValidContact; 28 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/CheckNumber.ts: -------------------------------------------------------------------------------- 1 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 2 | import { getWbot } from "../../libs/wbot"; 3 | 4 | const CheckContactNumber = async (number: string): Promise => { 5 | const defaultWhatsapp = await GetDefaultWhatsApp(); 6 | 7 | const wbot = getWbot(defaultWhatsapp.id); 8 | const isGroup = number.endsWith("@g.us"); 9 | let numberArray; 10 | if (isGroup) { 11 | const grupoMeta = await wbot.groupMetadata(number); 12 | numberArray = [ 13 | { 14 | jid: grupoMeta.id, 15 | exists: true 16 | } 17 | ]; 18 | } else { 19 | numberArray = await wbot.onWhatsApp(`${number}@s.whatsapp.net`); 20 | } 21 | 22 | const isNumberExit = numberArray; 23 | 24 | if (!isNumberExit[0]?.exists) { 25 | throw new Error("ERR_CHECK_NUMBER"); 26 | } 27 | 28 | return isGroup 29 | ? number.split("@")[0] 30 | : isNumberExit[0].jid.replace(/[^\d]/g, ""); 31 | }; 32 | 33 | export default CheckContactNumber; 34 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/GetProfilePicUrl.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 3 | import { getWbot } from "../../libs/wbot"; 4 | 5 | const GetProfilePicUrl = async (number: string): Promise => { 6 | const defaultWhatsapp = await GetDefaultWhatsApp(); 7 | const wbot = getWbot(defaultWhatsapp.id); 8 | let profilePicUrl: string 9 | 10 | try { 11 | profilePicUrl = await wbot.profilePictureUrl(`${number}@s.whatsapp.net`); 12 | } catch (err) { 13 | profilePicUrl = `${process.env.FRONTEND_URL}/nopicture.png`; 14 | } 15 | 16 | return profilePicUrl; 17 | }; 18 | 19 | export default GetProfilePicUrl; 20 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts: -------------------------------------------------------------------------------- 1 | import { startQueueProcess } from "../../queues"; 2 | import { runSendMessage } from "../../sendWork"; 3 | import ListWhatsAppsService from "../WhatsappService/ListWhatsAppsService"; 4 | import { StartWhatsAppSession } from "./StartWhatsAppSession"; 5 | 6 | export const StartAllWhatsAppsSessions = async (): Promise => { 7 | const whatsapps = await ListWhatsAppsService(); 8 | if (whatsapps.length > 0) { 9 | whatsapps.forEach(whatsapp => { 10 | StartWhatsAppSession(whatsapp); 11 | }); 12 | } 13 | 14 | setTimeout(() => { 15 | startQueueProcess(); 16 | }, 5000); 17 | 18 | setTimeout(() => { 19 | runSendMessage(); 20 | }, 30000); 21 | }; 22 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/StartWhatsAppSession.ts: -------------------------------------------------------------------------------- 1 | import { getIO } from "../../libs/socket"; 2 | import { initWbot } from "../../libs/wbot"; 3 | import Whatsapp from "../../models/Whatsapp"; 4 | import { logger } from "../../utils/logger"; 5 | import { wbotMessageListener } from "./wbotMessageListener"; 6 | import wbotMonitor from "./wbotMonitor"; 7 | 8 | export const StartWhatsAppSession = async ( 9 | whatsapp: Whatsapp 10 | ): Promise => { 11 | await whatsapp.update({ status: "OPENING" }); 12 | 13 | const io = getIO(); 14 | io.emit("whatsappSession", { 15 | action: "update", 16 | session: whatsapp 17 | }); 18 | 19 | try { 20 | const wbot = await initWbot(whatsapp); 21 | wbotMessageListener(wbot); 22 | wbotMonitor(wbot, whatsapp); 23 | } catch (err) { 24 | logger.error(err); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /backend/src/services/WhatsappService/AssociateWhatsappQueue.ts: -------------------------------------------------------------------------------- 1 | import Whatsapp from "../../models/Whatsapp"; 2 | 3 | const AssociateWhatsappQueue = async ( 4 | whatsapp: Whatsapp, 5 | queueIds: number[] 6 | ): Promise => { 7 | await whatsapp.$set("queues", queueIds); 8 | 9 | await whatsapp.reload(); 10 | }; 11 | 12 | export default AssociateWhatsappQueue; 13 | -------------------------------------------------------------------------------- /backend/src/services/WhatsappService/DeleteWhatsAppService.ts: -------------------------------------------------------------------------------- 1 | import Whatsapp from "../../models/Whatsapp"; 2 | import AppError from "../../errors/AppError"; 3 | import DeleteBaileysService from "../BaileysServices/DeleteBaileysService"; 4 | 5 | const DeleteWhatsAppService = async (id: string): Promise => { 6 | const whatsapp = await Whatsapp.findOne({ 7 | where: { id } 8 | }); 9 | 10 | if (!whatsapp) { 11 | throw new AppError("ERR_NO_WAPP_FOUND", 404); 12 | } 13 | 14 | await whatsapp.destroy(); 15 | await DeleteBaileysService(id); 16 | }; 17 | 18 | export default DeleteWhatsAppService; 19 | -------------------------------------------------------------------------------- /backend/src/services/WhatsappService/ListWhatsAppsService.ts: -------------------------------------------------------------------------------- 1 | import Queue from "../../models/Queue"; 2 | import Whatsapp from "../../models/Whatsapp"; 3 | 4 | const ListWhatsAppsService = async (): Promise => { 5 | const whatsapps = await Whatsapp.findAll({ 6 | include: [ 7 | { 8 | model: Queue, 9 | as: "queues", 10 | attributes: ["id", "name", "color", "greetingMessage"] 11 | } 12 | ] 13 | }); 14 | 15 | return whatsapps; 16 | }; 17 | 18 | export default ListWhatsAppsService; 19 | -------------------------------------------------------------------------------- /backend/src/services/WhatsappService/ShowWhatsAppService.ts: -------------------------------------------------------------------------------- 1 | import Whatsapp from "../../models/Whatsapp"; 2 | import AppError from "../../errors/AppError"; 3 | import Queue from "../../models/Queue"; 4 | import Chatbot from "../../models/Chatbot"; 5 | 6 | const ShowWhatsAppService = async (id: string | number): Promise => { 7 | const whatsapp = await Whatsapp.findByPk(id, { 8 | include: [ 9 | { 10 | model: Queue, 11 | as: "queues", 12 | attributes: ["id", "name", "color", "greetingMessage"], 13 | include: [ 14 | { 15 | model: Chatbot, 16 | as: "chatbots", 17 | attributes: ["id", "name", "greetingMessage"] 18 | } 19 | ] 20 | } 21 | ], 22 | order: [ 23 | ["queues", "id", "ASC"], 24 | ["queues", "chatbots", "id", "ASC"] 25 | ] 26 | }); 27 | 28 | if (!whatsapp) { 29 | throw new AppError("ERR_NO_WAPP_FOUND", 404); 30 | } 31 | 32 | return whatsapp; 33 | }; 34 | 35 | export default ShowWhatsAppService; 36 | -------------------------------------------------------------------------------- /backend/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | const logger = pino({ 4 | prettyPrint: { 5 | ignore: "pid,hostname" 6 | } 7 | }); 8 | 9 | export { logger }; 10 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "strict": false, 7 | "strictPropertyInitialization": false, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.phpmyadmin.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | networks: 4 | whaticket: 5 | 6 | services: 7 | 8 | phpmyadmin: 9 | image: phpmyadmin/phpmyadmin:latest 10 | environment: 11 | - PMA_HOSTS=mysql 12 | ports: 13 | - ${PMA_PORT:-9000}:80 14 | networks: 15 | - whaticket -------------------------------------------------------------------------------- /frontend/.docker/add-env-vars.sh: -------------------------------------------------------------------------------- 1 | _writeFrontendEnvVars() { 2 | ENV_JSON="$(jq --compact-output --null-input 'env | with_entries(select(.key | startswith("REACT_APP_")))')" 3 | ENV_JSON_ESCAPED="$(printf "%s" "${ENV_JSON}" | sed -e 's/[\&/]/\\&/g')" 4 | sed -i "s/