├── .github └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── UPDATE.sh ├── backend ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .sequelizerc ├── jest.config.js ├── package.json ├── prettier.config.js ├── 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 │ │ ├── ContactController.ts │ │ ├── DashbardController.ts │ │ ├── ImportPhoneContactsController.ts │ │ ├── MessageController.ts │ │ ├── QueueController.ts │ │ ├── QuickAnswerController.ts │ │ ├── SessionController.ts │ │ ├── SettingController.ts │ │ ├── TagController.ts │ │ ├── TicketController.ts │ │ ├── UserController.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-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 │ │ │ ├── 20210109192531-create-TicketTracking-table.ts │ │ │ ├── 20210109192532-add-column-online-to-Users-table.ts │ │ │ ├── 20210109192534-add-rated-to-TicketTraking.ts │ │ │ ├── 20210818102605-create-quickAnswers.ts │ │ │ ├── 20211016014719-add-farewellMessage-to-whatsapp.ts │ │ │ ├── 20220223095932-add-whatsapp-to-user.ts │ │ │ ├── 20220619203200-add-startwork-queues.ts │ │ │ ├── 20220619203500-add-endwork-queues.ts │ │ │ ├── 20220619203900-add-absencemessage-queues.ts │ │ │ ├── 20220906150400-create-tags.ts │ │ │ ├── 20220906150600-create-associate-contacttags.ts │ │ │ ├── 20221007000001-add-ratingMessage-to-Whatsapps.ts │ │ │ ├── 20221012212600-add-startwork-users.ts │ │ │ ├── 20221012212700-add-endwork-users.ts │ │ │ ├── 20221023085500-add-isdisplay-to-whatsapp.ts │ │ │ ├── 20221128234000-add-number-to-whatsapp.ts │ │ │ ├── 20221216000000-create-UserRatings-table.ts │ │ │ ├── 20222016014719-add-useNPS-whatsapp.ts │ │ │ ├── 20230310100337-add-allHistoric-users.ts │ │ │ ├── 20230313060000-add-outOfWorkMessage.ts │ │ │ ├── 20230313070000-add-defineWorkHours.ts │ │ │ ├── 20230313080001-add-monday-whatsapp.ts │ │ │ ├── 20230313080002-add-tuesday-whatsapp.ts │ │ │ ├── 20230313080003-add-wednesday-whatsapp.ts │ │ │ ├── 20230313080004-add-thursday-whatsapp.ts │ │ │ ├── 20230313080005-add-friday-whatsapp.ts │ │ │ ├── 20230313080006-add-saturday-whatsapp.ts │ │ │ ├── 20230313080007-add-sunday-whatsapp.ts │ │ │ ├── 20230313100800-add-StartDefineWorkHoursMonday.ts │ │ │ ├── 20230313101200-add-EndDefineWorkHoursMonday.ts │ │ │ ├── 20230313101300-add-StartDefineWorkHoursMondayLunch.ts │ │ │ ├── 20230313101301-add-EndDefineWorkHoursMondayLunch.ts │ │ │ ├── 20230313102400-add-StartDefineWorkHoursTuesday.ts │ │ │ ├── 20230313102401-add-EndDefineWorkHoursTuesday.ts │ │ │ ├── 20230313102500-add-StartDefineWorkHoursTuesdayLunch.ts │ │ │ ├── 20230313102600-add-EndDefineWorkHoursTuesdayLunch.ts │ │ │ ├── 20230313102800-add-StartDefineWorkHoursWednesday.ts │ │ │ ├── 20230313102900-add-EndDefineWorkHoursWednesday.ts │ │ │ ├── 20230313102901-add-StartDefineWorkHoursWednesdayLunch.ts │ │ │ ├── 20230313103000-add-EndDefineWorkHoursWednesdayLunch.ts │ │ │ ├── 20230313103100-add-StartDefineWorkHoursThursday.ts │ │ │ ├── 20230313103300-add-EndDefineWorkHoursThursday.ts │ │ │ ├── 20230313103301-add-StartDefineWorkHoursThursdayLunch.ts │ │ │ ├── 20230313103400-add-EndDefineWorkHoursThursdayLunch.ts │ │ │ ├── 20230313103500-add-StartDefineWorkHoursFriday.ts │ │ │ ├── 20230313103501-add-EndDefineWorkHoursFriday.ts │ │ │ ├── 20230313103600-add-StartDefineWorkHoursFridayLunch.ts │ │ │ ├── 20230313103601-add-EndDefineWorkHoursFridayLunch.ts │ │ │ ├── 20230313103700-add-StartDefineWorkHoursSaturday.ts │ │ │ ├── 20230313103800-add-EndDefineWorkHoursSaturday.ts │ │ │ ├── 20230313103801-add-StartDefineWorkHoursSaturdayLunch.ts │ │ │ ├── 20230313103900-add-EndDefineWorkHoursSaturdayLunch.ts │ │ │ ├── 20230313104000-add-StartDefineWorkHoursSunday.ts │ │ │ ├── 20230313104100-add-EndDefineWorkHoursSunday.ts │ │ │ ├── 20230313104101-add-StartDefineWorkHoursSundayLunch.ts │ │ │ ├── 20230313104200-add-EndDefineWorkHoursSundayLunch.ts │ │ │ ├── 20230411131007-add-isGroup-whatsapp.ts │ │ │ ├── 20230417203900-add-allTickets-user.ts │ │ │ ├── 20230417203900-add-isRemoveTags-user.ts │ │ │ ├── 20230417203900-add-viewConection-user.ts │ │ │ ├── 20230417203900-add-viewName-user.ts │ │ │ ├── 20230417203900-add-viewSector-user.ts │ │ │ ├── 20230417203900-add-viewTags-user.ts │ │ │ ├── 20230426221007-add-inactiveMessage-whatsapp.ts │ │ │ ├── 20230426221007-add-sendInactiveMessage-whatsapp.ts │ │ │ ├── 20230426221007-add-timeInactiveMessage-whatsapp.ts │ │ │ ├── 20230505221007-add-closedAt-TicketTraking.ts │ │ │ ├── 20230505221007-add-fromMe-Ticket.ts │ │ │ ├── 20230505221007-add-isFinished-Ticket.ts │ │ │ └── 20230505221007-add-isMsgGroup-Ticket.ts │ │ └── seeds │ │ │ ├── 20200904070004-create-default-settings.ts │ │ │ ├── 20200904070004-create-default-users.ts │ │ │ ├── 20200904070006-create-apiToken-settings.ts │ │ │ ├── 20230130004700-create-alltickets-settings.ts │ │ │ ├── 20230326221400-create-ASC-settings.ts │ │ │ └── 20230326221600-create-created-settings.ts │ ├── errors │ │ └── AppError.ts │ ├── helpers │ │ ├── CheckContactOpenTickets.ts │ │ ├── CheckSettings.ts │ │ ├── CreateTokens.ts │ │ ├── Debounce.ts │ │ ├── GetDefaultWhatsApp.ts │ │ ├── GetDefaultWhatsAppByUser.ts │ │ ├── GetTicketWbot.ts │ │ ├── GetWbotMessage.ts │ │ ├── GetWhatsappWbot.ts │ │ ├── Mustache.ts │ │ ├── SendRefreshToken.ts │ │ ├── SerializeUser.ts │ │ ├── SerializeWbotMsgId.ts │ │ ├── SetTicketMessagesAsRead.ts │ │ └── UpdateDeletedUserOpenTicketsStatus.ts │ ├── libs │ │ ├── socket.ts │ │ └── wbot.ts │ ├── middleware │ │ ├── isAuth.ts │ │ └── isAuthApi.ts │ ├── models │ │ ├── Chatbot.ts │ │ ├── Contact.ts │ │ ├── ContactCustomField.ts │ │ ├── ContactTag.ts │ │ ├── Message.ts │ │ ├── Queue.ts │ │ ├── QuickAnswer.ts │ │ ├── Setting.ts │ │ ├── Tag.ts │ │ ├── Ticket.ts │ │ ├── TicketTraking.ts │ │ ├── User.ts │ │ ├── UserQueue.ts │ │ ├── UserRating.ts │ │ ├── Whatsapp.ts │ │ └── WhatsappQueue.ts │ ├── routes │ │ ├── apiRoutes.ts │ │ ├── authRoutes.ts │ │ ├── contactRoutes.ts │ │ ├── dashboardRoutes.ts │ │ ├── index.ts │ │ ├── messageRoutes.ts │ │ ├── queueRoutes.ts │ │ ├── quickAnswerRoutes.ts │ │ ├── settingRoutes.ts │ │ ├── tagRoutes.ts │ │ ├── ticketRoutes.ts │ │ ├── userRoutes.ts │ │ ├── whatsappRoutes.ts │ │ └── whatsappSessionRoutes.ts │ ├── server.ts │ ├── services │ │ ├── AuthServices │ │ │ ├── FindUserFromToken.ts │ │ │ └── RefreshTokenService.ts │ │ ├── ContactServices │ │ │ ├── CreateContactService.ts │ │ │ ├── CreateOrUpdateContactService.ts │ │ │ ├── DeleteAllContactService.ts │ │ │ ├── DeleteContactService.ts │ │ │ ├── GetContactService.ts │ │ │ ├── ListContactsService.ts │ │ │ ├── ShowContactService.ts │ │ │ └── UpdateContactService.ts │ │ ├── MessageServices │ │ │ ├── CreateMessageService.ts │ │ │ ├── ListMessagesService.ts │ │ │ └── isQueueIdHistoryBlocked.ts │ │ ├── QueueService │ │ │ ├── CreateQueueService.ts │ │ │ ├── DeleteQueueService.ts │ │ │ ├── ListQueuesService.ts │ │ │ ├── ShowQueueService.ts │ │ │ └── UpdateQueueService.ts │ │ ├── QuickAnswerService │ │ │ ├── CreateQuickAnswerService.ts │ │ │ ├── DeleteAllQuickAnswerService.ts │ │ │ ├── DeleteQuickAnswerService.ts │ │ │ ├── ListQuickAnswerService.ts │ │ │ ├── ShowQuickAnswerService.ts │ │ │ └── UpdateQuickAnswerService.ts │ │ ├── ReportService │ │ │ └── DashbardDataService.ts │ │ ├── SettingServices │ │ │ ├── ListSettingByValueService.ts │ │ │ ├── ListSettingsService.ts │ │ │ ├── ListSettingsServiceOne.ts │ │ │ └── UpdateSettingService.ts │ │ ├── TagServices │ │ │ ├── CreateService.ts │ │ │ ├── DeleteAllService.ts │ │ │ ├── DeleteService.ts │ │ │ ├── ListService.ts │ │ │ ├── ShowService.ts │ │ │ ├── SimpleListService.ts │ │ │ ├── SyncTagsService.ts │ │ │ └── UpdateService.ts │ │ ├── TicketServices │ │ │ ├── CreateTicketService.ts │ │ │ ├── DeleteTicketService.ts │ │ │ ├── FindOrCreateATicketTrakingService.ts │ │ │ ├── FindOrCreateTicketService.ts │ │ │ ├── ListTicketsService.ts │ │ │ ├── ShowTicketService.ts │ │ │ └── UpdateTicketService.ts │ │ ├── UserServices │ │ │ ├── AuthUserService.ts │ │ │ ├── CreateUserService.ts │ │ │ ├── DeleteUserService.ts │ │ │ ├── ListUsersService.ts │ │ │ ├── ShowUserService.ts │ │ │ └── UpdateUserService.ts │ │ ├── WbotServices │ │ │ ├── CheckIsValidContact.ts │ │ │ ├── CheckNumber.ts │ │ │ ├── DeleteWhatsAppMessage.ts │ │ │ ├── GetProfilePicUrl.ts │ │ │ ├── ImportContactsService.ts │ │ │ ├── SendWhatsAppMedia.ts │ │ │ ├── SendWhatsAppMessage.ts │ │ │ ├── StartAllWhatsAppsSessions.ts │ │ │ ├── StartWhatsAppSession.ts │ │ │ ├── wbotCloseTickets.ts │ │ │ ├── wbotMessageListener.ts │ │ │ └── wbotMonitor.ts │ │ └── WhatsappService │ │ │ ├── AssociateWhatsappQueue.ts │ │ │ ├── CreateWhatsAppService.ts │ │ │ ├── DeleteWhatsAppService.ts │ │ │ ├── ListWhatsAppsService.ts │ │ │ ├── ShowWhatsAppService.ts │ │ │ ├── ShowWhatsaAppHours.ts │ │ │ └── UpdateWhatsAppService.ts │ ├── swagger.json │ └── utils │ │ └── logger.ts └── tsconfig.json ├── docs ├── CHANGELOG.md ├── INSTALL_VPS.md ├── INSTALL_horarioVPS.md ├── INSTALL_localhost.md └── INSTALL_phpmyadmin.md └── frontend ├── .env.example ├── .gitignore ├── package.json ├── public ├── android-chrome-192x192.png ├── apple-touch-icon.png ├── default-profile.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── manifest.json └── mstile-150x150.png ├── server.js └── src ├── App.js ├── assets ├── Logo_circle.png ├── Logo_circle_mkthub.png ├── logo-dash-mkthub.png ├── logo-dash.png ├── logo-mktshub.png ├── logo.png ├── receive.png ├── send.png ├── sound.mp3 ├── sound.ogg ├── wa-background-dark.jpg └── wa-background-light.png ├── components ├── AcceptTicketWithoutQueueModal │ └── index.js ├── Audio │ └── index.jsx ├── BackdropLoading │ └── index.js ├── ButtonWithSpinner │ └── index.js ├── Can │ └── index.js ├── ColorPicker │ └── index.js ├── ConfirmationModal │ └── index.js ├── ContactDrawer │ ├── ModalImage.js │ └── index.js ├── ContactDrawerSkeleton │ └── index.js ├── ContactModal │ └── index.js ├── ContactTag │ └── index.js ├── CopyToClipboard │ └── index.js ├── Dashboard │ ├── CardCounter.js │ └── TableAttendantsStatus.js ├── FormikTextField │ └── 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 ├── MessageVariablesPicker │ └── index.js ├── MessagesList │ └── index.js ├── ModalImageCors │ └── index.js ├── NewTicketModal │ └── index.js ├── NewTicketModalPageContact │ └── index.js ├── NotificationsPopOver │ └── index.js ├── OutlinedDiv │ └── index.js ├── QrcodeModal │ └── index.js ├── QueueModal │ └── index.js ├── QueueSelect │ └── index.js ├── QuickAnswersModal │ └── 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 ├── TransferTicketModal │ └── index.js ├── UserModal │ └── index.js ├── UsersFilter │ └── index.js ├── VcardPreview │ └── index.js ├── WhatsAppModal │ └── index.js └── WithSkeleton │ └── index.js ├── config.js ├── config.json.example ├── context ├── Auth │ └── AuthContext.js ├── ReplyingMessage │ └── ReplyingMessageContext.js └── WhatsApp │ └── WhatsAppsContext.js ├── errors └── toastError.js ├── hooks ├── useAuth.js │ └── index.js ├── useDashboard │ └── index.js ├── useLocalStorage │ └── index.js ├── useQueues │ └── index.js ├── useTickets │ └── index.js └── useWhatsApps │ └── index.js ├── index.js ├── layout ├── MainListItems.js └── index.js ├── pages ├── Api │ └── index.js ├── ApiDocs │ └── index.js ├── ApiKey │ └── index.js ├── Connections │ └── index.js ├── Contacts │ └── index.js ├── Dashboard │ ├── Chart.js │ ├── ChartAll.js │ ├── Title.js │ └── index.js ├── Login │ └── index.js ├── Queues │ └── index.js ├── QuickAnswers │ └── index.js ├── Settings │ └── 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 /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 20 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .docker/data/ 2 | ssl/ 3 | .env 4 | .vscode -------------------------------------------------------------------------------- /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/.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 | PORT=8080 5 | PROXY_PORT=8080 6 | CHROME_BIN=C:\Program Files\Google\Chrome\Application\chrome.exe 7 | 8 | DB_DIALECT=mysql 9 | DB_HOST=localhost 10 | DB_TIMEZONE=-03:00 11 | DB_USER=root 12 | DB_PASS= 13 | DB_NAME=pressticket 14 | 15 | USER_LIMIT=3 16 | CONNECTIONS_LIMIT=1 17 | 18 | JWT_SECRET=5g1yk7pD9q3YL0iBEuUlPwOiWLj3I5tK+/rhHm+jgdE= 19 | JWT_REFRESH_SECRET=F2c8gag5nvqQkBOmOu5dWkK+gqZnjPUzHmx7S2tWkvs= -------------------------------------------------------------------------------- /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 | "@typescript-eslint/no-non-null-assertion": "off", 21 | "@typescript-eslint/no-unused-vars": [ 22 | "error", 23 | { "argsIgnorePattern": "_" } 24 | ], 25 | "import/prefer-default-export": "off", 26 | "no-console": "off", 27 | "no-param-reassign": "off", 28 | "prettier/prettier": "error", 29 | "import/extensions": [ 30 | "error", 31 | "ignorePackages", 32 | { 33 | "ts": "never" 34 | } 35 | ], 36 | "quotes": [ 37 | 1, 38 | "double", 39 | { 40 | "avoidEscape": true 41 | } 42 | ] 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "typescript": {} 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/* 3 | dist 4 | !public/.gitkeep 5 | .env 6 | .env.test 7 | .wwebjs_auth 8 | sequelizeData.json 9 | 10 | package-lock.json 11 | yarn.lock 12 | yarn-error.log 13 | 14 | /src/config/sentry.js 15 | 16 | # Ignore test-related files 17 | /coverage.data 18 | /coverage/ 19 | -------------------------------------------------------------------------------- /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/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/app.ts: -------------------------------------------------------------------------------- 1 | import "./bootstrap"; 2 | import "reflect-metadata"; 3 | import "express-async-errors"; 4 | import express, { Request, Response, NextFunction } from "express"; 5 | import cors from "cors"; 6 | import cookieParser from "cookie-parser"; 7 | import * as Sentry from "@sentry/node"; 8 | 9 | import "./database"; 10 | import uploadConfig from "./config/upload"; 11 | import AppError from "./errors/AppError"; 12 | import routes from "./routes"; 13 | import { logger } from "./utils/logger"; 14 | 15 | Sentry.init({ dsn: process.env.SENTRY_DSN }); 16 | 17 | const app = express(); 18 | 19 | app.use( 20 | cors({ 21 | credentials: true, 22 | origin: process.env.FRONTEND_URL 23 | }) 24 | ); 25 | app.use(cookieParser()); 26 | app.use(express.json()); 27 | app.use(Sentry.Handlers.requestHandler()); 28 | app.use("/public", express.static(uploadConfig.directory)); 29 | app.use(routes); 30 | 31 | app.use(Sentry.Handlers.errorHandler()); 32 | 33 | app.use(async (err: Error, req: Request, res: Response, _: NextFunction) => { 34 | if (err instanceof AppError) { 35 | logger.warn(err); 36 | return res.status(err.statusCode).json({ error: err.message }); 37 | } 38 | 39 | logger.error(err); 40 | return res.status(500).json({ error: "Internal server error" }); 41 | }); 42 | 43 | export default app; 44 | -------------------------------------------------------------------------------- /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: "15m", 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: process.env.DB_TIMEZONE || "-03:00", 10 | host: process.env.DB_HOST, 11 | port: process.env.DB_PORT, 12 | database: process.env.DB_NAME, 13 | username: process.env.DB_USER, 14 | password: process.env.DB_PASS, 15 | logging: false, 16 | seederStorage: "json", 17 | seederStoragePath: "sequelizeData.json" 18 | }; 19 | -------------------------------------------------------------------------------- /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() + "_" + file.originalname; 12 | 13 | return cb(null, fileName); 14 | } 15 | }) 16 | }; -------------------------------------------------------------------------------- /backend/src/controllers/DashbardController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | import DashboardDataService, { 4 | DashboardData, 5 | Params 6 | } from "../services/ReportService/DashbardDataService"; 7 | 8 | export const index = async (req: Request, res: Response): Promise => { 9 | const params : Params = req.query; 10 | let daysInterval = 3; 11 | 12 | const dashboardData: DashboardData = await DashboardDataService( 13 | params 14 | ); 15 | return res.status(200).json(dashboardData); 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); 6 | await ImportContactsService(userId); 7 | 8 | return res.status(200).json({ message: "contacts imported" }); 9 | }; -------------------------------------------------------------------------------- /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 === "") { // Função que libera ou bloqueio os usuarios de acessar as configurações do Settings 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/controllers/WhatsAppSessionController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { getWbot } from "../libs/wbot"; 3 | import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; 4 | import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; 5 | import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService"; 6 | 7 | const store = async (req: Request, res: Response): Promise => { 8 | const { whatsappId } = req.params; 9 | const whatsapp = await ShowWhatsAppService(whatsappId); 10 | 11 | StartWhatsAppSession(whatsapp); 12 | 13 | return res.status(200).json({ message: "Starting session." }); 14 | }; 15 | 16 | const update = async (req: Request, res: Response): Promise => { 17 | const { whatsappId } = req.params; 18 | 19 | const { whatsapp } = await UpdateWhatsAppService({ 20 | whatsappId, 21 | whatsappData: { session: "" } 22 | }); 23 | 24 | StartWhatsAppSession(whatsapp); 25 | 26 | return res.status(200).json({ message: "Starting session." }); 27 | }; 28 | 29 | const remove = async (req: Request, res: Response): Promise => { 30 | const { whatsappId } = req.params; 31 | const whatsapp = await ShowWhatsAppService(whatsappId); 32 | 33 | const wbot = getWbot(whatsapp.id); 34 | 35 | wbot.logout(); 36 | 37 | return res.status(200).json({ message: "Session disconnected." }); 38 | }; 39 | 40 | export default { store, remove, update }; 41 | -------------------------------------------------------------------------------- /backend/src/database/index.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from "sequelize-typescript"; 2 | import User from "../models/User"; 3 | import Setting from "../models/Setting"; 4 | import Contact from "../models/Contact"; 5 | import Ticket from "../models/Ticket"; 6 | import Whatsapp from "../models/Whatsapp"; 7 | import ContactCustomField from "../models/ContactCustomField"; 8 | import TicketTraking from "../models/TicketTraking"; 9 | import Message from "../models/Message"; 10 | import Queue from "../models/Queue"; 11 | import WhatsappQueue from "../models/WhatsappQueue"; 12 | import UserQueue from "../models/UserQueue"; 13 | import QuickAnswer from "../models/QuickAnswer"; 14 | import Tag from "../models/Tag"; 15 | import ContactTag from "../models/ContactTag"; 16 | import UserRating from "../models/UserRating"; 17 | 18 | // eslint-disable-next-line 19 | const dbConfig = require("../config/database"); 20 | // import dbConfig from "../config/database"; 21 | 22 | const sequelize = new Sequelize(dbConfig); 23 | 24 | const models = [ 25 | User, 26 | Contact, 27 | Ticket, 28 | Message, 29 | Whatsapp, 30 | ContactCustomField, 31 | Setting, 32 | Queue, 33 | WhatsappQueue, 34 | UserQueue, 35 | QuickAnswer, 36 | Tag, 37 | ContactTag, 38 | UserRating, 39 | TicketTraking 40 | ]; 41 | 42 | sequelize.addModels(models); 43 | 44 | export default sequelize; 45 | -------------------------------------------------------------------------------- /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-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.STRING 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/20210109192532-add-column-online-to-Users-table.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "online", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Users", "online"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20210109192534-add-rated-to-TicketTraking.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return Promise.all([ 6 | queryInterface.addColumn("TicketTraking", "ratingAt", { 7 | type: DataTypes.DATE, 8 | allowNull: true, 9 | defaultValue: null 10 | }), 11 | queryInterface.addColumn("TicketTraking", "rated", { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | }) 15 | ]); 16 | }, 17 | 18 | down: (queryInterface: QueryInterface) => { 19 | return Promise.all([ 20 | queryInterface.removeColumn("TicketTraking", "ratingAt"), 21 | queryInterface.removeColumn("TicketTraking", "rated") 22 | ]); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /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/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 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20220619203200-add-startwork-queues.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Queues", "startWork", { 6 | type: DataTypes.STRING, 7 | allowNull: true, 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Queues", "startWork"); 13 | } 14 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20220619203500-add-endwork-queues.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Queues", "endWork", { 6 | type: DataTypes.STRING, 7 | allowNull: true, 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Queues", "endWork"); 13 | } 14 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20220619203900-add-absencemessage-queues.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Queues", "absenceMessage", { 6 | type: DataTypes.TEXT, 7 | allowNull: true, 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Queues", "absenceMessage"); 13 | } 14 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20220906150400-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/20220906150600-create-associate-contacttags.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("ContactTags", { 6 | contactId: { 7 | type: DataTypes.INTEGER, 8 | references: { model: "Contacts", 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("ContactTags"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20221007000001-add-ratingMessage-to-Whatsapps.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "ratingMessage", { 6 | type: DataTypes.TEXT, 7 | defaultValue: "" 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "ratingMessage"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20221012212600-add-startwork-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "startWork", { 6 | type: DataTypes.STRING, 7 | allowNull: true, 8 | defaultValue: "00:00" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "startWork"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20221012212700-add-endwork-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "endWork", { 6 | type: DataTypes.STRING, 7 | allowNull: true, 8 | defaultValue: "23:59" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "endWork"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20221023085500-add-isdisplay-to-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "isDisplay", { 6 | type: DataTypes.BOOLEAN, 7 | allowNull: false, 8 | defaultValue: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "isDisplay"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20221128234000-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/20221216000000-create-UserRatings-table.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.createTable("UserRatings", { 6 | id: { 7 | type: DataTypes.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | allowNull: false 11 | }, 12 | ticketId: { 13 | type: DataTypes.INTEGER, 14 | references: { model: "Tickets", key: "id" }, 15 | onDelete: "CASCADE" 16 | }, 17 | userId: { 18 | type: DataTypes.INTEGER, 19 | references: { model: "Users", key: "id" }, 20 | onDelete: "CASCADE", 21 | allowNull: true 22 | }, 23 | rate: { 24 | type: DataTypes.INTEGER, 25 | defaultValue: 0 26 | }, 27 | createdAt: { 28 | type: DataTypes.DATE, 29 | allowNull: true 30 | }, 31 | updatedAt: { 32 | type: DataTypes.DATE, 33 | allowNull: true 34 | } 35 | }); 36 | }, 37 | 38 | down: (queryInterface: QueryInterface) => { 39 | return queryInterface.dropTable("UserRatings"); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20222016014719-add-useNPS-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | // 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "useNPS", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "useNPS"); 13 | } 14 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230310100337-add-allHistoric-users.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "allHistoric", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "allHistoric"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313060000-add-outOfWorkMessage.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 | defaultValue: "" 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("Whatsapps", "outOfWorkMessage"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313070000-add-defineWorkHours.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/20230313080001-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/20230313080002-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/20230313080003-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/20230313080004-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/20230313080005-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/20230313080006-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/20230313080007-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/20230313100800-add-StartDefineWorkHoursMonday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursMonday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursMonday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313101200-add-EndDefineWorkHoursMonday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursMonday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursMonday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313101300-add-StartDefineWorkHoursMondayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursMondayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursMondayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313101301-add-EndDefineWorkHoursMondayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursMondayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursMondayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102400-add-StartDefineWorkHoursTuesday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursTuesday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursTuesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102401-add-EndDefineWorkHoursTuesday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursTuesday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursTuesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102500-add-StartDefineWorkHoursTuesdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursTuesdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursTuesdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102600-add-EndDefineWorkHoursTuesdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursTuesdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursTuesdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102800-add-StartDefineWorkHoursWednesday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursWednesday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursWednesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102900-add-EndDefineWorkHoursWednesday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursWednesday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursWednesday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313102901-add-StartDefineWorkHoursWednesdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursWednesdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursWednesdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103000-add-EndDefineWorkHoursWednesdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursWednesdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursWednesdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103100-add-StartDefineWorkHoursThursday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursThursday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursThursday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103300-add-EndDefineWorkHoursThursday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursThursday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursThursday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103301-add-StartDefineWorkHoursThursdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursThursdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursThursdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103400-add-EndDefineWorkHoursThursdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursThursdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursThursdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103500-add-StartDefineWorkHoursFriday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursFriday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursFriday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103501-add-EndDefineWorkHoursFriday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursFriday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursFriday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103600-add-StartDefineWorkHoursFridayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursFridayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursFridayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103601-add-EndDefineWorkHoursFridayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursFridayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursFridayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103700-add-StartDefineWorkHoursSaturday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursSaturday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursSaturday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103800-add-EndDefineWorkHoursSaturday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursSaturday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursSaturday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103801-add-StartDefineWorkHoursSaturdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursSaturdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursSaturdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313103900-add-EndDefineWorkHoursSaturdayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursSaturdayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursSaturdayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313104000-add-StartDefineWorkHoursSunday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursSunday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursSunday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313104100-add-EndDefineWorkHoursSunday.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursSunday", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursSunday"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313104101-add-StartDefineWorkHoursSundayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "StartDefineWorkHoursSundayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "StartDefineWorkHoursSundayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230313104200-add-EndDefineWorkHoursSundayLunch.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "EndDefineWorkHoursSundayLunch", { 6 | type: DataTypes.TEXT, 7 | defaultValue: null, 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "EndDefineWorkHoursSundayLunch"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230411131007-add-isGroup-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "isGroup", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "isGroup"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-allTickets-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "allTicket", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "desabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "allTicket"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-isRemoveTags-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "isRemoveTags", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "isRemoveTags"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-viewConection-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "viewConection", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "viewConection"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-viewName-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "viewName", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "viewName"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-viewSector-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "viewSector", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "viewSector"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230417203900-add-viewTags-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Users", "viewTags", { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: "enabled" 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Users", "viewTags"); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/src/database/migrations/20230426221007-add-inactiveMessage-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "inactiveMessage", { 6 | type: DataTypes.STRING, 7 | defaultValue: "", 8 | allowNull: true 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "inactiveMessage"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230426221007-add-sendInactiveMessage-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "sendInactiveMessage", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "sendInactiveMessage"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230426221007-add-timeInactiveMessage-whatsapp.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Whatsapps", "timeInactiveMessage", { 6 | type: DataTypes.STRING, 7 | defaultValue: "0", 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Whatsapps", "timeInactiveMessage"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230505221007-add-closedAt-TicketTraking.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("TicketTraking", "closedAt", { 6 | type: DataTypes.DATE, 7 | defaultValue: null 8 | }); 9 | }, 10 | 11 | down: (queryInterface: QueryInterface) => { 12 | return queryInterface.removeColumn("TicketTraking", "closedAt"); 13 | } 14 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230505221007-add-fromMe-Ticket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "fromMe", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Tickets", "fromMe"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230505221007-add-isFinished-Ticket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "isFinished", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Tickets", "isFinished"); 14 | } 15 | }; -------------------------------------------------------------------------------- /backend/src/database/migrations/20230505221007-add-isMsgGroup-Ticket.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface, DataTypes } from "sequelize"; 2 | 3 | module.exports = { 4 | up: (queryInterface: QueryInterface) => { 5 | return queryInterface.addColumn("Tickets", "isMsgGroup", { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false 9 | }); 10 | }, 11 | 12 | down: (queryInterface: QueryInterface) => { 13 | return queryInterface.removeColumn("Tickets", "isMsgGroup"); 14 | } 15 | }; -------------------------------------------------------------------------------- /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: "mkthub", 10 | email: "admin@mkthub.tech", 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/20230130004700-create-alltickets-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: "allTicket", 10 | value: "disabled", 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/20230326221400-create-ASC-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: "ASC", 10 | value: "disabled", 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/20230326221600-create-created-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: "created", 10 | value: "disabled", 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 | import User from "../models/User"; 5 | import { logger } from "../utils/logger"; 6 | 7 | const CheckContactOpenTickets = async ( 8 | contactId: number, 9 | whatsappId: number 10 | ): Promise => { 11 | const ticket = await Ticket.findOne({ 12 | where: { 13 | contactId, 14 | whatsappId, 15 | status: { 16 | [Op.or]: ["open", "pending"] 17 | } 18 | }, 19 | include: [{ model: User, as: "user" }] // associação com o modelo User 20 | }); 21 | 22 | if (ticket) { 23 | const userName = ticket.user?.name; // acessa o nome do usuário através do objeto ticket 24 | 25 | logger.info('Debug chamado ja Aberto ' + (userName ? userName : 'Sem Nome')); 26 | 27 | if (userName) { 28 | throw new AppError(`Já existe um chamado aberto para este contato com ${userName}`); 29 | } else { 30 | logger.info('Debug Ticket ja Aberto ' + 'Sem Nome'); 31 | throw new AppError("Já existe um chamado aberto para este contato com Sem Nome"); 32 | } 33 | } 34 | }; 35 | 36 | export default CheckContactOpenTickets; -------------------------------------------------------------------------------- /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 ( 6 | userId?: number 7 | ): Promise => { 8 | if(userId) { 9 | const whatsappByUser = await GetDefaultWhatsAppByUser(userId); 10 | if(whatsappByUser !== null) { 11 | return whatsappByUser; 12 | } 13 | } 14 | 15 | const defaultWhatsapp = await Whatsapp.findOne({ 16 | where: { isDefault: true } 17 | }); 18 | 19 | if (!defaultWhatsapp) { 20 | throw new AppError("ERR_NO_DEF_WAPP_FOUND"); 21 | } 22 | 23 | return defaultWhatsapp; 24 | }; 25 | 26 | export default GetDefaultWhatsApp; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /backend/src/helpers/GetTicketWbot.ts: -------------------------------------------------------------------------------- 1 | import { Client as Session } from "whatsapp-web.js"; 2 | import { getWbot } from "../libs/wbot"; 3 | import GetDefaultWhatsApp from "./GetDefaultWhatsApp"; 4 | import Ticket from "../models/Ticket"; 5 | 6 | const GetTicketWbot = async (ticket: Ticket): Promise => { 7 | if (!ticket.whatsappId) { 8 | const defaultWhatsapp = await GetDefaultWhatsApp(ticket.user.id); 9 | 10 | await ticket.$set("whatsapp", defaultWhatsapp); 11 | } 12 | 13 | const wbot = getWbot(ticket.whatsappId); 14 | 15 | return wbot; 16 | }; 17 | 18 | export default GetTicketWbot; -------------------------------------------------------------------------------- /backend/src/helpers/GetWbotMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message as WbotMessage } from "whatsapp-web.js"; 2 | import Ticket from "../models/Ticket"; 3 | import GetTicketWbot from "./GetTicketWbot"; 4 | import AppError from "../errors/AppError"; 5 | 6 | export const GetWbotMessage = async ( 7 | ticket: Ticket, 8 | messageId: string 9 | ): Promise => { 10 | const wbot = await GetTicketWbot(ticket); 11 | 12 | const wbotChat = await wbot.getChatById( 13 | `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` 14 | ); 15 | 16 | let limit = 20; 17 | 18 | const fetchWbotMessagesGradually = async (): Promise => { 19 | const chatMessages = await wbotChat.fetchMessages({ limit }); 20 | 21 | const msgFound = chatMessages.find(msg => msg.id.id === messageId); 22 | 23 | if (!msgFound && limit < 100) { 24 | limit += 20; 25 | return fetchWbotMessagesGradually(); 26 | } 27 | 28 | return msgFound; 29 | }; 30 | 31 | try { 32 | const msgFound = await fetchWbotMessagesGradually(); 33 | 34 | if (!msgFound) { 35 | throw new Error("Cannot found message within 100 last messages"); 36 | } 37 | 38 | return msgFound; 39 | } catch (err) { 40 | throw new AppError("ERR_FETCH_WAPP_MSG"); 41 | } 42 | }; 43 | 44 | export default GetWbotMessage; 45 | -------------------------------------------------------------------------------- /backend/src/helpers/GetWhatsappWbot.ts: -------------------------------------------------------------------------------- 1 | import { getWbot } from "../libs/wbot"; 2 | import Whatsapp from "../models/Whatsapp"; 3 | 4 | const GetWhatsappWbot = async (whatsapp: Whatsapp) => { 5 | const wbot = await getWbot(whatsapp.id); 6 | return wbot; 7 | }; 8 | 9 | export default GetWhatsappWbot; -------------------------------------------------------------------------------- /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 | allHistoric: string, 13 | isRemoveTags: string, 14 | viewConection: string, 15 | viewSector: string, 16 | viewName: string, 17 | viewTags: string, 18 | allTicket: string, 19 | startWork: string; 20 | endWork: string; 21 | } 22 | 23 | export const SerializeUser = (user: User): SerializedUser => { 24 | return { 25 | id: user.id, 26 | name: user.name, 27 | email: user.email, 28 | profile: user.profile, 29 | queues: user.queues, 30 | whatsapp: user.whatsapp, 31 | allHistoric: user.allHistoric, 32 | isRemoveTags: user.isRemoveTags, 33 | viewConection: user.viewConection, 34 | viewSector: user.viewSector, 35 | viewName: user.viewName, 36 | viewTags: user.viewTags, 37 | allTicket: user.allTicket, 38 | startWork: user.startWork, 39 | endWork: user.endWork 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /backend/src/helpers/SerializeWbotMsgId.ts: -------------------------------------------------------------------------------- 1 | import Message from "../models/Message"; 2 | import Ticket from "../models/Ticket"; 3 | 4 | 5 | const SerializeWbotMsgId = (ticket: Ticket, message: Message): string => { 6 | 7 | // let SeGrupo = 'g.us_' + message.id + '_'+ (message.contact.number ?? ticket) + '@c.us'; 8 | // let SeIndiv = 'c.us_' + message.id; 9 | let SeGrupo; 10 | if (ticket.isGroup) { 11 | if (message.contact != null) { 12 | SeGrupo = `g.us_${message.id}_${message.contact.number}@c.us`; 13 | } else { 14 | SeGrupo = `g.us_${message.id}_${ticket.whatsappId}@c.us`; 15 | // console.log(ticket) 16 | } 17 | 18 | } 19 | else { 20 | SeGrupo = `c.us_${message.id}`; 21 | } 22 | 23 | 24 | // const serializedMsgId = `${message.fromMe}_${ticket.contact.number}@${ticket.isGroup ? SeGrupo : SeIndiv }`; 25 | const serializedMsgId = `${message.fromMe}_${ticket.contact.number}@${SeGrupo}`; 26 | return serializedMsgId; 27 | }; 28 | 29 | export default SerializeWbotMsgId; -------------------------------------------------------------------------------- /backend/src/helpers/SetTicketMessagesAsRead.ts: -------------------------------------------------------------------------------- 1 | import { getIO } from "../libs/socket"; 2 | import Message from "../models/Message"; 3 | import Ticket from "../models/Ticket"; 4 | import { logger } from "../utils/logger"; 5 | import GetTicketWbot from "./GetTicketWbot"; 6 | 7 | const SetTicketMessagesAsRead = async (ticket: Ticket): Promise => { 8 | await Message.update( 9 | { read: true }, 10 | { 11 | where: { 12 | ticketId: ticket.id, 13 | read: false 14 | } 15 | } 16 | ); 17 | 18 | await ticket.update({ unreadMessages: 0 }); 19 | 20 | try { 21 | const wbot = await GetTicketWbot(ticket); 22 | await wbot.sendSeen( 23 | `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` 24 | ); 25 | } catch (err) { 26 | logger.warn( 27 | `Could not mark messages as read. Maybe whatsapp session disconnected? Err: ${err}` 28 | ); 29 | } 30 | 31 | const io = getIO(); 32 | io.to(ticket.status).to("notification").emit("ticket", { 33 | action: "updateUnread", 34 | ticketId: ticket.id 35 | }); 36 | }; 37 | 38 | export default SetTicketMessagesAsRead; 39 | -------------------------------------------------------------------------------- /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/libs/socket.ts: -------------------------------------------------------------------------------- 1 | import { Server as SocketIO } from "socket.io"; 2 | import { Server } from "http"; 3 | import AppError from "../errors/AppError"; 4 | import { logger } from "../utils/logger"; 5 | 6 | let io: SocketIO; 7 | 8 | export const initIO = (httpServer: Server): SocketIO => { 9 | io = new SocketIO(httpServer, { 10 | cors: { 11 | origin: process.env.FRONTEND_URL 12 | } 13 | }); 14 | 15 | io.on("connection", socket => { 16 | logger.info("Client Connected"); 17 | socket.on("joinChatBox", (ticketId: string) => { 18 | logger.info("A client joined a ticket channel"); 19 | socket.join(ticketId); 20 | }); 21 | 22 | socket.on("joinNotification", () => { 23 | logger.info("A client joined notification channel"); 24 | socket.join("notification"); 25 | }); 26 | 27 | socket.on("joinTickets", (status: string) => { 28 | logger.info(`A client joined to ${status} tickets channel.`); 29 | socket.join(status); 30 | }); 31 | 32 | socket.on("disconnect", () => { 33 | logger.info("Client disconnected"); 34 | }); 35 | }); 36 | return io; 37 | }; 38 | 39 | export const getIO = (): SocketIO => { 40 | if (!io) { 41 | throw new AppError("Socket IO not initialized"); 42 | } 43 | return io; 44 | }; 45 | -------------------------------------------------------------------------------- /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/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 | AllowNull, 3 | AutoIncrement, 4 | BelongsToMany, 5 | Column, 6 | CreatedAt, 7 | Default, 8 | HasMany, 9 | Model, 10 | PrimaryKey, 11 | Table, 12 | Unique, 13 | UpdatedAt 14 | } from "sequelize-typescript"; 15 | import ContactCustomField from "./ContactCustomField"; 16 | import Ticket from "./Ticket"; 17 | import Tag from "./Tag"; 18 | import ContactTag from "./ContactTag"; 19 | 20 | @Table 21 | class Contact extends Model { 22 | @PrimaryKey 23 | @AutoIncrement 24 | @Column 25 | id: number; 26 | 27 | @Column 28 | name: string; 29 | 30 | @AllowNull(false) 31 | @Unique 32 | @Column 33 | number: string; 34 | 35 | @AllowNull(false) 36 | @Default("") 37 | @Column 38 | email: string; 39 | 40 | @Column 41 | profilePicUrl: string; 42 | 43 | @Default(false) 44 | @Column 45 | isGroup: boolean; 46 | 47 | @CreatedAt 48 | createdAt: Date; 49 | 50 | @UpdatedAt 51 | updatedAt: Date; 52 | 53 | @HasMany(() => Ticket) 54 | tickets: Ticket[]; 55 | 56 | @HasMany(() => ContactCustomField) 57 | extraInfo: ContactCustomField[]; 58 | 59 | @HasMany(() => ContactTag) 60 | contactTags: ContactTag[]; 61 | 62 | @BelongsToMany(() => Tag, () => ContactTag) 63 | tags: Tag[]; 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/ContactTag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | ForeignKey 8 | } from "sequelize-typescript"; 9 | import Tag from "./Tag"; 10 | import Contact from "./Contact"; 11 | 12 | @Table({ 13 | tableName: "ContactTags" 14 | }) 15 | class ContactTag extends Model { 16 | @ForeignKey(() => Contact) 17 | @Column 18 | contactId: number; 19 | 20 | @ForeignKey(() => Tag) 21 | @Column 22 | tagId: number; 23 | 24 | @CreatedAt 25 | createdAt: Date; 26 | 27 | @UpdatedAt 28 | updatedAt: Date; 29 | } 30 | 31 | export default ContactTag; 32 | -------------------------------------------------------------------------------- /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 | } from "sequelize-typescript"; 13 | import User from "./User"; 14 | import UserQueue from "./UserQueue"; 15 | 16 | import Whatsapp from "./Whatsapp"; 17 | import WhatsappQueue from "./WhatsappQueue"; 18 | 19 | @Table 20 | class Queue extends Model { 21 | @PrimaryKey 22 | @AutoIncrement 23 | @Column 24 | id: number; 25 | 26 | @AllowNull(false) 27 | @Unique 28 | @Column 29 | name: string; 30 | 31 | @AllowNull(false) 32 | @Unique 33 | @Column 34 | color: string; 35 | 36 | @Column 37 | greetingMessage: string; 38 | 39 | @Column 40 | startWork: string; 41 | 42 | @Column 43 | endWork: string; 44 | 45 | @Column 46 | absenceMessage: string; 47 | 48 | @CreatedAt 49 | createdAt: Date; 50 | 51 | @UpdatedAt 52 | updatedAt: Date; 53 | 54 | @BelongsToMany(() => Whatsapp, () => WhatsappQueue) 55 | whatsapps: Array; 56 | 57 | @BelongsToMany(() => User, () => UserQueue) 58 | users: Array; 59 | } 60 | 61 | export default Queue; 62 | -------------------------------------------------------------------------------- /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/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/Tag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | AutoIncrement, 9 | BelongsToMany, 10 | HasMany 11 | } from "sequelize-typescript"; 12 | import Contact from "./Contact"; 13 | import ContactTag from "./ContactTag"; 14 | 15 | @Table 16 | class Tag extends Model { 17 | @PrimaryKey 18 | @AutoIncrement 19 | @Column 20 | id: number; 21 | 22 | @Column 23 | name: string; 24 | 25 | @Column 26 | color: string; 27 | 28 | @CreatedAt 29 | createdAt: Date; 30 | 31 | @UpdatedAt 32 | updatedAt: Date; 33 | 34 | @BelongsToMany(() => Contact, () => ContactTag) 35 | contacts: Array; 36 | 37 | @HasMany(() => ContactTag) 38 | contacttag: ContactTag[]; 39 | } 40 | 41 | export default Tag; 42 | -------------------------------------------------------------------------------- /backend/src/models/TicketTraking.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | ForeignKey, 9 | BelongsTo, 10 | AutoIncrement 11 | } from "sequelize-typescript"; 12 | 13 | import User from "./User"; 14 | import Ticket from "./Ticket"; 15 | import Whatsapp from "./Whatsapp"; 16 | 17 | @Table({ 18 | tableName: "TicketTraking" 19 | }) 20 | class TicketTraking extends Model { 21 | @PrimaryKey 22 | @AutoIncrement 23 | @Column 24 | id: number; 25 | 26 | @ForeignKey(() => Ticket) 27 | @Column 28 | ticketId: number; 29 | 30 | @BelongsTo(() => Ticket) 31 | ticket: Ticket; 32 | 33 | 34 | @ForeignKey(() => Whatsapp) 35 | @Column 36 | whatsappId: number; 37 | 38 | @BelongsTo(() => Whatsapp) 39 | whatsapp: Whatsapp; 40 | 41 | @ForeignKey(() => User) 42 | @Column 43 | userId: number; 44 | 45 | @Column 46 | rated: boolean; 47 | 48 | @BelongsTo(() => User) 49 | user: User; 50 | 51 | @CreatedAt 52 | createdAt: Date; 53 | 54 | @UpdatedAt 55 | updatedAt: Date; 56 | 57 | @Column 58 | startedAt: Date; 59 | 60 | @Column 61 | queuedAt: Date; 62 | 63 | @Column 64 | closedAt: Date; 65 | 66 | @Column 67 | finishedAt: Date; 68 | 69 | @Column 70 | ratingAt: Date; 71 | } 72 | 73 | export default TicketTraking; 74 | -------------------------------------------------------------------------------- /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/UserRating.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | CreatedAt, 5 | UpdatedAt, 6 | Model, 7 | PrimaryKey, 8 | ForeignKey, 9 | BelongsTo, 10 | AutoIncrement 11 | } from "sequelize-typescript"; 12 | 13 | import User from "./User"; 14 | import Ticket from "./Ticket"; 15 | 16 | @Table({ 17 | tableName: "UserRatings" 18 | }) 19 | class UserRating extends Model { 20 | @PrimaryKey 21 | @AutoIncrement 22 | @Column 23 | id: number; 24 | 25 | @ForeignKey(() => Ticket) 26 | @Column 27 | ticketId: number; 28 | 29 | @BelongsTo(() => Ticket) 30 | ticket: Ticket; 31 | 32 | @ForeignKey(() => User) 33 | @Column 34 | userId: number; 35 | 36 | @BelongsTo(() => User) 37 | user: User; 38 | 39 | @Column 40 | rate: number; 41 | 42 | @CreatedAt 43 | createdAt: Date; 44 | 45 | @UpdatedAt 46 | updatedAt: Date; 47 | } 48 | 49 | export default UserRating; 50 | -------------------------------------------------------------------------------- /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 | ApiRoutes.get("/queue/list", isAuthApi, ApiController.list); 14 | 15 | export default ApiRoutes; 16 | -------------------------------------------------------------------------------- /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/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("/contacts/import", isAuth, ImportPhoneContactsController.store); 10 | contactRoutes.get("/contacts", isAuth, ContactController.index); 11 | contactRoutes.get("/contacts/:contactId", isAuth, ContactController.show); 12 | contactRoutes.post("/contacts", isAuth, ContactController.store); 13 | contactRoutes.post("/contact", isAuth, ContactController.getContact); 14 | contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update); 15 | contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove); 16 | contactRoutes.delete("/contacts", isAuth, ContactController.removeAll); 17 | 18 | export default contactRoutes; 19 | -------------------------------------------------------------------------------- /backend/src/routes/dashboardRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuth from "../middleware/isAuth"; 3 | 4 | import * as DashboardController from "../controllers/DashbardController"; 5 | 6 | const routes = express.Router(); 7 | 8 | routes.get("/dashboard", isAuth, DashboardController.index); 9 | 10 | export default routes; 11 | -------------------------------------------------------------------------------- /backend/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import userRoutes from "./userRoutes"; 4 | import authRoutes from "./authRoutes"; 5 | import settingRoutes from "./settingRoutes"; 6 | import contactRoutes from "./contactRoutes"; 7 | import ticketRoutes from "./ticketRoutes"; 8 | import whatsappRoutes from "./whatsappRoutes"; 9 | import messageRoutes from "./messageRoutes"; 10 | import whatsappSessionRoutes from "./whatsappSessionRoutes"; 11 | import queueRoutes from "./queueRoutes"; 12 | import quickAnswerRoutes from "./quickAnswerRoutes"; 13 | import apiRoutes from "./apiRoutes"; 14 | import tagRoutes from "./tagRoutes"; 15 | import dashboardRoutes from "./dashboardRoutes" 16 | 17 | const routes = Router(); 18 | 19 | routes.use(userRoutes); 20 | routes.use("/auth", authRoutes); 21 | routes.use(settingRoutes); 22 | routes.use(contactRoutes); 23 | routes.use(ticketRoutes); 24 | routes.use(whatsappRoutes); 25 | routes.use(messageRoutes); 26 | routes.use(whatsappSessionRoutes); 27 | routes.use(queueRoutes); 28 | routes.use(quickAnswerRoutes); 29 | routes.use("/api", apiRoutes); 30 | routes.use(tagRoutes); 31 | routes.use(dashboardRoutes); 32 | 33 | export default routes; 34 | -------------------------------------------------------------------------------- /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("/quickAnswers/:quickAnswerId", isAuth, QuickAnswerController.show); 11 | quickAnswerRoutes.post("/quickAnswers", isAuth, QuickAnswerController.store); 12 | quickAnswerRoutes.put("/quickAnswers/:quickAnswerId", isAuth, QuickAnswerController.update); 13 | quickAnswerRoutes.delete("/quickAnswers/:quickAnswerId", isAuth, QuickAnswerController.remove); 14 | quickAnswerRoutes.delete("/quickAnswers", isAuth, QuickAnswerController.removeAll); 15 | 16 | export default quickAnswerRoutes; 17 | -------------------------------------------------------------------------------- /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 | tagRoutes.get("/tags", isAuth, TagController.index); 10 | tagRoutes.post("/tags", isAuth, TagController.store); 11 | tagRoutes.put("/tags/:tagId", isAuth, TagController.update); 12 | tagRoutes.get("/tags/:tagId", isAuth, TagController.show); 13 | tagRoutes.delete("/tags/:tagId", isAuth, TagController.remove); 14 | tagRoutes.delete("/tags", isAuth, TagController.removeAll); 15 | tagRoutes.post("/tags/sync", isAuth, TagController.syncTags); 16 | 17 | export default tagRoutes; 18 | -------------------------------------------------------------------------------- /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/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 cron from "node-cron"; 4 | import { initIO } from "./libs/socket"; 5 | import { logger } from "./utils/logger"; 6 | import { StartAllWhatsAppsSessions } from "./services/WbotServices/StartAllWhatsAppsSessions"; 7 | import { ClosedAllOpenTickets } from "./services/WbotServices/wbotCloseTickets"; 8 | 9 | import swaggerUi from "swagger-ui-express"; 10 | 11 | import swaggerDocs from "./swagger.json"; 12 | 13 | var options = { 14 | customCss: '.swagger-ui .topbar { display: none }' 15 | }; 16 | 17 | app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs, options)); 18 | 19 | const server = app.listen(process.env.PORT, () => { 20 | logger.info(`Servidor iniciado na porta: ${process.env.PORT}`); 21 | }); 22 | 23 | cron.schedule("*/1 * * * *", async () => { 24 | 25 | try { 26 | await ClosedAllOpenTickets(); 27 | } 28 | catch (error) { 29 | logger.error(error); 30 | } 31 | 32 | }); 33 | 34 | initIO(server); 35 | StartAllWhatsAppsSessions(); 36 | gracefulShutdown(server); -------------------------------------------------------------------------------- /backend/src/services/AuthServices/FindUserFromToken.ts: -------------------------------------------------------------------------------- 1 | import { verify } from "jsonwebtoken"; 2 | import ShowUserService from "../UserServices/ShowUserService"; 3 | import authConfig from "../../config/auth"; 4 | import User from "../../models/User"; 5 | 6 | interface RefreshTokenPayload { 7 | id: string; 8 | tokenVersion: number; 9 | } 10 | 11 | export default async function FindUserFromToken(token: string): Promise { 12 | const decoded = verify(token, authConfig.refreshSecret); 13 | const { id } = decoded as RefreshTokenPayload; 14 | 15 | const user = await ShowUserService(id); 16 | return user; 17 | } 18 | -------------------------------------------------------------------------------- /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/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/CreateOrUpdateContactService.ts: -------------------------------------------------------------------------------- 1 | import { getIO } from "../../libs/socket"; 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 | isGroup: boolean; 13 | email?: string; 14 | profilePicUrl?: string; 15 | extraInfo?: ExtraInfo[]; 16 | } 17 | 18 | const CreateOrUpdateContactService = async ({ 19 | name, 20 | number: rawNumber, 21 | profilePicUrl, 22 | isGroup, 23 | email = "", 24 | extraInfo = [] 25 | }: Request): Promise => { 26 | const number = isGroup ? rawNumber : rawNumber.replace(/[^0-9]/g, ""); 27 | 28 | const io = getIO(); 29 | let contact: Contact | null; 30 | 31 | contact = await Contact.findOne({ where: { number } }); 32 | 33 | if (contact) { 34 | contact.update({ profilePicUrl }); 35 | 36 | io.emit("contact", { 37 | action: "update", 38 | contact 39 | }); 40 | } else { 41 | contact = await Contact.create({ 42 | name, 43 | number, 44 | profilePicUrl, 45 | email, 46 | isGroup, 47 | extraInfo 48 | }); 49 | 50 | io.emit("contact", { 51 | action: "create", 52 | contact 53 | }); 54 | } 55 | 56 | return contact; 57 | }; 58 | 59 | export default CreateOrUpdateContactService; 60 | -------------------------------------------------------------------------------- /backend/src/services/ContactServices/DeleteAllContactService.ts: -------------------------------------------------------------------------------- 1 | import Contact from "../../models/Contact"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteAllContactService = async (): Promise => { 5 | await Contact.findAll(); 6 | 7 | if (!Contact) { 8 | throw new AppError("ERR_NO_CONTACT_FOUND", 404); 9 | } 10 | 11 | await Contact.destroy({where: {} }) 12 | }; 13 | 14 | export default DeleteAllContactService; -------------------------------------------------------------------------------- /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/ShowContactService.ts: -------------------------------------------------------------------------------- 1 | import Contact from "../../models/Contact"; 2 | import AppError from "../../errors/AppError"; 3 | import Tag from "../../models/Tag"; 4 | 5 | const ShowContactService = async (id: string | number): Promise => { 6 | const contact = await Contact.findByPk(id, { 7 | include: [ 8 | "extraInfo", 9 | { 10 | model: Tag, 11 | as: "tags", 12 | attributes: ["id", "name", "color"] 13 | } 14 | ] 15 | }); 16 | 17 | if (!contact) { 18 | throw new AppError("ERR_NO_CONTACT_FOUND", 404); 19 | } 20 | 21 | return contact; 22 | }; 23 | 24 | export default ShowContactService; 25 | -------------------------------------------------------------------------------- /backend/src/services/MessageServices/isQueueIdHistoryBlocked.ts: -------------------------------------------------------------------------------- 1 | import User from "../../models/User"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | interface Request { 5 | userRequest?: string; 6 | } 7 | 8 | const isQueueIdHistoryBlocked = async ({ 9 | userRequest 10 | }: Request): Promise => { 11 | if (!userRequest) { 12 | throw new AppError("ERR_NO_USER_FOUND", 404); 13 | } 14 | const user = await User.findByPk(userRequest); 15 | if (!user) { 16 | throw new AppError("ERR_NO_USER_FOUND", 404); 17 | } 18 | return user.allHistoric === "enabled"; 19 | }; 20 | 21 | export default isQueueIdHistoryBlocked; -------------------------------------------------------------------------------- /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({ order: [["name", "ASC"]] }); 5 | 6 | return queues; 7 | }; 8 | 9 | export default ListQueuesService; 10 | -------------------------------------------------------------------------------- /backend/src/services/QueueService/ShowQueueService.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Queue from "../../models/Queue"; 3 | 4 | const ShowQueueService = async (queueId: number | string): Promise => { 5 | const queue = await Queue.findByPk(queueId); 6 | 7 | if (!queue) { 8 | throw new AppError("ERR_QUEUE_NOT_FOUND"); 9 | } 10 | 11 | return queue; 12 | }; 13 | 14 | export default ShowQueueService; 15 | -------------------------------------------------------------------------------- /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/DeleteAllQuickAnswerService.ts: -------------------------------------------------------------------------------- 1 | import QuickAnswer from "../../models/QuickAnswer"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteAllQuickAnswerService = async (): Promise => { 5 | await QuickAnswer.findAll(); 6 | 7 | if (!QuickAnswer) { 8 | throw new AppError("ERR_NO_QUICK_ANSWER_FOUND", 404); 9 | } 10 | 11 | await QuickAnswer.destroy({ 12 | where: { } 13 | }); 14 | }; 15 | 16 | export default DeleteAllQuickAnswerService; -------------------------------------------------------------------------------- /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/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_API_TOKEN_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/ListSettingsServiceOne.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import Setting from "../../models/Setting"; 3 | 4 | interface Request { 5 | key: string; 6 | } 7 | 8 | const ListSettingsServiceOne = async ({ 9 | key 10 | }: Request): Promise => { 11 | const setting = await Setting.findOne({ 12 | where: { key } 13 | }); 14 | 15 | if (!setting) { 16 | throw new AppError("ERR_NO_SETTING_FOUND", 404); 17 | } 18 | 19 | return setting; 20 | }; 21 | 22 | export default ListSettingsServiceOne; -------------------------------------------------------------------------------- /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() 17 | .required() 18 | .min(3) 19 | .test("Check-unique-name", "ERR_TAG_NAME_ALREADY_EXISTS", async value => { 20 | if (value) { 21 | const tagWithSameName = await Tag.findOne({ 22 | where: { name: value } 23 | }); 24 | 25 | return !tagWithSameName; 26 | } 27 | return false; 28 | }) 29 | }); 30 | 31 | try { 32 | await schema.validate({ name }); 33 | } catch (err: any) { 34 | throw new AppError(err.message); 35 | } 36 | 37 | const [tag] = await Tag.findOrCreate({ 38 | where: { name, color }, 39 | defaults: { name, color } 40 | }); 41 | 42 | await tag.reload(); 43 | 44 | return tag; 45 | }; 46 | 47 | export default CreateService; 48 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/DeleteAllService.ts: -------------------------------------------------------------------------------- 1 | import Tag from "../../models/Tag"; 2 | import AppError from "../../errors/AppError"; 3 | 4 | const DeleteAllService = async (): Promise => { 5 | await Tag.findAll(); 6 | 7 | if (!Tag) { 8 | throw new AppError("ERR_NO_TAG_FOUND", 404); 9 | } 10 | 11 | await Tag.destroy({ where: {} }); 12 | }; 13 | 14 | export default DeleteAllService; 15 | -------------------------------------------------------------------------------- /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/ListService.ts: -------------------------------------------------------------------------------- 1 | import { Op } from "sequelize"; 2 | import Tag from "../../models/Tag"; 3 | import ContactTag from "../../models/ContactTag"; 4 | import Contact from "../../models/Contact"; // Add dados de contacts 5 | 6 | interface Request { 7 | searchParam?: string; 8 | pageNumber?: string | number; 9 | } 10 | 11 | interface Response { 12 | tags: Tag[]; 13 | count: number; 14 | hasMore: boolean; 15 | } 16 | 17 | const ListService = async ({ 18 | searchParam, 19 | pageNumber = "1" 20 | }: Request): Promise => { 21 | let whereCondition = {}; 22 | const limit = 9999; //Aumenta o limite de apresentações dos dados 23 | const offset = limit * (+pageNumber - 1); 24 | 25 | if (searchParam) { 26 | whereCondition = { 27 | [Op.or]: [ 28 | { name: { [Op.like]: `%${searchParam}%` } }, 29 | { color: { [Op.like]: `%${searchParam}%` } } 30 | ] 31 | }; 32 | } 33 | 34 | const { count, rows: tags } = await Tag.findAndCountAll({ 35 | where: whereCondition, 36 | limit, 37 | offset, 38 | order: [["name", "ASC"]], 39 | include: [ 40 | { model: ContactTag, attributes: ["tagId"] }, 41 | { model: Contact, through: { attributes: [] } } //Add funcition que busca também o contato vinculado a tag 42 | ] 43 | }); 44 | 45 | const hasMore = count > offset + tags.length; 46 | 47 | return { 48 | tags, 49 | count, 50 | hasMore 51 | }; 52 | }; 53 | 54 | export default ListService; 55 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/ShowService.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from "sequelize"; 2 | import Tag from "../../models/Tag"; 3 | import AppError from "../../errors/AppError"; 4 | import Contact from "../../models/Contact"; 5 | 6 | const TagService = async (id: string | number): Promise => { 7 | const tag = await Tag.findByPk(id, { 8 | attributes: { 9 | include: [ 10 | [Sequelize.fn("COUNT", Sequelize.col("contacts.id")), "contactsCount"] 11 | ] 12 | }, 13 | group: [ 14 | "Tag.id", 15 | "contacts.ContactTag.tagId", 16 | "contacts.ContactTag.contactId", 17 | "contacts.ContactTag.createdAt", 18 | "contacts.ContactTag.updatedAt" 19 | ], 20 | include: [ 21 | { 22 | model: Contact, 23 | as: "contacts", 24 | attributes: [] 25 | } 26 | ] 27 | }); 28 | 29 | if (!tag) { 30 | throw new AppError("ERR_NO_TAG_FOUND", 404); 31 | } 32 | 33 | return tag; 34 | }; 35 | 36 | export default TagService; 37 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/SimpleListService.ts: -------------------------------------------------------------------------------- 1 | import { Op, Sequelize } from "sequelize"; 2 | import Tag from "../../models/Tag"; 3 | import Contact from "../../models/Contact"; 4 | 5 | interface Request { 6 | searchParam?: string; 7 | } 8 | 9 | const ListService = async ({ searchParam }: Request): Promise => { 10 | let whereCondition = {}; 11 | 12 | if (searchParam) { 13 | whereCondition = { 14 | [Op.or]: [ 15 | { name: { [Op.like]: `%${searchParam}%` } }, 16 | { color: { [Op.like]: `%${searchParam}%` } } 17 | ] 18 | }; 19 | } 20 | 21 | const tags = await Tag.findAll({ 22 | where: whereCondition, 23 | order: [["name", "ASC"]], 24 | include: [ 25 | { 26 | model: Contact, 27 | as: "contacts" 28 | } 29 | ], 30 | attributes: { 31 | exclude: ["createdAt", "updatedAt"], 32 | include: [ 33 | [Sequelize.fn("COUNT", Sequelize.col("contacts.id")), "contactsCount"] 34 | ] 35 | }, 36 | group: [ 37 | "Tag.id", 38 | "contacts.ContactTag.tagId", 39 | "contacts.ContactTag.contactId", 40 | "contacts.ContactTag.createdAt", 41 | "contacts.ContactTag.updatedAt", 42 | "contacts.id" 43 | ] 44 | }); 45 | 46 | return tags; 47 | }; 48 | 49 | export default ListService; 50 | -------------------------------------------------------------------------------- /backend/src/services/TagServices/SyncTagsService.ts: -------------------------------------------------------------------------------- 1 | import Tag from "../../models/Tag"; 2 | import Contact from "../../models/Contact"; 3 | import ContactTag from "../../models/ContactTag"; 4 | 5 | interface Request { 6 | tags: Tag[]; 7 | contactId: number; 8 | } 9 | 10 | const SyncTags = async ({ 11 | tags, 12 | contactId 13 | }: Request): Promise => { 14 | const contact = await Contact.findByPk(contactId, { include: [Tag] }); 15 | 16 | const tagList = tags.map(t => ({ tagId: t.id, contactId })); 17 | 18 | await ContactTag.destroy({ where: { contactId } }); 19 | await ContactTag.bulkCreate(tagList); 20 | 21 | contact?.reload(); 22 | 23 | return contact; 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 { name, color } = tagData; 29 | 30 | try { 31 | await schema.validate({ name }); 32 | } catch (err: any) { 33 | throw new AppError(err.message); 34 | } 35 | 36 | await tag.update({ 37 | name, 38 | color 39 | }); 40 | 41 | await tag.reload(); 42 | return tag; 43 | }; 44 | 45 | export default UpdateUserService; 46 | -------------------------------------------------------------------------------- /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/FindOrCreateATicketTrakingService.ts: -------------------------------------------------------------------------------- 1 | import TicketTraking from "../../models/TicketTraking"; 2 | 3 | interface Params { 4 | ticketId: string | number; 5 | whatsappId?: string | number; 6 | userId?: string | number; 7 | } 8 | 9 | const FindOrCreateATicketTrakingService = async ({ 10 | ticketId, 11 | whatsappId, 12 | userId 13 | }: Params): Promise => { 14 | 15 | const ticketTraking = await TicketTraking.findOne({ 16 | where: { 17 | ticketId, 18 | finishedAt: null 19 | } 20 | }); 21 | 22 | if (ticketTraking) { 23 | return ticketTraking; 24 | } 25 | 26 | const newRecord = await TicketTraking.create({ 27 | ticketId, 28 | whatsappId, 29 | userId 30 | }); 31 | 32 | return newRecord; 33 | }; 34 | 35 | export default FindOrCreateATicketTrakingService; 36 | -------------------------------------------------------------------------------- /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 | 8 | const ShowTicketService = async (id: string | number): Promise => { 9 | const ticket = await Ticket.findByPk(id, { 10 | include: [ 11 | { 12 | model: Contact, 13 | as: "contact", 14 | attributes: ["id", "name", "number", "email", "profilePicUrl"], 15 | include: ["extraInfo", "tags"] 16 | }, 17 | { 18 | model: User, 19 | as: "user", 20 | attributes: ["id", "name"] 21 | }, 22 | { 23 | model: Queue, 24 | as: "queue", 25 | attributes: ["id", "name", "color"] 26 | }, 27 | { 28 | model: Whatsapp, 29 | as: "whatsapp", 30 | attributes: ["name"] 31 | } 32 | ] 33 | }); 34 | 35 | if (!ticket) { 36 | throw new AppError("ERR_NO_TICKET_FOUND", 404); 37 | } 38 | 39 | return ticket; 40 | }; 41 | 42 | export default ShowTicketService; 43 | -------------------------------------------------------------------------------- /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: [ 9 | "name", 10 | "id", 11 | "email", 12 | "profile", 13 | "tokenVersion", 14 | "whatsappId", 15 | "allHistoric", 16 | "isRemoveTags", 17 | "viewConection", 18 | "viewSector", 19 | "viewName", 20 | "viewTags", 21 | "allTicket", 22 | "startWork", 23 | "endWork" 24 | ], 25 | include: [ 26 | { model: Queue, as: "queues", attributes: ["id", "name", "color"] }, 27 | { model: Whatsapp, as: "whatsapp", attributes: ["id", "name"] } 28 | ], 29 | order: [[{ model: Queue, as: "queues" }, "name", "asc"]] 30 | }); 31 | if (!user) { 32 | throw new AppError("ERR_NO_USER_FOUND", 404); 33 | } 34 | 35 | return user; 36 | }; 37 | 38 | export default ShowUserService; 39 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/CheckIsValidContact.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 3 | import { getWbot } from "../../libs/wbot"; 4 | 5 | const CheckIsValidContact = async (number: string): Promise => { 6 | const defaultWhatsapp = await GetDefaultWhatsApp(); 7 | 8 | const wbot = getWbot(defaultWhatsapp.id); 9 | 10 | try { 11 | const isValidNumber = await wbot.isRegisteredUser(`${number}@c.us`); 12 | if (!isValidNumber) { 13 | throw new AppError("invalidNumber"); 14 | } 15 | } catch (err) { 16 | if (err.message === "invalidNumber") { 17 | throw new AppError("ERR_WAPP_INVALID_CONTACT"); 18 | } 19 | throw new AppError("ERR_WAPP_CHECK_CONTACT"); 20 | } 21 | }; 22 | 23 | export default CheckIsValidContact; 24 | -------------------------------------------------------------------------------- /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 | 9 | const validNumber : any = await wbot.getNumberId(`${number}@c.us`); 10 | return validNumber.user 11 | }; 12 | 13 | export default CheckContactNumber; 14 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/DeleteWhatsAppMessage.ts: -------------------------------------------------------------------------------- 1 | import AppError from "../../errors/AppError"; 2 | import GetWbotMessage from "../../helpers/GetWbotMessage"; 3 | import Message from "../../models/Message"; 4 | import Ticket from "../../models/Ticket"; 5 | 6 | const DeleteWhatsAppMessage = async (messageId: string): Promise => { 7 | const message = await Message.findByPk(messageId, { 8 | include: [ 9 | { 10 | model: Ticket, 11 | as: "ticket", 12 | include: ["contact"] 13 | } 14 | ] 15 | }); 16 | 17 | if (!message) { 18 | throw new AppError("No message found with this ID."); 19 | } 20 | 21 | const { ticket } = message; 22 | 23 | const messageToDelete = await GetWbotMessage(ticket, messageId); 24 | 25 | try { 26 | await messageToDelete.delete(true); 27 | } catch (err) { 28 | throw new AppError("ERR_DELETE_WAPP_MSG"); 29 | } 30 | 31 | await message.update({ isDeleted: true }); 32 | 33 | return message; 34 | }; 35 | 36 | export default DeleteWhatsAppMessage; 37 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/GetProfilePicUrl.ts: -------------------------------------------------------------------------------- 1 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 2 | import { getWbot } from "../../libs/wbot"; 3 | 4 | const GetProfilePicUrl = async (number: string): Promise => { 5 | const defaultWhatsapp = await GetDefaultWhatsApp(); 6 | 7 | const wbot = getWbot(defaultWhatsapp.id); 8 | 9 | const profilePicUrl = await wbot.getProfilePicUrl(`${number}@c.us`); 10 | 11 | return profilePicUrl; 12 | }; 13 | 14 | export default GetProfilePicUrl; 15 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/ImportContactsService.ts: -------------------------------------------------------------------------------- 1 | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; 2 | import { getWbot } from "../../libs/wbot"; 3 | import Contact from "../../models/Contact"; 4 | import { logger } from "../../utils/logger"; 5 | 6 | const ImportContactsService = async (userId:number): Promise => { 7 | const defaultWhatsapp = await GetDefaultWhatsApp(userId); 8 | 9 | const wbot = getWbot(defaultWhatsapp.id); 10 | 11 | let phoneContacts; 12 | 13 | try { 14 | phoneContacts = await wbot.getContacts(); 15 | } catch (err) { 16 | logger.error(`Could not get whatsapp contacts from phone. Err: ${err}`); 17 | } 18 | 19 | if (phoneContacts) { 20 | await Promise.all( 21 | phoneContacts.map(async ({ number, name }) => { 22 | if (!number) { 23 | return null; 24 | } 25 | if (!name) { 26 | name = number; 27 | } 28 | 29 | const numberExists = await Contact.findOne({ 30 | where: { number } 31 | }); 32 | 33 | if (numberExists) return null; 34 | 35 | return Contact.create({ number, name }); 36 | }) 37 | ); 38 | } 39 | }; 40 | 41 | export default ImportContactsService; -------------------------------------------------------------------------------- /backend/src/services/WbotServices/SendWhatsAppMedia.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js"; 3 | import AppError from "../../errors/AppError"; 4 | import GetTicketWbot from "../../helpers/GetTicketWbot"; 5 | import Ticket from "../../models/Ticket"; 6 | 7 | import formatBody from "../../helpers/Mustache"; 8 | 9 | interface Request { 10 | media: Express.Multer.File; 11 | ticket: Ticket; 12 | body?: string; 13 | } 14 | 15 | const SendWhatsAppMedia = async ({ 16 | media, 17 | ticket, 18 | body 19 | }: Request): Promise => { 20 | try { 21 | const wbot = await GetTicketWbot(ticket); 22 | const hasBody = body 23 | ? formatBody(body as string, ticket) 24 | : undefined; 25 | 26 | const newMedia = MessageMedia.fromFilePath(media.path); 27 | const sentMessage = await wbot.sendMessage( 28 | `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, 29 | newMedia, 30 | { 31 | caption: hasBody, 32 | sendAudioAsVoice: true 33 | } 34 | ); 35 | 36 | await ticket.update({ lastMessage: body || media.filename }); 37 | 38 | fs.unlinkSync(media.path); 39 | 40 | return sentMessage; 41 | } catch (err) { 42 | console.log(err); 43 | throw new AppError("ERR_SENDING_WAPP_MSG"); 44 | } 45 | }; 46 | 47 | export default SendWhatsAppMedia; 48 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/SendWhatsAppMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message as WbotMessage } from "whatsapp-web.js"; 2 | import AppError from "../../errors/AppError"; 3 | import GetTicketWbot from "../../helpers/GetTicketWbot"; 4 | import GetWbotMessage from "../../helpers/GetWbotMessage"; 5 | import SerializeWbotMsgId from "../../helpers/SerializeWbotMsgId"; 6 | import Message from "../../models/Message"; 7 | import Ticket from "../../models/Ticket"; 8 | 9 | import formatBody from "../../helpers/Mustache"; 10 | 11 | interface Request { 12 | body: string; 13 | ticket: Ticket; 14 | quotedMsg?: Message; 15 | } 16 | 17 | const SendWhatsAppMessage = async ({ 18 | body, 19 | ticket, 20 | quotedMsg 21 | }: Request): Promise => { 22 | let quotedMsgSerializedId: string | undefined; 23 | if (quotedMsg) { 24 | await GetWbotMessage(ticket, quotedMsg.id); 25 | quotedMsgSerializedId = SerializeWbotMsgId(ticket, quotedMsg); 26 | } 27 | 28 | const wbot = await GetTicketWbot(ticket); 29 | 30 | try { 31 | const sentMessage = await wbot.sendMessage( 32 | `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, 33 | formatBody(body, ticket), 34 | { 35 | quotedMessageId: quotedMsgSerializedId, 36 | linkPreview: false 37 | } 38 | ); 39 | 40 | await ticket.update({ lastMessage: body }); 41 | return sentMessage; 42 | } catch (err) { 43 | throw new AppError("ERR_SENDING_WAPP_MSG"); 44 | } 45 | }; 46 | 47 | export default SendWhatsAppMessage; -------------------------------------------------------------------------------- /backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts: -------------------------------------------------------------------------------- 1 | import ListWhatsAppsService from "../WhatsappService/ListWhatsAppsService"; 2 | import { StartWhatsAppSession } from "./StartWhatsAppSession"; 3 | 4 | export const StartAllWhatsAppsSessions = async (): Promise => { 5 | const whatsapps = await ListWhatsAppsService(); 6 | if (whatsapps.length > 0) { 7 | whatsapps.forEach(whatsapp => { 8 | StartWhatsAppSession(whatsapp); 9 | }); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /backend/src/services/WbotServices/StartWhatsAppSession.ts: -------------------------------------------------------------------------------- 1 | import { initWbot } from "../../libs/wbot"; 2 | import Whatsapp from "../../models/Whatsapp"; 3 | import { wbotMessageListener } from "./wbotMessageListener"; 4 | import { getIO } from "../../libs/socket"; 5 | import wbotMonitor from "./wbotMonitor"; 6 | import { logger } from "../../utils/logger"; 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 | 4 | const DeleteWhatsAppService = async (id: string): Promise => { 5 | const whatsapp = await Whatsapp.findOne({ 6 | where: { id } 7 | }); 8 | 9 | if (!whatsapp) { 10 | throw new AppError("ERR_NO_WAPP_FOUND", 404); 11 | } 12 | 13 | await whatsapp.destroy(); 14 | }; 15 | 16 | export default DeleteWhatsAppService; -------------------------------------------------------------------------------- /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", "startWork", "endWork", "absenceMessage"] 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 | 5 | const ShowWhatsAppService = async (id: string | number): Promise => { 6 | const whatsapp = await Whatsapp.findByPk(id, { 7 | include: [ 8 | { 9 | model: Queue, 10 | as: "queues", 11 | attributes: ["id", "name", "color", "greetingMessage", "startWork", "endWork", "absenceMessage"] 12 | } 13 | ], 14 | order: [["queues", "name", "ASC"]] 15 | }); 16 | 17 | if (!whatsapp) { 18 | throw new AppError("ERR_NO_WAPP_FOUND", 404); 19 | } 20 | 21 | return whatsapp; 22 | }; 23 | 24 | export default ShowWhatsAppService; 25 | -------------------------------------------------------------------------------- /backend/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | const logger = pino({ 4 | transport: { 5 | target: "pino-pretty", 6 | options: { 7 | levelFirst: true, 8 | translateTime: true, 9 | colorize: true 10 | } 11 | } 12 | }); 13 | 14 | export { logger }; -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "strict": true, 7 | "useUnknownInCatchVariables": false, 8 | "strictPropertyInitialization": false, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/INSTALL_horarioVPS.md: -------------------------------------------------------------------------------- 1 | # Alterar o horário da VPS pelo terminal 2 | 3 | OBS: O Tutorial abaixo deve ser usado em casos onde alguma implementação que dependa de horário não esteja funcionando corretamente devido ao fuso horário da VPS. 4 | 5 | ================================================ 6 | 7 | 1. Verificar a hora atual da VPS pelo terminal 8 | 9 | ```bash 10 | date 11 | ``` 12 | 13 | 2. Comando para alterar o fuso horário da VPS 14 | 15 | ```bash 16 | sudo dpkg-reconfigure tzdata 17 | ``` 18 | 19 | 3. Iniciará uma tela de configuração do fuso horário onde deverá escolher ```America``` e selecionar ok 20 | 21 | 4. Nesta tela deverá a cidade correspondente ao seu fuso horário, no meu caso usarei ```Sao_Paulo``` 22 | 23 | 5. Agora será exibido um log similar a este informando que o fuso horário foi alterado com sucesso 24 | 25 | ```Current default time zone: 'America/Sao_Paulo'``` 26 | ```Local time is now: Tue Mar 20 08:21:57 -03 2018.``` 27 | ```Universal Time is now: Tue Mar 20 11:21:57 UTC 2018.``` -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_BACKEND_URL=http://localhost:8080 2 | SERVER_PORT=3333 3 | REACT_APP_PAGE_TITLE=mktsend -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | /dist 15 | /src/config.json 16 | 17 | # misc 18 | .DS_Store 19 | .env 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | package-lock.json 29 | yarn.lock 30 | build -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/default-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/default-profile.png -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Atendimento - mktsend 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mkthub", 3 | "name": "Mkthub", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/server.js: -------------------------------------------------------------------------------- 1 | //simple express server to run frontend production build; 2 | const express = require("express"); 3 | const path = require("path"); 4 | const app = express(); 5 | require('dotenv').config(); 6 | 7 | app.use(express.static(path.join(__dirname, "build"))); 8 | app.get("/*", function (req, res) { 9 | res.sendFile(path.join(__dirname, "build", "index.html")); 10 | }); 11 | 12 | app.listen(process.env.PORT || 3000); -------------------------------------------------------------------------------- /frontend/src/assets/Logo_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/Logo_circle.png -------------------------------------------------------------------------------- /frontend/src/assets/Logo_circle_mkthub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/Logo_circle_mkthub.png -------------------------------------------------------------------------------- /frontend/src/assets/logo-dash-mkthub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/logo-dash-mkthub.png -------------------------------------------------------------------------------- /frontend/src/assets/logo-dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/logo-dash.png -------------------------------------------------------------------------------- /frontend/src/assets/logo-mktshub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/logo-mktshub.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/receive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/receive.png -------------------------------------------------------------------------------- /frontend/src/assets/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/send.png -------------------------------------------------------------------------------- /frontend/src/assets/sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/sound.mp3 -------------------------------------------------------------------------------- /frontend/src/assets/sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/sound.ogg -------------------------------------------------------------------------------- /frontend/src/assets/wa-background-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/wa-background-dark.jpg -------------------------------------------------------------------------------- /frontend/src/assets/wa-background-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipeCamillo/Press-Ticket/df614c1ce3bec9f9be808298da4498fad8bc004b/frontend/src/assets/wa-background-light.png -------------------------------------------------------------------------------- /frontend/src/components/BackdropLoading/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Backdrop from "@material-ui/core/Backdrop"; 4 | import CircularProgress from "@material-ui/core/CircularProgress"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | backdrop: { 9 | zIndex: theme.zIndex.drawer + 1, 10 | color: "#fff", 11 | }, 12 | })); 13 | 14 | const BackdropLoading = () => { 15 | const classes = useStyles(); 16 | return ( 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default BackdropLoading; 24 | -------------------------------------------------------------------------------- /frontend/src/components/ButtonWithSpinner/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import { green } from "@material-ui/core/colors"; 5 | import { CircularProgress, Button } from "@material-ui/core"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | button: { 9 | position: "relative", 10 | }, 11 | 12 | buttonProgress: { 13 | color: green[500], 14 | position: "absolute", 15 | top: "50%", 16 | left: "50%", 17 | marginTop: -12, 18 | marginLeft: -12, 19 | }, 20 | })); 21 | 22 | const ButtonWithSpinner = ({ loading, children, ...rest }) => { 23 | const classes = useStyles(); 24 | 25 | return ( 26 | 32 | ); 33 | }; 34 | 35 | export default ButtonWithSpinner; 36 | -------------------------------------------------------------------------------- /frontend/src/components/Can/index.js: -------------------------------------------------------------------------------- 1 | import rules from "../../rules"; 2 | 3 | const check = (role, action, data) => { 4 | const permissions = rules[role]; 5 | if (!permissions) { 6 | // role is not present in the rules 7 | return false; 8 | } 9 | 10 | const staticPermissions = permissions.static; 11 | 12 | if (staticPermissions && staticPermissions.includes(action)) { 13 | // static rule not provided for action 14 | return true; 15 | } 16 | 17 | const dynamicPermissions = permissions.dynamic; 18 | 19 | if (dynamicPermissions) { 20 | const permissionCondition = dynamicPermissions[action]; 21 | if (!permissionCondition) { 22 | // dynamic rule not provided for action 23 | return false; 24 | } 25 | 26 | return permissionCondition(data); 27 | } 28 | return false; 29 | }; 30 | 31 | const Can = ({ role, perform, data, yes, no }) => 32 | check(role, perform, data) ? yes() : no(); 33 | 34 | Can.defaultProps = { 35 | yes: () => null, 36 | no: () => null, 37 | }; 38 | 39 | export { Can }; 40 | -------------------------------------------------------------------------------- /frontend/src/components/ConfirmationModal/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import Dialog from "@material-ui/core/Dialog"; 4 | import DialogActions from "@material-ui/core/DialogActions"; 5 | import DialogContent from "@material-ui/core/DialogContent"; 6 | import DialogTitle from "@material-ui/core/DialogTitle"; 7 | import Typography from "@material-ui/core/Typography"; 8 | 9 | import { i18n } from "../../translate/i18n"; 10 | 11 | const ConfirmationModal = ({ title, children, open, onClose, onConfirm }) => { 12 | return ( 13 | onClose(false)} 16 | aria-labelledby="confirm-dialog" 17 | > 18 | {title} 19 | 20 | {children} 21 | 22 | 23 | 30 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default ConfirmationModal; 46 | -------------------------------------------------------------------------------- /frontend/src/components/ContactDrawer/ModalImage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | 4 | import ModalImage from "react-modal-image"; 5 | import api from "../../services/api"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | messageMedia: { 9 | objectFit: "cover", 10 | margin: 15, 11 | width: 160, 12 | height: 160, 13 | borderRadius: 10, 14 | }, 15 | })); 16 | 17 | const ModalImageContatc = ({ imageUrl }) => { 18 | const classes = useStyles(); 19 | const [fetching, setFetching] = useState(true); 20 | const [blobUrl, setBlobUrl] = useState(""); 21 | 22 | useEffect(() => { 23 | if (!imageUrl) return; 24 | const fetchImage = async () => { 25 | const { data, headers } = await api.get(imageUrl, { 26 | responseType: "blob", 27 | }); 28 | const url = window.URL.createObjectURL( 29 | new Blob([data], { type: headers["content-type"] }) 30 | ); 31 | setBlobUrl(url); 32 | setFetching(false); 33 | }; 34 | fetchImage(); 35 | }, [imageUrl]); 36 | 37 | return ( 38 | 46 | ); 47 | }; 48 | 49 | 50 | export default ModalImageContatc; 51 | -------------------------------------------------------------------------------- /frontend/src/components/ContactTag/index.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from "@material-ui/styles"; 2 | import React from "react"; 3 | 4 | const useStyles = makeStyles(theme => ({ 5 | tag: { 6 | padding: "1px 5px", 7 | borderRadius: "3px", 8 | fontSize: "0.8em", 9 | fontWeight: "bold", 10 | color: "#FFF", 11 | marginRight: "5px", 12 | marginBottom: "3px", 13 | whiteSpace: "nowrap", 14 | } 15 | })); 16 | 17 | const ContactTag = ({ tag }) => { 18 | const classes = useStyles(); 19 | 20 | return ( 21 |
22 | {tag.name.toUpperCase()} 23 |
24 | ) 25 | } 26 | 27 | export default ContactTag; -------------------------------------------------------------------------------- /frontend/src/components/CopyToClipboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | IconButton, 4 | Tooltip 5 | } from "@material-ui/core"; 6 | import { FileCopyOutlined } from "@material-ui/icons"; 7 | import { i18n } from "../../translate/i18n"; 8 | 9 | const CopyToClipboard = ({ content, color }) => { 10 | const [tooltipMessage, setTooltipMessage] = useState( 11 | i18n.t("copyToClipboard.copy") 12 | ); 13 | 14 | const handleCopyToClipboard = () => { 15 | navigator.clipboard.writeText(content); 16 | setTooltipMessage(i18n.t("copyToClipboard.copied")); 17 | }; 18 | 19 | const handleCloseTooltip = () => { 20 | setTooltipMessage(i18n.t("copyToClipboard.copy")); 21 | }; 22 | 23 | return ( 24 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default CopyToClipboard; -------------------------------------------------------------------------------- /frontend/src/components/FormikTextField/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TextField } from "@material-ui/core"; 3 | import { Field } from "formik"; 4 | 5 | const FormikTextField = ({ 6 | name, 7 | values, 8 | touched, 9 | errors, 10 | isSubmitting, 11 | loading, 12 | ...rest 13 | }) => { 14 | return ( 15 | 22 | ); 23 | }; 24 | 25 | export default FormikTextField; -------------------------------------------------------------------------------- /frontend/src/components/MainContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Container from "@material-ui/core/Container"; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | mainContainer: { 8 | flex: 1, 9 | // padding: theme.spacing(2), 10 | // height: `calc(100% - 48px)`, 11 | padding: 0, 12 | height: "92%", 13 | }, 14 | 15 | contentWrapper: { 16 | height: "100%", 17 | overflowY: "hidden", 18 | display: "flex", 19 | flexDirection: "column", 20 | }, 21 | })); 22 | 23 | const MainContainer = ({ children }) => { 24 | const classes = useStyles(); 25 | 26 | return ( 27 | 28 |
{children}
29 |
30 | ); 31 | }; 32 | 33 | export default MainContainer; 34 | -------------------------------------------------------------------------------- /frontend/src/components/MainHeader/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | contactsHeader: { 7 | display: "flex", 8 | alignItems: "center", 9 | padding: "0px 6px 6px 6px", 10 | }, 11 | })); 12 | 13 | const MainHeader = ({ children }) => { 14 | const classes = useStyles(); 15 | 16 | return
{children}
; 17 | }; 18 | 19 | export default MainHeader; 20 | -------------------------------------------------------------------------------- /frontend/src/components/MainHeaderButtonsWrapper/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | MainHeaderButtonsWrapper: { 7 | display: "flex", 8 | marginLeft: "auto", 9 | "& > *": { 10 | margin: theme.spacing(1), 11 | }, 12 | }, 13 | })); 14 | 15 | const MainHeaderButtonsWrapper = ({ children }) => { 16 | const classes = useStyles(); 17 | 18 | return
{children}
; 19 | }; 20 | 21 | export default MainHeaderButtonsWrapper; 22 | -------------------------------------------------------------------------------- /frontend/src/components/MessageInput/RecordingTimer.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | 4 | const useStyles = makeStyles(theme => ({ 5 | timerBox: { 6 | display: "flex", 7 | marginLeft: 10, 8 | marginRight: 10, 9 | alignItems: "center", 10 | }, 11 | })); 12 | 13 | const RecordingTimer = () => { 14 | const classes = useStyles(); 15 | const initialState = { 16 | minutes: 0, 17 | seconds: 0, 18 | }; 19 | const [timer, setTimer] = useState(initialState); 20 | 21 | useEffect(() => { 22 | const interval = setInterval( 23 | () => 24 | setTimer(prevState => { 25 | if (prevState.seconds === 59) { 26 | return { ...prevState, minutes: prevState.minutes + 1, seconds: 0 }; 27 | } 28 | return { ...prevState, seconds: prevState.seconds + 1 }; 29 | }), 30 | 1000 31 | ); 32 | return () => { 33 | clearInterval(interval); 34 | }; 35 | }, []); 36 | 37 | const addZero = n => { 38 | return n < 10 ? "0" + n : n; 39 | }; 40 | 41 | return ( 42 |
43 | {`${addZero(timer.minutes)}:${addZero(timer.seconds)}`} 44 |
45 | ); 46 | }; 47 | 48 | export default RecordingTimer; 49 | -------------------------------------------------------------------------------- /frontend/src/components/ModalImageCors/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | 4 | import ModalImage from "react-modal-image"; 5 | import api from "../../services/api"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | messageMedia: { 9 | objectFit: "cover", 10 | width: 250, 11 | height: 200, 12 | borderTopLeftRadius: 8, 13 | borderTopRightRadius: 8, 14 | borderBottomLeftRadius: 8, 15 | borderBottomRightRadius: 8, 16 | }, 17 | })); 18 | 19 | const ModalImageCors = ({ imageUrl }) => { 20 | const classes = useStyles(); 21 | const [fetching, setFetching] = useState(true); 22 | const [blobUrl, setBlobUrl] = useState(""); 23 | 24 | useEffect(() => { 25 | if (!imageUrl) return; 26 | const fetchImage = async () => { 27 | const { data, headers } = await api.get(imageUrl, { 28 | responseType: "blob", 29 | }); 30 | const url = window.URL.createObjectURL( 31 | new Blob([data], { type: headers["content-type"] }) 32 | ); 33 | setBlobUrl(url); 34 | setFetching(false); 35 | }; 36 | fetchImage(); 37 | }, [imageUrl]); 38 | 39 | return ( 40 | 48 | ); 49 | }; 50 | 51 | export default ModalImageCors; -------------------------------------------------------------------------------- /frontend/src/components/OutlinedDiv/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TextField from "@material-ui/core/TextField"; 4 | 5 | const InputComponent = ({ inputRef, ...other }) =>
; 6 | 7 | const OutlinedDiv = ({ 8 | InputProps, 9 | children, 10 | InputLabelProps, 11 | label, 12 | ...other 13 | }) => { 14 | return ( 15 | 27 | ); 28 | }; 29 | 30 | export default OutlinedDiv; -------------------------------------------------------------------------------- /frontend/src/components/TabPanel/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TabPanel = ({ children, value, name, ...rest }) => { 4 | if (value === name) { 5 | return ( 6 |
12 | <>{children} 13 |
14 | ); 15 | } else return null; 16 | }; 17 | 18 | export default TabPanel; 19 | -------------------------------------------------------------------------------- /frontend/src/components/TableRowSkeleton/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TableCell from "@material-ui/core/TableCell"; 3 | import TableRow from "@material-ui/core/TableRow"; 4 | import Skeleton from "@material-ui/lab/Skeleton"; 5 | import { makeStyles } from "@material-ui/core"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | customTableCell: { 9 | display: "flex", 10 | alignItems: "center", 11 | justifyContent: "center", 12 | }, 13 | })); 14 | 15 | const TableRowSkeleton = ({ avatar, columns }) => { 16 | const classes = useStyles(); 17 | return ( 18 | <> 19 | 20 | {avatar && ( 21 | <> 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | )} 35 | {Array.from({ length: columns }, (_, index) => ( 36 | 37 |
38 | 44 |
45 |
46 | ))} 47 |
48 | 49 | ); 50 | }; 51 | 52 | export default TableRowSkeleton; 53 | -------------------------------------------------------------------------------- /frontend/src/components/TicketHeaderSkeleton/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import { Avatar, Card, CardHeader } from "@material-ui/core"; 5 | import Skeleton from "@material-ui/lab/Skeleton"; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | ticketHeader: { 9 | display: "flex", 10 | backgroundColor: "#eee", 11 | flex: "none", 12 | borderBottom: "1px solid rgba(0, 0, 0, 0.12)", 13 | }, 14 | })); 15 | 16 | const TicketHeaderSkeleton = () => { 17 | const classes = useStyles(); 18 | 19 | return ( 20 | 21 | 26 | 27 | 28 | } 29 | title={} 30 | subheader={} 31 | /> 32 | 33 | ); 34 | }; 35 | 36 | export default TicketHeaderSkeleton; 37 | -------------------------------------------------------------------------------- /frontend/src/components/TicketInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Avatar, CardHeader } from "@material-ui/core"; 4 | 5 | //import { i18n } from "../../translate/i18n"; 6 | 7 | const TicketInfo = ({ contact, ticket, onClick }) => { 8 | return ( 9 | } 15 | title={`${contact.name}`} 16 | subheader={ 17 | ticket.user && 18 | `Chamado Nº ${ticket.id}` 19 | } 20 | /> 21 | ); 22 | }; 23 | 24 | export default TicketInfo; -------------------------------------------------------------------------------- /frontend/src/components/Title/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Typography from "@material-ui/core/Typography"; 3 | 4 | export default function Title(props) { 5 | return ( 6 | 7 | {props.children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/WithSkeleton/index.js: -------------------------------------------------------------------------------- 1 | import Skeleton from "@material-ui/lab/Skeleton"; 2 | import React from "react"; 3 | 4 | const WithSkeleton = ({ loading, children, fullWidth }) => { 5 | return ( 6 | <> 7 | {loading ? ( 8 | {children} 9 | ) : ( 10 | <>{children} 11 | )} 12 | 13 | ); 14 | }; 15 | 16 | export default WithSkeleton; -------------------------------------------------------------------------------- /frontend/src/config.js: -------------------------------------------------------------------------------- 1 | function getConfig(name, defaultValue=null) { 2 | // If inside a docker container, use window.ENV 3 | if( window.ENV !== undefined ) { 4 | return window.ENV[name] || defaultValue; 5 | } 6 | 7 | return process.env[name] || defaultValue; 8 | } 9 | 10 | export function getBackendUrl() { 11 | return getConfig('REACT_APP_BACKEND_URL'); 12 | } 13 | 14 | // export function getHoursCloseTicketsAuto() { 15 | // return getConfig('REACT_APP_HOURS_CLOSE_TICKETS_AUTO'); 16 | // } -------------------------------------------------------------------------------- /frontend/src/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "name": "Mktsend", 4 | "url": "https://mkthub.com.br", 5 | "color": { 6 | "lightTheme": { 7 | "palette": { 8 | "primary": "#F04405", 9 | "secondary": "#0D0D0D" 10 | }, 11 | "toolbar": { 12 | "background": "linear-gradient(to right, #F04405, #F04405)" 13 | }, 14 | "menuItens": "#ffffff", 15 | "sub": "#ffffff", 16 | "toolbarIcon": "#ffffff", 17 | "divide": "#E0E0E0" 18 | }, 19 | "darkTheme": { 20 | "palette": { 21 | "primary": "#F04405", 22 | "secondary": "#ff9100", 23 | "background": { 24 | "default": "#080d14", 25 | "paper": "#181d22" 26 | }, 27 | "text": { 28 | "primary": "#F2F0E4", 29 | "secondary": "#F2F0E4" 30 | } 31 | }, 32 | "toolbar": { 33 | "background": "linear-gradient(to right, #F04405, #F04405)" 34 | }, 35 | "menuItens": "#181d22", 36 | "sub": "#181d22", 37 | "toolbarIcon": "#F04405", 38 | "divide": "#080d14" 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /frontend/src/context/Auth/AuthContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext } from "react"; 2 | 3 | import useAuth from "../../hooks/useAuth.js"; 4 | 5 | const AuthContext = createContext(); 6 | 7 | const AuthProvider = ({ children }) => { 8 | const { loading, user, isAuth, handleLogin, handleLogout } = useAuth(); 9 | 10 | return ( 11 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | export { AuthContext, AuthProvider }; 20 | -------------------------------------------------------------------------------- /frontend/src/context/ReplyingMessage/ReplyingMessageContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from "react"; 2 | 3 | const ReplyMessageContext = createContext(); 4 | 5 | const ReplyMessageProvider = ({ children }) => { 6 | const [replyingMessage, setReplyingMessage] = useState(null); 7 | 8 | return ( 9 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export { ReplyMessageContext, ReplyMessageProvider }; 18 | -------------------------------------------------------------------------------- /frontend/src/context/WhatsApp/WhatsAppsContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext } from "react"; 2 | 3 | import useWhatsApps from "../../hooks/useWhatsApps"; 4 | 5 | const WhatsAppsContext = createContext(); 6 | 7 | const WhatsAppsProvider = ({ children }) => { 8 | const { loading, whatsApps } = useWhatsApps(); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export { WhatsAppsContext, WhatsAppsProvider }; 18 | -------------------------------------------------------------------------------- /frontend/src/errors/toastError.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | import { i18n } from "../translate/i18n"; 3 | 4 | const toastError = err => { 5 | const errorMsg = err.response?.data?.message || err.response.data.error; 6 | if (errorMsg) { 7 | if (i18n.exists(`backendErrors.${errorMsg}`)) { 8 | toast.error(i18n.t(`backendErrors.${errorMsg}`), { 9 | toastId: errorMsg, 10 | }); 11 | } else { 12 | toast.error(errorMsg, { 13 | toastId: errorMsg, 14 | }); 15 | } 16 | } else { 17 | toast.error("An error occurred!"); 18 | } 19 | }; 20 | 21 | export default toastError; 22 | -------------------------------------------------------------------------------- /frontend/src/hooks/useDashboard/index.js: -------------------------------------------------------------------------------- 1 | import api from "../../services/api"; 2 | 3 | const useDashboard = () => { 4 | 5 | const find = async (params) => { 6 | const { data } = await api.request({ 7 | url: `/dashboard`, 8 | method: 'GET', 9 | params 10 | }); 11 | return data; 12 | } 13 | 14 | return { 15 | find 16 | } 17 | } 18 | 19 | export default useDashboard; -------------------------------------------------------------------------------- /frontend/src/hooks/useLocalStorage/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import toastError from "../../errors/toastError"; 3 | 4 | export function useLocalStorage(key, initialValue) { 5 | const [storedValue, setStoredValue] = useState(() => { 6 | try { 7 | const item = localStorage.getItem(key); 8 | return item ? JSON.parse(item) : initialValue; 9 | } catch (error) { 10 | toastError(error); 11 | return initialValue; 12 | } 13 | }); 14 | 15 | const setValue = value => { 16 | try { 17 | const valueToStore = 18 | value instanceof Function ? value(storedValue) : value; 19 | 20 | setStoredValue(valueToStore); 21 | 22 | localStorage.setItem(key, JSON.stringify(valueToStore)); 23 | } catch (error) { 24 | toastError(error); 25 | } 26 | }; 27 | 28 | return [storedValue, setValue]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/hooks/useQueues/index.js: -------------------------------------------------------------------------------- 1 | import api from "../../services/api"; 2 | 3 | const useQueues = () => { 4 | const findAll = async () => { 5 | const { data } = await api.get("/queue"); 6 | return data; 7 | } 8 | 9 | return { findAll }; 10 | }; 11 | 12 | export default useQueues; 13 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import CssBaseline from "@material-ui/core/CssBaseline"; 4 | 5 | import App from "./App"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // ReactDOM.render( 15 | // 16 | // 17 | // 18 | // , 19 | // 20 | 21 | // document.getElementById("root") 22 | // ); 23 | -------------------------------------------------------------------------------- /frontend/src/pages/ApiDocs/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import openSocket from "socket.io-client"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | root: { 7 | display: "flex", 8 | alignItems: "center", 9 | padding: theme.spacing(1) 10 | }, 11 | 12 | paper: { 13 | padding: theme.spacing(2), 14 | display: "flex", 15 | alignItems: "center", 16 | }, 17 | 18 | settingOption: { 19 | marginLeft: "auto", 20 | }, 21 | margin: { 22 | margin: theme.spacing(1), 23 | }, 24 | })); 25 | 26 | 27 | const ApiDocs = () => { 28 | const classes = useStyles(); 29 | 30 | useEffect(() => { 31 | const socket = openSocket(process.env.REACT_APP_BACKEND_URL); 32 | return () => { 33 | socket.disconnect(); 34 | }; 35 | }, []); 36 | 37 | const back = process.env.REACT_APP_BACKEND_URL; 38 | const endapi = "/api-docs"; 39 | const urlapi = back.concat(endapi); 40 | 41 | return ( 42 | 43 |
44 |