hiesoftrd 1 rok pred
rodič
commit
947d15ec66
100 zmenil súbory, kde vykonal 6175 pridanie a 0 odobranie
  1. 9 0
      backend/.editorconfig
  2. 36 0
      backend/.env.example
  3. 3 0
      backend/.eslintignore
  4. 49 0
      backend/.eslintrc.json
  5. 16 0
      backend/.gitignore
  6. 8 0
      backend/.sequelizerc
  7. 7 0
      backend/Dockerfile.sqlsetup
  8. 5 0
      backend/build-and-run-docker-sql.sh
  9. 1 0
      backend/certs/coloque_seus_certificado_aqui
  10. 67 0
      backend/docker-compose.databases.yml
  11. 4 0
      backend/exit-docker-sql.sh
  12. 186 0
      backend/jest.config.js
  13. 101 0
      backend/package.json
  14. 5 0
      backend/prettier.config.js
  15. 0 0
      backend/public/.gitkeep
  16. 4 0
      backend/run-docker-sql.sh
  17. 5 0
      backend/src/@types/express.d.ts
  18. 1 0
      backend/src/@types/qrcode-terminal.d.ts
  19. 54 0
      backend/src/app.ts
  20. 5 0
      backend/src/bootstrap.ts
  21. 13 0
      backend/src/config/Gn.ts
  22. 6 0
      backend/src/config/auth.ts
  23. 41 0
      backend/src/config/database.ts
  24. 3 0
      backend/src/config/redis.ts
  25. 39 0
      backend/src/config/upload.ts
  26. 205 0
      backend/src/controllers/AnnouncementController.ts
  27. 362 0
      backend/src/controllers/CampaignController.ts
  28. 34 0
      backend/src/controllers/CampaignSettingController.ts
  29. 205 0
      backend/src/controllers/ChatController.ts
  30. 181 0
      backend/src/controllers/CompanyController.ts
  31. 266 0
      backend/src/controllers/ContactController.ts
  32. 159 0
      backend/src/controllers/ContactListController.ts
  33. 145 0
      backend/src/controllers/ContactListItemController.ts
  34. 42 0
      backend/src/controllers/DashbardController.ts
  35. 153 0
      backend/src/controllers/FilesController.ts
  36. 25 0
      backend/src/controllers/ForgotController.ts
  37. 131 0
      backend/src/controllers/HelpController.ts
  38. 10 0
      backend/src/controllers/ImportPhoneContactsController.ts
  39. 172 0
      backend/src/controllers/InvoicesController.ts
  40. 192 0
      backend/src/controllers/MessageController.ts
  41. 136 0
      backend/src/controllers/PlanController.ts
  42. 114 0
      backend/src/controllers/PromptController.ts
  43. 107 0
      backend/src/controllers/QueueController.ts
  44. 99 0
      backend/src/controllers/QueueIntegrationController.ts
  45. 60 0
      backend/src/controllers/QueueOptionController.ts
  46. 197 0
      backend/src/controllers/QuickMessageController.ts
  47. 146 0
      backend/src/controllers/QuickMessageController_OLD.ts
  48. 154 0
      backend/src/controllers/ScheduleController.ts
  49. 81 0
      backend/src/controllers/SessionController.ts
  50. 45 0
      backend/src/controllers/SettingController.ts
  51. 198 0
      backend/src/controllers/SubscriptionController.ts
  52. 128 0
      backend/src/controllers/TagController.ts
  53. 224 0
      backend/src/controllers/TicketController.ts
  54. 138 0
      backend/src/controllers/TicketNoteController.ts
  55. 57 0
      backend/src/controllers/TicketTagController.ts
  56. 171 0
      backend/src/controllers/UserController.ts
  57. 7 0
      backend/src/controllers/VersionController.ts
  58. 166 0
      backend/src/controllers/WhatsAppController.ts
  59. 46 0
      backend/src/controllers/WhatsAppSessionController.ts
  60. 90 0
      backend/src/database/index.ts
  61. 10 0
      backend/src/database/migrations/20200717133431-add-uuid-ossp.ts
  62. 39 0
      backend/src/database/migrations/20200717133438-create-users.ts
  63. 37 0
      backend/src/database/migrations/20200717144403-create-contacts.ts
  64. 46 0
      backend/src/database/migrations/20200717145643-create-tickets.ts
  65. 58 0
      backend/src/database/migrations/20200717151645-create-messages.ts
  66. 41 0
      backend/src/database/migrations/20200717170223-create-whatsapps.ts
  67. 41 0
      backend/src/database/migrations/20200723200315-create-contacts-custom-fields.ts
  68. 15 0
      backend/src/database/migrations/20200723202116-add-email-field-to-contacts.ts
  69. 16 0
      backend/src/database/migrations/20200730153237-remove-user-association-from-messages.ts
  70. 15 0
      backend/src/database/migrations/20200730153545-add-fromMe-to-messages.ts
  71. 15 0
      backend/src/database/migrations/20200813114236-change-ticket-lastMessage-column-type.ts
  72. 15 0
      backend/src/database/migrations/20200901235509-add-profile-column-to-users.ts
  73. 29 0
      backend/src/database/migrations/20200903215941-create-settings.ts
  74. 15 0
      backend/src/database/migrations/20200904220257-add-name-to-whatsapp.ts
  75. 15 0
      backend/src/database/migrations/20200906122228-add-name-default-field-to-whatsapp.ts
  76. 16 0
      backend/src/database/migrations/20200906155658-add-whatsapp-field-to-tickets.ts
  77. 11 0
      backend/src/database/migrations/20200919124112-update-default-column-name-on-whatsappp.ts
  78. 15 0
      backend/src/database/migrations/20200927220708-add-isDeleted-column-to-messages.ts
  79. 15 0
      backend/src/database/migrations/20200929145451-add-user-tokenVersion-column.ts
  80. 15 0
      backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts
  81. 15 0
      backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts
  82. 16 0
      backend/src/database/migrations/20201004150008-add-contactId-column-to-messages.ts
  83. 16 0
      backend/src/database/migrations/20201004155719-add-vcardContactId-column-to-messages.ts
  84. 16 0
      backend/src/database/migrations/20201004955719-remove-vcardContactId-column-to-messages.ts
  85. 15 0
      backend/src/database/migrations/20201026215410-add-retries-to-whatsapps.ts
  86. 16 0
      backend/src/database/migrations/20201028124427-add-quoted-msg-to-messages.ts
  87. 13 0
      backend/src/database/migrations/20210108001431-add-unreadMessages-to-tickets.ts
  88. 39 0
      backend/src/database/migrations/20210108164404-create-queues.ts
  89. 16 0
      backend/src/database/migrations/20210108164504-add-queueId-to-tickets.ts
  90. 28 0
      backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts
  91. 28 0
      backend/src/database/migrations/20210108204708-associate-users-queue.ts
  92. 13 0
      backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts
  93. 39 0
      backend/src/database/migrations/20210109192514-create-companies-table.ts
  94. 16 0
      backend/src/database/migrations/20210109192515-add-column-companyId-to-Settings-table.ts
  95. 16 0
      backend/src/database/migrations/20210109192516-add-column-companyId-to-Users-table.ts
  96. 16 0
      backend/src/database/migrations/20210109192517-add-column-companyId-to-Contacts-table.ts
  97. 16 0
      backend/src/database/migrations/20210109192518-add-column-companyId-to-Messages-table.ts
  98. 16 0
      backend/src/database/migrations/20210109192519-add-column-companyId-to-Queues-table.ts
  99. 16 0
      backend/src/database/migrations/20210109192520-add-column-companyId-to-Whatsapps-table.ts
  100. 16 0
      backend/src/database/migrations/20210109192521-add-column-companyId-to-Tickets-table.ts

+ 9 - 0
backend/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+end_of_line = lf
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 36 - 0
backend/.env.example

@@ -0,0 +1,36 @@
+NODE_ENV=
+BACKEND_URL=http://localhost
+FRONTEND_URL=http://localhost:3000
+PROXY_PORT=8080
+PORT=8080
+
+DB_DIALECT=postgres
+DB_HOST=localhost
+DB_PORT=5432
+DB_USER=user
+DB_PASS=senha
+DB_NAME=db_name
+
+JWT_SECRET=kZaOTd+YZpjRUyyuQUpigJaEMk4vcW4YOymKPZX0Ts8=
+JWT_REFRESH_SECRET=dBSXqFg9TaNUEDXVp6fhMTRLBysP+j2DSqf7+raxD3A=
+
+REDIS_URI=redis://:123456@127.0.0.1:6379
+REDIS_OPT_LIMITER_MAX=1
+REDIS_OPT_LIMITER_DURATION=3000
+
+USER_LIMIT=10000
+CONNECTIONS_LIMIT=100000
+CLOSED_SEND_BY_ME=true
+
+GERENCIANET_SANDBOX=false
+GERENCIANET_CLIENT_ID=Client_Id_Gerencianet
+GERENCIANET_CLIENT_SECRET=Client_Secret_Gerencianet
+GERENCIANET_PIX_CERT=certificado-Gerencianet
+GERENCIANET_PIX_KEY=chave pix gerencianet
+
+# EMAIL
+ MAIL_HOST="smtp.gmail.com"
+ MAIL_USER="seu@gmail.com"
+ MAIL_PASS="SuaSenha"
+ MAIL_FROM="seu@gmail.com"
+ MAIL_PORT="465"

+ 3 - 0
backend/.eslintignore

@@ -0,0 +1,3 @@
+/*.js
+node_modules
+dist

+ 49 - 0
backend/.eslintrc.json

@@ -0,0 +1,49 @@
+{
+  "env": {
+    "es2021": true,
+    "node": true,
+    "jest": true
+  },
+  "extends": [
+    "airbnb-base",
+    "plugin:@typescript-eslint/recommended",
+    "prettier/@typescript-eslint",
+    "plugin:prettier/recommended"
+  ],
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaVersion": 12,
+    "sourceType": "module"
+  },
+  "plugins": ["@typescript-eslint", "prettier"],
+  "rules": {
+    "@typescript-eslint/no-non-null-assertion": "off",
+    "@typescript-eslint/no-unused-vars": [
+      "error",
+      { "argsIgnorePattern": "_" }
+    ],
+    "import/prefer-default-export": "off",
+    "no-console": "off",
+    "no-param-reassign": "off",
+    "prettier/prettier": "error",
+    "import/extensions": [
+      "error",
+      "ignorePackages",
+      {
+        "ts": "never"
+      }
+    ],
+    "quotes": [
+      1,
+      "double",
+      {
+        "avoidEscape": true
+      }
+    ]
+  },
+  "settings": {
+    "import/resolver": {
+      "typescript": {}
+    }
+  }
+}

+ 16 - 0
backend/.gitignore

@@ -0,0 +1,16 @@
+node_modules
+public/*
+dist
+!public/.gitkeep
+.env
+.env.test
+
+package-lock.json
+yarn.lock
+yarn-error.log
+
+/src/config/sentry.js
+
+# Ignore test-related files
+/coverage.data
+/coverage/

+ 8 - 0
backend/.sequelizerc

@@ -0,0 +1,8 @@
+const { resolve } = require("path");
+
+module.exports = {
+  "config": resolve(__dirname, "dist", "config", "database.js"),
+  "modules-path": resolve(__dirname, "dist", "models"),
+  "migrations-path": resolve(__dirname, "dist", "database", "migrations"),
+  "seeders-path": resolve(__dirname, "dist", "database", "seeds")
+};

+ 7 - 0
backend/Dockerfile.sqlsetup

@@ -0,0 +1,7 @@
+FROM node:20 as builder
+RUN echo "Instalando backend para rodar migrations. . ."
+WORKDIR /app
+COPY . .
+RUN npm install
+RUN npm run build
+ENTRYPOINT [ "npm", "run", "db:migrate" ,"&&", "npm", "run", "db:seed" ]

+ 5 - 0
backend/build-and-run-docker-sql.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+COMPOSE_FILE="-f docker-compose.databases.yml"
+export $(grep -v '^#' .env | xargs)
+docker compose $COMPOSE_FILE build --no-cache
+docker compose $COMPOSE_FILE up $SERVICES_TO_COMPOSE_UP_SQL "$@"

+ 1 - 0
backend/certs/coloque_seus_certificado_aqui

@@ -0,0 +1 @@
+

+ 67 - 0
backend/docker-compose.databases.yml

@@ -0,0 +1,67 @@
+services:
+  cache:
+    image: redis:latest
+    restart: always
+    ports:
+      - "${REDIS_PORT}:${REDIS_PORT}"
+    environment:
+      - REDIS_PASSWORD=${REDIS_PASS}
+      - REDIS_PORT=${REDIS_PORT}
+      - REDIS_DATABASES=${REDIS_DBS}
+    networks:
+      - app_network
+
+  db_postgres:
+    image: postgres
+    environment:
+      - POSTGRES_PASSWORD=${DB_PASS}
+      - POSTGRES_USER=${DB_USER}
+      - POSTGRES_DB=${DB_NAME}
+    ports:
+      - ${DB_PORT}:${DB_PORT}
+    networks:
+      - app_network
+    healthcheck:
+      test: ["CMD-SHELL", "psql -U ${DB_USER} -d ${DB_NAME} -c 'SELECT 1' || exit 1"]
+      interval: 10s
+      timeout: 3s
+      retries: 3
+
+  # db_mysql:
+  #   image: mysql
+  #   networks:
+  #     - app_network
+  #   ports:
+  #     - ${DB_PORT}:${DB_PORT}
+  #   environment:
+  #     - MYSQL_ROOT_PASSWORD=${DB_PASS}
+  #     - MYSQL_DATABASE=${DB_NAME}
+  #     - MYSQL_USER=${DB_USER}
+  #     - MYSQL_PASSWORD=${DB_PASS}
+  #   healthcheck:
+  #     test: "mysql -u$$MYSQL_USER -p$$MYSQL_ROOT_PASSWORD  -e 'SHOW databases'"
+  #     interval: 10s
+  #     retries: 3
+
+  db_migrate:
+    build:
+      dockerfile: ./Dockerfile.sqlsetup
+    environment:
+      - DB_DIALECT=${DB_DIALECT}
+      - DB_HOST=${DB_HOST}
+      - DB_PORT=${DB_PORT}
+      - DB_NAME=${DB_NAME}
+      - DB_USER=${DB_USER}
+      - DB_PASS=${DB_PASS}
+      - DB_DEBUG=${DB_DEBUG}
+    networks:
+      - app_network
+    depends_on:
+      db_postgres:
+      # db_mysql:
+        condition: service_healthy
+
+networks:
+  app_network:
+
+

+ 4 - 0
backend/exit-docker-sql.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+COMPOSE_FILE="-f docker-compose.databases.yml"
+export $(grep -v '^#' .env | xargs)
+docker compose $COMPOSE_FILE down $SERVICES_TO_COMPOSE_UP_SQL "$@"

+ 186 - 0
backend/jest.config.js

@@ -0,0 +1,186 @@
+/*
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/en/configuration.html
+ */
+
+module.exports = {
+  // All imported modules in your tests should be mocked automatically
+  // automock: false,
+
+  // Stop running tests after `n` failures
+  bail: 1,
+
+  // The directory where Jest should store its cached dependency information
+  // cacheDirectory: "/tmp/jest_rs",
+
+  // Automatically clear mock calls and instances between every test
+  clearMocks: true,
+
+  // Indicates whether the coverage information should be collected while executing the test
+  collectCoverage: true,
+
+  // An array of glob patterns indicating a set of files for which coverage information should be collected
+  collectCoverageFrom: ["<rootDir>/src/services/**/*.ts"],
+
+  // The directory where Jest should output its coverage files
+  coverageDirectory: "coverage",
+
+  // An array of regexp pattern strings used to skip coverage collection
+  // coveragePathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
+
+  // Indicates which provider should be used to instrument code for coverage
+  coverageProvider: "v8",
+
+  // A list of reporter names that Jest uses when writing coverage reports
+  coverageReporters: ["text", "lcov"],
+
+  // An object that configures minimum threshold enforcement for coverage results
+  // coverageThreshold: undefined,
+
+  // A path to a custom dependency extractor
+  // dependencyExtractor: undefined,
+
+  // Make calling deprecated APIs throw helpful error messages
+  // errorOnDeprecated: false,
+
+  // Force coverage collection from ignored files using an array of glob patterns
+  // forceCoverageMatch: [],
+
+  // A path to a module which exports an async function that is triggered once before all test suites
+  // globalSetup: undefined,
+
+  // A path to a module which exports an async function that is triggered once after all test suites
+  // globalTeardown: undefined,
+
+  // A set of global variables that need to be available in all test environments
+  // globals: {},
+
+  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+  // maxWorkers: "50%",
+
+  // An array of directory names to be searched recursively up from the requiring module's location
+  // moduleDirectories: [
+  //   "node_modules"
+  // ],
+
+  // An array of file extensions your modules use
+  // moduleFileExtensions: [
+  //   "js",
+  //   "json",
+  //   "jsx",
+  //   "ts",
+  //   "tsx",
+  //   "node"
+  // ],
+
+  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+  // moduleNameMapper: {},
+
+  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+  // modulePathIgnorePatterns: [],
+
+  // Activates notifications for test results
+  // notify: false,
+
+  // An enum that specifies notification mode. Requires { notify: true }
+  // notifyMode: "failure-change",
+
+  // A preset that is used as a base for Jest's configuration
+  preset: "ts-jest",
+
+  // Run tests from one or more projects
+  // projects: undefined,
+
+  // Use this configuration option to add custom reporters to Jest
+  // reporters: undefined,
+
+  // Automatically reset mock state between every test
+  // resetMocks: false,
+
+  // Reset the module registry before running each individual test
+  // resetModules: false,
+
+  // A path to a custom resolver
+  // resolver: undefined,
+
+  // Automatically restore mock state between every test
+  // restoreMocks: false,
+
+  // The root directory that Jest should scan for tests and modules within
+  // rootDir: undefined,
+
+  // A list of paths to directories that Jest should use to search for files in
+  // roots: [
+  //   "<rootDir>"
+  // ],
+
+  // Allows you to use a custom runner instead of Jest's default test runner
+  // runner: "jest-runner",
+
+  // The paths to modules that run some code to configure or set up the testing environment before each test
+  // setupFiles: [],
+
+  // A list of paths to modules that run some code to configure or set up the testing framework before each test
+  // setupFilesAfterEnv: [],
+
+  // The number of seconds after which a test is considered as slow and reported as such in the results.
+  // slowTestThreshold: 5,
+
+  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+  // snapshotSerializers: [],
+
+  // The test environment that will be used for testing
+  testEnvironment: "node",
+
+  // Options that will be passed to the testEnvironment
+  // testEnvironmentOptions: {},
+
+  // Adds a location field to test results
+  // testLocationInResults: false,
+
+  // The glob patterns Jest uses to detect test files
+  testMatch: ["**/__tests__/**/*.spec.ts"]
+
+  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+  // testPathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
+
+  // The regexp pattern or array of patterns that Jest uses to detect test files
+  // testRegex: [],
+
+  // This option allows the use of a custom results processor
+  // testResultsProcessor: undefined,
+
+  // This option allows use of a custom test runner
+  // testRunner: "jasmine2",
+
+  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
+  // testURL: "http://localhost",
+
+  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
+  // timers: "real",
+
+  // A map from regular expressions to paths to transformers
+  // transform: undefined,
+
+  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+  // transformIgnorePatterns: [
+  //   "/node_modules/",
+  //   "\\.pnp\\.[^\\/]+$"
+  // ],
+
+  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+  // unmockedModulePathPatterns: undefined,
+
+  // Indicates whether each individual test should be reported during the run
+  // verbose: undefined,
+
+  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+  // watchPathIgnorePatterns: [],
+
+  // Whether to use watchman for file crawling
+  // watchman: true,
+};

+ 101 - 0
backend/package.json

@@ -0,0 +1,101 @@
+{
+  "name": "backend",
+  "version": "6.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "build": "tsc",
+    "watch": "tsc -w",
+    "start": "nodemon dist/server.js",
+    "dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
+    "db:migrate": "npx sequelize db:migrate",
+    "db:seed": "sequelize db:seed:all",
+    "pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
+    "test": "NODE_ENV=test jest",
+    "posttest": "NODE_ENV=test sequelize db:migrate:undo:all",
+    "lint": "eslint src/**/*.ts"
+  },
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "@adiwajshing/keyed-db": "^0.2.4",
+    "@ffmpeg-installer/ffmpeg": "^1.1.0",
+    "@hapi/boom": "^9.1.4",
+    "@sentry/node": "^6.18.1",
+    "@types/fs-extra": "^11.0.4",
+    "@types/mime-types": "^2.1.4",
+    "@whiskeysockets/baileys": "latest",
+    "bcryptjs": "^2.4.3",
+    "body-parser": "^1.20.2",
+    "bull": "^4.8.2",
+    "cookie-parser": "^1.4.6",
+    "cors": "^2.8.5",
+    "cron": "^2.1.0",
+    "date-fns": "^2.28.0",
+    "dotenv": "^16.0.0",
+    "express": "^4.17.3",
+    "express-async-errors": "^3.1.1",
+    "fluent-ffmpeg": "^2.1.2",
+    "gn-api-sdk-typescript": "^1.0.7",
+    "http-graceful-shutdown": "^3.1.6",
+    "jsonwebtoken": "^8.5.1",
+    "microsoft-cognitiveservices-speech-sdk": "1.31.0",
+    "mime-types": "^2.1.35",
+    "multer": "^1.4.4",
+    "mustache": "^4.2.0",
+    "mysql2": "^2.2.5",
+    "node-cache": "^5.1.2",
+    "node-cron": "^3.0.2",
+    "nodemailer": "^6.8.0",
+    "openai": "3.3.0",
+    "pg": "^8.7.3",
+    "pino": "^7.8.0",
+    "pino-pretty": "^10.0.0",
+    "puppeteer": "^19.4.0",
+    "qrcode-terminal": "^0.12.0",
+    "reflect-metadata": "^0.1.13",
+    "request": "2.88.2",
+    "sequelize": "^5.22.3",
+    "sequelize-cli": "^5.5.1",
+    "sequelize-typescript": "^1.1.0",
+    "socket.io": "^4.7.4",
+    "uuid": "^8.3.2",
+    "xlsx": "^0.18.3",
+    "yup": "^0.32.11"
+  },
+  "devDependencies": {
+    "@types/bcryptjs": "^2.4.2",
+    "@types/bluebird": "^3.5.36",
+    "@types/chance": "^1.1.3",
+    "@types/cookie-parser": "^1.4.2",
+    "@types/cors": "^2.8.12",
+    "@types/express": "^4.17.13",
+    "@types/factory-girl": "^5.0.8",
+    "@types/jest": "^27.4.1",
+    "@types/jsonwebtoken": "^8.5.8",
+    "@types/multer": "^1.4.7",
+    "@types/mustache": "^4.1.2",
+    "@types/node": "^17.0.21",
+    "@types/supertest": "^2.0.11",
+    "@types/uuid": "^8.3.4",
+    "@types/validator": "^13.7.1",
+    "@types/yup": "^0.29.13",
+    "@typescript-eslint/eslint-plugin": "^5.13.0",
+    "@typescript-eslint/parser": "^5.13.0",
+    "chance": "^1.1.8",
+    "eslint": "^8.10.0",
+    "eslint-config-airbnb-base": "^15.0.0",
+    "eslint-config-prettier": "^8.5.0",
+    "eslint-import-resolver-typescript": "^2.5.0",
+    "eslint-plugin-import": "^2.25.4",
+    "eslint-plugin-prettier": "^4.0.0",
+    "factory-girl": "^5.0.4",
+    "jest": "^27.5.1",
+    "nodemon": "^2.0.15",
+    "prettier": "^2.5.1",
+    "supertest": "^6.2.2",
+    "ts-jest": "^27.1.3",
+    "ts-node-dev": "^1.1.8",
+    "typescript": "^4.6.3"
+  }
+}

+ 5 - 0
backend/prettier.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  singleQuote: false,
+  trailingComma: "none",
+  arrowParens: "avoid"
+};

+ 0 - 0
backend/public/.gitkeep


+ 4 - 0
backend/run-docker-sql.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+COMPOSE_FILE="-f docker-compose.databases.yml"
+export $(grep -v '^#' .env | xargs)
+docker compose $COMPOSE_FILE up $SERVICES_TO_COMPOSE_UP_SQL "$@"

+ 5 - 0
backend/src/@types/express.d.ts

@@ -0,0 +1,5 @@
+declare namespace Express {
+  export interface Request {
+    user: { id: string; profile: string; companyId: number };
+  }
+}

+ 1 - 0
backend/src/@types/qrcode-terminal.d.ts

@@ -0,0 +1 @@
+declare module "qrcode-terminal";

+ 54 - 0
backend/src/app.ts

@@ -0,0 +1,54 @@
+import "./bootstrap";
+import "reflect-metadata";
+import "express-async-errors";
+import express, { Request, Response, NextFunction } from "express";
+import cors from "cors";
+import cookieParser from "cookie-parser";
+import * as Sentry from "@sentry/node";
+
+import "./database";
+import uploadConfig from "./config/upload";
+import AppError from "./errors/AppError";
+import routes from "./routes";
+import { logger } from "./utils/logger";
+import { messageQueue, sendScheduledMessages } from "./queues";
+import bodyParser from 'body-parser';
+
+Sentry.init({ dsn: process.env.SENTRY_DSN });
+
+const app = express();
+
+app.set("queues", {
+  messageQueue,
+  sendScheduledMessages
+});
+
+const bodyparser = require('body-parser');
+app.use(bodyParser.json({ limit: '10mb' }));
+
+app.use(
+  cors({
+    credentials: true,
+    origin: process.env.FRONTEND_URL
+  })
+);
+app.use(cookieParser());
+app.use(express.json());
+app.use(Sentry.Handlers.requestHandler());
+app.use("/public", express.static(uploadConfig.directory));
+app.use(routes);
+
+app.use(Sentry.Handlers.errorHandler());
+
+app.use(async (err: Error, req: Request, res: Response, _: NextFunction) => {
+
+  if (err instanceof AppError) {
+    logger.warn(err);
+    return res.status(err.statusCode).json({ error: err.message });
+  }
+
+  logger.error(err);
+  return res.status(500).json({ error: "ERR_INTERNAL_SERVER_ERROR" });
+});
+
+export default app;

+ 5 - 0
backend/src/bootstrap.ts

@@ -0,0 +1,5 @@
+import dotenv from "dotenv";
+
+dotenv.config({
+  path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
+});

+ 13 - 0
backend/src/config/Gn.ts

@@ -0,0 +1,13 @@
+import path from "path";
+
+const cert = path.join(
+  __dirname,
+  `../../certs/${process.env.GERENCIANET_PIX_CERT}.p12`
+);
+
+export = {
+  sandbox: false,
+  client_id: process.env.GERENCIANET_CLIENT_ID as string,
+  client_secret: process.env.GERENCIANET_CLIENT_SECRET as string,
+  pix_cert: cert
+};

+ 6 - 0
backend/src/config/auth.ts

@@ -0,0 +1,6 @@
+export default {
+  secret: process.env.JWT_SECRET || "mysecret",
+  expiresIn: "15m",
+  refreshSecret: process.env.JWT_REFRESH_SECRET || "myanothersecret",
+  refreshExpiresIn: "7d"
+};

+ 41 - 0
backend/src/config/database.ts

@@ -0,0 +1,41 @@
+import "../bootstrap";
+
+module.exports = {
+  define: {
+    charset: "utf8mb4",
+    collate: "utf8mb4_bin",
+  },
+  dialect: process.env.DB_DIALECT || "mysql",
+  timezone: "-03:00",
+  host: process.env.DB_HOST,
+  port: process.env.DB_PORT || 3306,
+  database: process.env.DB_NAME,
+  username: process.env.DB_USER,
+  password: process.env.DB_PASS,
+  logging: process.env.DB_DEBUG === "true" 
+    ? (msg) => console.log(`[Sequelize] ${new Date().toISOString()}: ${msg}`) 
+    : false,
+  pool: {
+    max: 20,
+    min: 1,
+    acquire: 0,
+    idle: 30000,
+    evict: 1000 * 60 * 5,
+  },
+  retry: {
+    max: 3,
+    timeout: 30000,
+    match: [
+      /Deadlock/i,
+      /SequelizeConnectionError/,
+      /SequelizeConnectionRefusedError/,
+      /SequelizeConnectionTimedOutError/,
+      /SequelizeHostNotFoundError/,
+      /SequelizeHostNotReachableError/,
+      /SequelizeInvalidConnectionError/,
+      /SequelizeConnectionAcquireTimeoutError/,
+      /Operation timeout/,
+      /ETIMEDOUT/
+    ]
+  },
+};

+ 3 - 0
backend/src/config/redis.ts

@@ -0,0 +1,3 @@
+export const REDIS_URI_CONNECTION = process.env.REDIS_URI || "";
+export const REDIS_OPT_LIMITER_MAX = process.env.REDIS_OPT_LIMITER_MAX || 1;
+export const REDIS_OPT_LIMITER_DURATION = process.env.REDIS_OPT_LIMITER_DURATION || 3000;

+ 39 - 0
backend/src/config/upload.ts

@@ -0,0 +1,39 @@
+import path from "path";
+import multer from "multer";
+import fs from "fs";
+
+const publicFolder = path.resolve(__dirname, "..", "..", "public");
+
+export default {
+  directory: publicFolder,
+  storage: multer.diskStorage({
+    destination: async function (req, file, cb) {
+
+      const { typeArch, fileId } = req.body;      
+
+      let folder;
+
+      if (typeArch && typeArch !== "announcements") {
+        folder =  path.resolve(publicFolder , typeArch, fileId ? fileId : "") 
+      } else if (typeArch && typeArch === "announcements") {
+        folder =  path.resolve(publicFolder , typeArch) 
+      }
+      else
+      {
+        folder =  path.resolve(publicFolder) 
+      }
+
+      if (!fs.existsSync(folder)) {
+        fs.mkdirSync(folder,  { recursive: true })
+        fs.chmodSync(folder, 0o777)
+      }
+      return cb(null, folder);
+    },
+    filename(req, file, cb) {
+      const { typeArch } = req.body;
+
+      const fileName = typeArch && typeArch !== "announcements" ? file.originalname.replace('/','-').replace(/ /g, "_") : new Date().getTime() + '_' + file.originalname.replace('/','-').replace(/ /g, "_");
+      return cb(null, fileName);
+    }
+  })
+};

+ 205 - 0
backend/src/controllers/AnnouncementController.ts

@@ -0,0 +1,205 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import { head } from "lodash";
+import fs from "fs";
+import path from "path";
+
+import ListService from "../services/AnnouncementService/ListService";
+import CreateService from "../services/AnnouncementService/CreateService";
+import ShowService from "../services/AnnouncementService/ShowService";
+import UpdateService from "../services/AnnouncementService/UpdateService";
+import DeleteService from "../services/AnnouncementService/DeleteService";
+import FindService from "../services/AnnouncementService/FindService";
+
+import Announcement from "../models/Announcement";
+
+import AppError from "../errors/AppError";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  companyId: string | number;
+};
+
+type StoreData = {
+  priority: string;
+  title: string;
+  text: string;
+  status: string;
+  companyId: number;
+  mediaPath?: string;
+  mediaName?: string;
+};
+
+type FindParams = {
+  companyId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    title: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data,
+    companyId
+  });
+
+  const io = getIO();
+  io.emit(`company-announcement`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    title: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    id
+  });
+
+  const io = getIO();
+  io.emit(`company-announcement`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-announcement`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Announcement deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as FindParams;
+  const records: Announcement[] = await FindService(params);
+
+  return res.status(200).json(records);
+};
+
+export const mediaUpload = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const files = req.files as Express.Multer.File[];
+  const file = head(files);
+
+  try {
+    const announcement = await Announcement.findByPk(id);
+
+    await announcement.update({
+      mediaPath: file.filename,
+      mediaName: file.originalname
+    });
+    await announcement.reload();
+
+    const io = getIO();
+    io.emit(`company-announcement`, {
+      action: "update",
+      record: announcement
+    });
+
+    return res.send({ mensagem: "Mensagem enviada" });
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+};
+
+export const deleteMedia = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  try {
+    const announcement = await Announcement.findByPk(id);
+    const filePath = path.resolve("public", announcement.mediaPath);
+    const fileExists = fs.existsSync(filePath);
+    if (fileExists) {
+      fs.unlinkSync(filePath);
+    }
+
+    await announcement.update({
+      mediaPath: null,
+      mediaName: null
+    });
+    await announcement.reload();
+
+    const io = getIO();
+    io.emit(`company-announcement`, {
+      action: "update",
+      record: announcement
+    });
+
+    return res.send({ mensagem: "Arquivo excluído" });
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+};

+ 362 - 0
backend/src/controllers/CampaignController.ts

@@ -0,0 +1,362 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import { head } from "lodash";
+import fs from "fs";
+import path from "path";
+
+import ListService from "../services/CampaignService/ListService";
+import CreateService from "../services/CampaignService/CreateService";
+import ShowService from "../services/CampaignService/ShowService";
+import UpdateService from "../services/CampaignService/UpdateService";
+import DeleteService from "../services/CampaignService/DeleteService";
+import FindService from "../services/CampaignService/FindService";
+
+import Campaign from "../models/Campaign";
+
+import AppError from "../errors/AppError";
+import { CancelService } from "../services/CampaignService/CancelService";
+import { RestartService } from "../services/CampaignService/RestartService";
+import TicketTag from "../models/TicketTag";
+import Ticket from "../models/Ticket";
+import Contact from "../models/Contact";
+import ContactList from "../models/ContactList";
+import ContactListItem from "../models/ContactListItem";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  companyId: string | number;
+};
+
+type StoreData = {
+  name: string;
+  status: string;
+  scheduledAt: string;
+  companyId: number;
+  contactListId: number;
+  tagListId: number | string;
+  fileListId: number;
+};
+
+type FindParams = {
+  companyId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  if (typeof data.tagListId === 'number' && typeof data.contactListId !== 'number') {
+    const tagId = data.tagListId;
+    const campanhaNome = data.name;
+
+    try {
+      const contactListId = await createContactListFromTag(tagId, companyId, campanhaNome);
+
+      const record = await CreateService({
+        ...data,
+        tagId: Number(data.tagListId),
+        companyId,
+        contactListId: contactListId,
+      });
+      const io = getIO();
+      io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
+        action: "create",
+        record
+      });
+      return res.status(200).json(record);
+    } catch (error) {
+      console.error('Error:', error);
+      return res.status(500).json({ error: 'Error creating contact list' });
+    }
+  }
+
+  if (typeof data.tagListId === 'number' && typeof data.contactListId === 'number') {
+    const tagId = data.tagListId;
+    const campanhaNome = data.name;
+
+    try {
+      const contactListId = await createContactListFromTagAndContactList(tagId, data.contactListId, companyId, campanhaNome);
+
+      const record = await CreateService({
+        ...data,
+        tagId: Number(data.tagListId),
+        companyId,
+        contactListId: contactListId,
+      });
+
+      const io = getIO();
+
+      io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
+        action: "create",
+        record
+      });
+
+      return res.status(200).json(record);
+    } catch (error) {
+      console.error('Error:', error);
+      return res.status(500).json({ error: 'Error creating contact list' });
+    }
+  }
+
+  const record = await CreateService({
+    ...data,
+    tagId: null,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    tagId: typeof data.tagListId === 'number' ? Number(data.tagListId) : null,
+    id
+  }, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const cancel = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  await CancelService(+id);
+
+  return res.status(204).json({ message: "Cancelamento realizado" });
+};
+
+export const restart = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  await RestartService(+id);
+
+  return res.status(204).json({ message: "Reinício dos disparos" });
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Campaign deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as FindParams;
+  const records: Campaign[] = await FindService(params);
+
+  return res.status(200).json(records);
+};
+
+export const mediaUpload = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const files = req.files as Express.Multer.File[];
+  const file = head(files);
+
+  try {
+    const campaign = await Campaign.findByPk(id);
+    campaign.mediaPath = file.filename;
+    campaign.mediaName = file.originalname;
+    await campaign.save();
+    return res.send({ mensagem: "Mensagem enviada" });
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+};
+
+export const deleteMedia = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  try {
+    const campaign = await Campaign.findByPk(id);
+    const filePath = path.resolve("public", campaign.mediaPath);
+    const fileExists = fs.existsSync(filePath);
+    if (fileExists) {
+      fs.unlinkSync(filePath);
+    }
+
+    campaign.mediaPath = null;
+    campaign.mediaName = null;
+    await campaign.save();
+    return res.send({ mensagem: "Arquivo excluído" });
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+};
+
+export async function createContactListFromTag(tagId: number, companyId: number, campanhaNome: string) : Promise<number> {
+  const currentDate = new Date();
+  const formattedDate = currentDate.toISOString();
+
+  try {
+    const ticketTags = await TicketTag.findAll({ where: { tagId } });
+    const ticketIds = ticketTags.map((ticketTag) => ticketTag.ticketId);
+
+    const tickets = await Ticket.findAll({ where: { id: ticketIds } });
+    const contactIds = tickets.map((ticket) => ticket.contactId);
+
+    const selectedContacts = await Contact.findAll({ where: { id: contactIds } });
+
+    const randomName = `${campanhaNome} | TAG: ${tagId} - ${formattedDate}`
+    const contactList = await ContactList.create({ name: randomName, companyId: companyId });
+
+    const { id: contactListId } = contactList;
+
+    const setContacts = new Set(selectedContacts);
+    const contacts = Array.from(setContacts);
+
+    const contactListItems = contacts.map((contact) => ({
+      name: contact.name,
+      number: contact.number,
+      email: contact.email,
+      contactListId,
+      companyId,
+      isWhatsappValid: true,
+    }));
+
+    await ContactListItem.bulkCreate(contactListItems);
+
+    return contactListId;
+  } catch (error) {
+    console.error('Error creating contact list:', error);
+    throw error;
+  }
+}
+
+export async function createContactListFromTagAndContactList(tagId: number, contactListId: number, companyId: number, campanhaNome: string) : Promise<number> {
+  const currentDate = new Date();
+  const formattedDate = currentDate.toISOString();
+
+  try {
+    const ticketTags = await TicketTag.findAll({ where: { tagId } });
+    const ticketIds = ticketTags.map((ticketTag) => ticketTag.ticketId);
+
+    const tickets = await Ticket.findAll({ where: { id: ticketIds } });
+    const contactIds = tickets.map((ticket) => ticket.contactId);
+
+    const selectedContactListItems = await ContactListItem.findAll({ where: { contactListId } })
+    const ticketContacts = await Contact.findAll({ where: { id: contactIds } });
+
+    const contactMap = new Map<string, {email: string, name: string, number: string}>();
+
+    selectedContactListItems.forEach(contact => {
+      contactMap.set(contact.number, {email: contact.email, name: contact.name, number: contact.number});
+    });
+
+    ticketContacts.forEach(contact => {
+      contactMap.set(contact.number, {email: contact.email, name: contact.name, number: contact.number});
+    });
+
+    const mergedContacts = Array.from(contactMap.values());
+
+    const randomName = `${campanhaNome} | TAG: ${tagId} - ${formattedDate}`
+    const contactList = await ContactList.create({ name: randomName, companyId: companyId });
+
+    const { id } = contactList;
+
+    const contactLists = mergedContacts.map((contact) => ({
+      name: contact.name,
+      number: contact.number,
+      email: contact.email,
+      contactListId: id,
+      companyId,
+      isWhatsappValid: true,
+
+    }));
+
+    await ContactListItem.bulkCreate(contactLists);
+
+    return id;
+  } catch (error) {
+    console.error('Error creating contact list:', error);
+    throw error;
+  }
+}

+ 34 - 0
backend/src/controllers/CampaignSettingController.ts

@@ -0,0 +1,34 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/CampaignSettingServices/ListService";
+import CreateService from "../services/CampaignSettingServices/CreateService";
+
+interface StoreData {
+  settings: any;
+}
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+
+  const records = await ListService({
+    companyId
+  });
+
+  return res.json(records);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const record = await CreateService(data, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaignSettings`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};

+ 205 - 0
backend/src/controllers/ChatController.ts

@@ -0,0 +1,205 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import CreateService from "../services/ChatService/CreateService";
+import ListService from "../services/ChatService/ListService";
+import ShowFromUuidService from "../services/ChatService/ShowFromUuidService";
+import DeleteService from "../services/ChatService/DeleteService";
+import FindMessages from "../services/ChatService/FindMessages";
+import UpdateService from "../services/ChatService/UpdateService";
+
+import Chat from "../models/Chat";
+import CreateMessageService from "../services/ChatService/CreateMessageService";
+import User from "../models/User";
+import ChatUser from "../models/ChatUser";
+
+type IndexQuery = {
+  pageNumber: string;
+  companyId: string | number;
+  ownerId?: number;
+};
+
+type StoreData = {
+  users: any[];
+  title: string;
+};
+
+type FindParams = {
+  companyId: number;
+  ownerId?: number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { pageNumber } = req.query as unknown as IndexQuery;
+  const ownerId = +req.user.id;
+
+  const { records, count, hasMore } = await ListService({
+    ownerId,
+    pageNumber
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const ownerId = +req.user.id;
+  const data = req.body as StoreData;
+
+  const record = await CreateService({
+    ...data,
+    ownerId,
+    companyId
+  });
+
+  const io = getIO();
+
+  record.users.forEach(user => {
+    io.to(`user-${user.userId}`).emit(`company-${companyId}-chat-user-${user.userId}`, {
+      action: "create",
+      record
+    });
+  });
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body;
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    id: +id
+  });
+
+  const io = getIO();
+
+  record.users.forEach(user => {
+    io.to(`user-${user.userId}`).emit(`company-${companyId}-chat-user-${user.userId}`, {
+      action: "update",
+      record
+    });
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowFromUuidService(id);
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-chat`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Chat deleted" });
+};
+
+export const saveMessage = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { companyId } = req.user;
+  const { message } = req.body;
+  const { id } = req.params;
+  const senderId = +req.user.id;
+  const chatId = +id;
+
+  const newMessage = await CreateMessageService({
+    chatId,
+    senderId,
+    message
+  });
+
+  const chat = await Chat.findByPk(chatId, {
+    include: [
+      { model: User, as: "owner" },
+      { model: ChatUser, as: "users" }
+    ]
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-chat-${chatId}`, {
+    action: "new-message",
+    newMessage,
+    chat
+  });
+
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-chat`, {
+    action: "new-message",
+    newMessage,
+    chat
+  });
+
+  return res.json(newMessage);
+};
+
+export const checkAsRead = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { companyId } = req.user;
+  const { userId } = req.body;
+  const { id } = req.params;
+
+  const chatUser = await ChatUser.findOne({ where: { chatId: id, userId } });
+  await chatUser.update({ unreads: 0 });
+
+  const chat = await Chat.findByPk(id, {
+    include: [
+      { model: User, as: "owner" },
+      { model: ChatUser, as: "users" }
+    ]
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-chat-${id}`, {
+    action: "update",
+    chat
+  });
+
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-chat`, {
+    action: "update",
+    chat
+  });
+
+  return res.json(chat);
+};
+
+export const messages = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { pageNumber } = req.query as unknown as IndexQuery;
+  const { id: chatId } = req.params;
+  const ownerId = +req.user.id;
+
+  const { records, count, hasMore } = await FindMessages({
+    chatId,
+    ownerId,
+    pageNumber
+  });
+
+  return res.json({ records, count, hasMore });
+};

+ 181 - 0
backend/src/controllers/CompanyController.ts

@@ -0,0 +1,181 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+// import { getIO } from "../libs/socket";
+import AppError from "../errors/AppError";
+import Company from "../models/Company";
+import authConfig from "../config/auth";
+
+import ListCompaniesService from "../services/CompanyService/ListCompaniesService";
+import CreateCompanyService from "../services/CompanyService/CreateCompanyService";
+import UpdateCompanyService from "../services/CompanyService/UpdateCompanyService";
+import ShowCompanyService from "../services/CompanyService/ShowCompanyService";
+import UpdateSchedulesService from "../services/CompanyService/UpdateSchedulesService";
+import DeleteCompanyService from "../services/CompanyService/DeleteCompanyService";
+import FindAllCompaniesService from "../services/CompanyService/FindAllCompaniesService";
+import { verify } from "jsonwebtoken";
+import User from "../models/User";
+import ShowPlanCompanyService from "../services/CompanyService/ShowPlanCompanyService";
+import ListCompaniesPlanService from "../services/CompanyService/ListCompaniesPlanService";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+interface TokenPayload {
+  id: string;
+  username: string;
+  profile: string;
+  companyId: number;
+  iat: number;
+  exp: number;
+}
+
+type CompanyData = {
+  name: string;
+  id?: number;
+  phone?: string;
+  email?: string;
+  status?: boolean;
+  planId?: number;
+  campaignsEnabled?: boolean;
+  dueDate?: string;
+  recurrence?: string;
+  password: string;
+};
+
+type SchedulesData = {
+  schedules: [];
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { companies, count, hasMore } = await ListCompaniesService({
+    searchParam,
+    pageNumber
+  });
+
+  return res.json({ companies, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const newCompany: CompanyData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(newCompany);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const company = await CreateCompanyService(newCompany);
+
+  return res.status(200).json(company);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const company = await ShowCompanyService(id);
+
+  return res.status(200).json(company);
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const companies: Company[] = await FindAllCompaniesService();
+
+  return res.status(200).json(companies);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const companyData: CompanyData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string()
+  });
+
+  try {
+    await schema.validate(companyData);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const company = await UpdateCompanyService({ id, ...companyData });
+
+  return res.status(200).json(company);
+};
+
+export const updateSchedules = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { schedules }: SchedulesData = req.body;
+  const { id } = req.params;
+
+  const company = await UpdateSchedulesService({
+    id,
+    schedules
+  });
+
+  return res.status(200).json(company);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  const company = await DeleteCompanyService(id);
+
+  return res.status(200).json(company);
+};
+
+export const listPlan = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { id: requestUserId, profile, companyId } = decoded as TokenPayload;
+  const requestUser = await User.findByPk(requestUserId);
+
+  if (requestUser.super === true) {
+    const company = await ShowPlanCompanyService(id);
+    return res.status(200).json(company);
+  } else if (companyId.toString() !== id) {
+    return res.status(400).json({ error: "Você não possui permissão para acessar este recurso!" });
+  } else {
+    const company = await ShowPlanCompanyService(id);
+    return res.status(200).json(company);
+  }
+
+};
+
+export const indexPlan = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { id, profile, companyId } = decoded as TokenPayload;
+  // const company = await Company.findByPk(companyId);
+  const requestUser = await User.findByPk(id);
+
+  if (requestUser.super === true) {
+    const companies = await ListCompaniesPlanService();
+    return res.json({ companies });
+  } else {
+    return res.status(400).json({ error: "Você não possui permissão para acessar este recurso!" });
+  }
+
+};

+ 266 - 0
backend/src/controllers/ContactController.ts

@@ -0,0 +1,266 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListContactsService from "../services/ContactServices/ListContactsService";
+import CreateContactService from "../services/ContactServices/CreateContactService";
+import ShowContactService from "../services/ContactServices/ShowContactService";
+import UpdateContactService from "../services/ContactServices/UpdateContactService";
+import DeleteContactService from "../services/ContactServices/DeleteContactService";
+import GetContactService from "../services/ContactServices/GetContactService";
+
+import CheckContactNumber from "../services/WbotServices/CheckNumber";
+import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
+import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
+import AppError from "../errors/AppError";
+import SimpleListService, {
+  SearchContactParams
+} from "../services/ContactServices/SimpleListService";
+import ContactCustomField from "../models/ContactCustomField";
+import { logger } from "../utils/logger";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+type IndexGetContactQuery = {
+  name: string;
+  number: string;
+};
+
+interface ExtraInfo extends ContactCustomField {
+  name: string;
+  value: string;
+}
+interface ContactData {
+  name: string;
+  number: string;
+  email?: string;
+  extraInfo?: ExtraInfo[];
+}
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { contacts, count, hasMore } = await ListContactsService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ contacts, count, hasMore });
+};
+
+export const getContact = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { name, number } = req.body as IndexGetContactQuery;
+  const { companyId } = req.user;
+
+  const contact = await GetContactService({
+    name,
+    number,
+    companyId
+  });
+
+  return res.status(200).json(contact);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const newContact: ContactData = req.body;
+  newContact.number = newContact.number.replace(/\D/g, '');
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required(),
+    number: Yup.string()
+      .required()
+      .matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
+  });
+
+  const contact = await createNewContact(newContact,companyId,schema);
+
+  return res.status(200).json(contact);
+};
+
+export const storeUpload = async (req: Request, res: Response) : Promise<Response> => {
+
+  const {companyId} = req.user;
+  const contacts = req.body;
+
+  let errorBag = [];
+  let contactAdded = [];
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required(),
+    number: Yup.string().required()
+  });
+
+  const promises = contacts.map(async contact => {
+
+    const newContact : ContactData = {name: contact.Nome, number: contact.Telefone.replace(/\D/g, '')}
+
+    try{
+
+      const contact = await createUploadedContact( newContact, companyId, schema )
+      contactAdded.push( {contactName: contact.name, contactId: contact.id} );
+
+    }catch(e){
+      errorBag.push({contactName: contact.Nome, error: e || e.message});
+    }
+  });
+
+  await Promise.all(promises);
+
+  return res.status(200).json({newContacts: contactAdded, errorBag: errorBag});
+}
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { contactId } = req.params;
+  const { companyId } = req.user;
+
+  const contact = await ShowContactService(contactId, companyId);
+
+  return res.status(200).json(contact);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const contactData: ContactData = req.body;
+  const { companyId } = req.user;
+
+  contactData.number = contactData.number.replace(/\D/g, '');
+
+  const schema = Yup.object().shape({
+    name: Yup.string(),
+    number: Yup.string().matches(
+      /^\d+$/,
+      "Invalid number format. Only numbers is allowed."
+    )
+  });
+
+  try {
+    await schema.validate(contactData);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  contactData.number = contactData.number.replace(/\D/g, "");
+
+  await CheckIsValidContact(contactData.number, companyId);
+  const validNumber = await CheckContactNumber(contactData.number, companyId);
+  const number = validNumber.jid.replace(/\D/g, "");
+  contactData.number = number;
+
+  const { contactId } = req.params;
+
+  const contact = await UpdateContactService({
+    contactData,
+    contactId,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-contact`, {
+    action: "update",
+    contact
+  });
+
+  return res.status(200).json(contact);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { contactId } = req.params;
+  const { companyId } = req.user;
+
+  await ShowContactService(contactId, companyId);
+
+  await DeleteContactService(contactId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-contact`, {
+    action: "delete",
+    contactId
+  });
+
+  return res.status(200).json({ message: "Contact deleted" });
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const { name } = req.query as unknown as SearchContactParams;
+  const { companyId } = req.user;
+
+  const contacts = await SimpleListService({ name, companyId });
+
+  return res.json(contacts);
+};
+
+const createNewContact = async ( newContact : ContactData, companyId : number, schema : any ) => {
+
+    try{
+      await schema.validate(newContact);
+    }catch(err:any){
+      throw new AppError(err.message);
+    }
+
+    logger.info(newContact);
+
+    await CheckIsValidContact(newContact.number, companyId);
+    const number = newContact.number.replace(/\D/g, "");
+    const validNumber = await CheckContactNumber(number, companyId);
+
+    if( !validNumber )
+      throw new AppError("Não foi possível localizar o número informado no Whatsapp");
+
+    newContact.number = number;
+
+    /**
+     * Código desabilitado por demora no retorno
+     */
+    // const profilePicUrl = await GetProfilePicUrl(validNumber.jid, companyId);
+
+    const contact = await CreateContactService({
+      ...newContact,
+      // profilePicUrl,
+      companyId
+    });
+
+    const io = getIO();
+    io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-contact`, {
+      action: "create",
+      contact
+    });
+
+    return contact;
+}
+
+const createUploadedContact = async ( newContact : ContactData, companyId : number, schema : any) => {
+
+  try{
+    await schema.validate(newContact);
+  }catch(err:any){
+    throw new AppError(err.message);
+  }
+
+  newContact.number = newContact.number.replace(/\D/g, "");
+  const contact = await CreateContactService({
+    ...newContact,
+    companyId
+  });
+
+  const io = getIO();
+    io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-contact`, {
+      action: "create",
+      contact
+    });
+
+  return contact;
+}

+ 159 - 0
backend/src/controllers/ContactListController.ts

@@ -0,0 +1,159 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/ContactListService/ListService";
+import CreateService from "../services/ContactListService/CreateService";
+import ShowService from "../services/ContactListService/ShowService";
+import UpdateService from "../services/ContactListService/UpdateService";
+import DeleteService from "../services/ContactListService/DeleteService";
+import FindService from "../services/ContactListService/FindService";
+import { head } from "lodash";
+
+import ContactList from "../models/ContactList";
+
+import AppError from "../errors/AppError";
+import { ImportContacts } from "../services/ContactListService/ImportContacts";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  companyId: string | number;
+};
+
+type StoreData = {
+  name: string;
+  companyId: string;
+};
+
+type FindParams = {
+  companyId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactList`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    id
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactList`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactList`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Contact list deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as FindParams;
+  const records: ContactList[] = await FindService(params);
+
+  return res.status(200).json(records);
+};
+
+export const upload = async (req: Request, res: Response) => {
+  const files = req.files as Express.Multer.File[];
+  const file: Express.Multer.File = head(files) as Express.Multer.File;
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  const response = await ImportContacts(+id, companyId, file);
+
+  const io = getIO();
+
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactListItem-${+id}`, {
+    action: "reload",
+    records: response
+  });
+
+  return res.status(200).json(response);
+};

+ 145 - 0
backend/src/controllers/ContactListItemController.ts

@@ -0,0 +1,145 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/ContactListItemService/ListService";
+import CreateService from "../services/ContactListItemService/CreateService";
+import ShowService from "../services/ContactListItemService/ShowService";
+import UpdateService from "../services/ContactListItemService/UpdateService";
+import DeleteService from "../services/ContactListItemService/DeleteService";
+import FindService from "../services/ContactListItemService/FindService";
+
+import ContactListItem from "../models/ContactListItem";
+
+import AppError from "../errors/AppError";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  companyId: string | number;
+  contactListId: string | number;
+};
+
+type StoreData = {
+  name: string;
+  number: string;
+  contactListId: number;
+  companyId?: string;
+  email?: string;
+};
+
+type FindParams = {
+  companyId: number;
+  contactListId: number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber, contactListId } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { contacts, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId,
+    contactListId
+  });
+
+  return res.json({ contacts, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactListItem`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    id
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactListItem`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-ContactListItem`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Contact deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as unknown as FindParams;
+  const records: ContactListItem[] = await FindService(params);
+
+  return res.status(200).json(records);
+};

+ 42 - 0
backend/src/controllers/DashbardController.ts

@@ -0,0 +1,42 @@
+import { Request, Response } from "express";
+import DashboardDataService, { DashboardData, Params } from "../services/ReportService/DashbardDataService";
+import { TicketsAttendance } from "../services/ReportService/TicketsAttendance";
+import { TicketsDayService } from "../services/ReportService/TicketsDayService";
+
+type IndexQuery = {
+  initialDate: string;
+  finalDate: string;
+  companyId: number | any;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const params: Params = req.query;
+  const { companyId } = req.user;
+  let daysInterval = 3;
+
+  const dashboardData: DashboardData = await DashboardDataService(
+    companyId,
+    params
+  );
+  return res.status(200).json(dashboardData);
+};
+
+export const reportsUsers = async (req: Request, res: Response): Promise<Response> => {
+
+  const { initialDate, finalDate, companyId } = req.query as IndexQuery
+
+  const { data } = await TicketsAttendance({ initialDate, finalDate, companyId });
+
+  return res.json({ data });
+
+}
+
+export const reportsDay = async (req: Request, res: Response): Promise<Response> => {
+
+  const { initialDate, finalDate, companyId } = req.query as IndexQuery
+
+  const { count, data } = await TicketsDayService({ initialDate, finalDate, companyId });
+
+  return res.json({ count, data });
+
+}

+ 153 - 0
backend/src/controllers/FilesController.ts

@@ -0,0 +1,153 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import AppError from "../errors/AppError";
+import { head } from "lodash";
+
+import CreateService from "../services/FileServices/CreateService";
+import ListService from "../services/FileServices/ListService";
+import UpdateService from "../services/FileServices/UpdateService";
+import ShowService from "../services/FileServices/ShowService";
+import DeleteService from "../services/FileServices/DeleteService";
+import SimpleListService from "../services/FileServices/SimpleListService";
+import DeleteAllService from "../services/FileServices/DeleteAllService";
+import FilesOptions from "../models/FilesOptions";
+
+type IndexQuery = {
+  searchParam?: string;
+  pageNumber?: string | number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { pageNumber, searchParam } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { files, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ files, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { name, message, options } = req.body;
+  const { companyId } = req.user;
+
+  const fileList = await CreateService({
+    name,
+    message,
+    options,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company${companyId}-file`, {
+    action: "create",
+    fileList
+  });
+
+  return res.status(200).json(fileList);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { fileId } = req.params;
+  const { companyId } = req.user;
+
+  const file = await ShowService(fileId, companyId);
+
+  return res.status(200).json(file);
+};
+
+export const uploadMedias = async (req: Request, res: Response): Promise<Response> => {
+  const { fileId, id, mediaType } = req.body;
+  const files = req.files as Express.Multer.File[];
+  const file = head(files);
+
+  try {
+    
+    let fileOpt
+    if (files.length > 0) {
+
+      for (const [index, file] of files.entries()) {
+        fileOpt = await FilesOptions.findOne({
+          where: {
+            fileId,
+            id: Array.isArray(id)? id[index] : id
+          }
+        });
+
+        fileOpt.update({
+          path: file.filename.replace('/','-'),
+          mediaType: Array.isArray(mediaType)? mediaType[index] : mediaType
+        }) ;
+      }
+    }
+    
+    return res.send({ mensagem: "Arquivos atualizados" });
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  const { fileId } = req.params;
+  const fileData = req.body;
+  const { companyId } = req.user;
+
+  const fileList = await UpdateService({ fileData, id: fileId, companyId });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company${companyId}-file`, {
+    action: "update",
+    fileList
+  });
+
+  return res.status(200).json(fileList);
+};
+    
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { fileId } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(fileId, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company${companyId}-file`, {
+    action: "delete",
+    fileId
+  });
+
+  return res.status(200).json({ message: "File List deleted" });
+};
+
+export const removeAll = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { companyId } = req.user;
+  await DeleteAllService(companyId);
+
+  return res.send();
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const ratings = await SimpleListService({ searchParam, companyId });
+
+  return res.json(ratings);
+};

+ 25 - 0
backend/src/controllers/ForgotController.ts

@@ -0,0 +1,25 @@
+import { v4 as uuid } from "uuid";
+import { Request, Response } from "express";
+import SendMail from "../services/ForgotPassWordServices/SendMail";
+import ResetPassword from "../services/ResetPasswordService/ResetPassword";
+type IndexQuery = { email?: string; token?: string; password?: string };
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { email } = req.params as IndexQuery;
+  const TokenSenha = uuid();
+  const forgotPassword = await SendMail(email, TokenSenha);
+  if (!forgotPassword) {
+     return res.status(200).json({ message: "E-mail enviado com sucesso" });
+  }
+  return res.status(404).json({ error: "E-mail enviado com sucesso" });
+};
+export const resetPasswords = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { email, token, password } = req.params as IndexQuery;
+  const resetPassword = await ResetPassword(email, token, password);
+  if (!resetPassword) {
+    return res.status(200).json({ message: "Senha redefinida com sucesso" });
+  }
+  return res.status(404).json({ error: "Verifique o Token informado" });
+};

+ 131 - 0
backend/src/controllers/HelpController.ts

@@ -0,0 +1,131 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/HelpServices/ListService";
+import CreateService from "../services/HelpServices/CreateService";
+import ShowService from "../services/HelpServices/ShowService";
+import UpdateService from "../services/HelpServices/UpdateService";
+import DeleteService from "../services/HelpServices/DeleteService";
+import FindService from "../services/HelpServices/FindService";
+
+import Help from "../models/Help";
+
+import AppError from "../errors/AppError";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+type StoreData = {
+  title: string;
+  description: string;
+  video?: string;
+  link?: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber
+  });
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    title: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-help`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    title: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    id
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-help`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-help`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Help deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const records: Help[] = await FindService();
+
+  return res.status(200).json(records);
+};

+ 10 - 0
backend/src/controllers/ImportPhoneContactsController.ts

@@ -0,0 +1,10 @@
+import { Request, Response } from "express";
+import ImportContactsService from "../services/WbotServices/ImportContactsService";
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+
+  await ImportContactsService(companyId);
+
+  return res.status(200).json({ message: "contacts imported" });
+};

+ 172 - 0
backend/src/controllers/InvoicesController.ts

@@ -0,0 +1,172 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+// import { getIO } from "../libs/socket";
+import AppError from "../errors/AppError";
+import Invoices from "../models/Invoices";
+
+import CreatePlanService from "../services/PlanService/CreatePlanService";
+import UpdatePlanService from "../services/PlanService/UpdatePlanService";
+import ShowPlanService from "../services/PlanService/ShowPlanService";
+import DeletePlanService from "../services/PlanService/DeletePlanService";
+
+import FindAllInvoiceService from "../services/InvoicesService/FindAllInvoiceService";
+import ListInvoicesServices from "../services/InvoicesService/ListInvoicesServices";
+import ShowInvoceService from "../services/InvoicesService/ShowInvoiceService";
+import UpdateInvoiceService from "../services/InvoicesService/UpdateInvoiceService";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+ 
+type StorePlanData = {
+  name: string;
+  id?: number | string;
+  users: number | 0;
+  connections: number | 0;
+  queues: number | 0;
+  value: number;
+};
+
+type UpdateInvoiceData = {
+  status: string;
+  id?: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { invoices, count, hasMore } = await ListInvoicesServices({
+    searchParam,
+    pageNumber
+  });
+
+  return res.json({ invoices, count, hasMore });
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { Invoiceid } = req.params;
+
+  const invoice = await ShowInvoceService(Invoiceid);
+
+  return res.status(200).json(invoice);
+};
+
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const invoice: Invoices[] = await FindAllInvoiceService(companyId);
+
+  return res.status(200).json(invoice);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const InvoiceData: UpdateInvoiceData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string()
+  });
+
+  try {
+    await schema.validate(InvoiceData);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const { id, status } = InvoiceData;
+
+  const plan = await UpdateInvoiceService({
+    id,
+    status,
+
+  });
+
+  // const io = getIO();
+  // io.emit("plan", {
+  //   action: "update",
+  //   plan
+  // });
+
+  return res.status(200).json(plan);
+};
+/* export const store = async (req: Request, res: Response): Promise<Response> => {
+  const newPlan: StorePlanData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(newPlan);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const plan = await CreatePlanService(newPlan);
+
+  // const io = getIO();
+  // io.emit("plan", {
+  //   action: "create",
+  //   plan
+  // });
+
+  return res.status(200).json(plan);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const plan = await ShowPlanService(id);
+
+  return res.status(200).json(plan);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const planData: UpdateInvoiceData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string()
+  });
+
+  try {
+    await schema.validate(planData);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const { id, name, users, connections, queues, value } = planData;
+
+  const plan = await UpdatePlanService({
+    id,
+    name,
+    users,
+    connections,
+    queues,
+    value
+  });
+
+  // const io = getIO();
+  // io.emit("plan", {
+  //   action: "update",
+  //   plan
+  // });
+
+  return res.status(200).json(plan);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  const plan = await DeletePlanService(id);
+
+  return res.status(200).json(plan);
+}; */

+ 192 - 0
backend/src/controllers/MessageController.ts

@@ -0,0 +1,192 @@
+import { Request, Response } from "express";
+import AppError from "../errors/AppError";
+
+import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
+import { getIO } from "../libs/socket";
+import Message from "../models/Message";
+import Queue from "../models/Queue";
+import User from "../models/User";
+import Whatsapp from "../models/Whatsapp";
+import formatBody from "../helpers/Mustache";
+
+import ListMessagesService from "../services/MessageServices/ListMessagesService";
+import ShowTicketService from "../services/TicketServices/ShowTicketService";
+import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
+import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
+import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
+import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
+import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
+import CheckContactNumber from "../services/WbotServices/CheckNumber";
+import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
+import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
+import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
+type IndexQuery = {
+  pageNumber: string;
+};
+
+type MessageData = {
+  body: string;
+  fromMe: boolean;
+  read: boolean;
+  quotedMsg?: Message;
+  number?: string;
+  closeTicket?: true;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId } = req.params;
+  const { pageNumber } = req.query as IndexQuery;
+  const { companyId, profile } = req.user;
+  const queues: number[] = [];
+
+  if (profile !== "admin") {
+    const user = await User.findByPk(req.user.id, {
+      include: [{ model: Queue, as: "queues" }]
+    });
+    user.queues.forEach(queue => {
+      queues.push(queue.id);
+    });
+  }
+
+  const { count, messages, ticket, hasMore } = await ListMessagesService({
+    pageNumber,
+    ticketId,
+    companyId,
+    queues
+  });
+
+  SetTicketMessagesAsRead(ticket);
+
+  return res.json({ count, messages, ticket, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId } = req.params;
+  const { body, quotedMsg }: MessageData = req.body;
+  const medias = req.files as Express.Multer.File[];
+  const { companyId } = req.user;
+
+  const ticket = await ShowTicketService(ticketId, companyId);
+
+  SetTicketMessagesAsRead(ticket);
+
+  if (medias) {
+    await Promise.all(
+      medias.map(async (media: Express.Multer.File, index) => {
+        await SendWhatsAppMedia({ media, ticket, body: Array.isArray(body) ? body[index] : body });
+      })
+    );
+  } else {
+    const send = await SendWhatsAppMessage({ body, ticket, quotedMsg });
+  }
+
+  return res.send();
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { messageId } = req.params;
+  const { companyId } = req.user;
+
+  const message = await DeleteWhatsAppMessage(messageId);
+
+  const io = getIO();
+  io.to(message.ticketId.toString()).emit(`company-${companyId}-appMessage`, {
+    action: "update",
+    message
+  });
+
+  return res.send();
+};
+
+export const send = async (req: Request, res: Response): Promise<Response> => {
+  const { whatsappId } = req.params as unknown as { whatsappId: number };
+  const messageData: MessageData = req.body;
+  const medias = req.files as Express.Multer.File[];
+
+  try {
+    const whatsapp = await Whatsapp.findByPk(whatsappId);
+
+    if (!whatsapp) {
+      throw new Error("Não foi possível realizar a operação");
+    }
+
+    if (messageData.number === undefined) {
+      throw new Error("O número é obrigatório");
+    }
+
+    const numberToTest = messageData.number;
+    const body = messageData.body;
+
+    const companyId = whatsapp.companyId;
+
+    const CheckValidNumber = await CheckContactNumber(numberToTest, companyId);
+    const number = CheckValidNumber.jid.replace(/\D/g, "");
+    const profilePicUrl = await GetProfilePicUrl(
+      number,
+      companyId
+    );
+    const contactData = {
+      name: `${number}`,
+      number,
+      profilePicUrl,
+      isGroup: false,
+      companyId
+    };
+
+    const contact = await CreateOrUpdateContactService(contactData);
+
+    const ticket = await FindOrCreateTicketService(contact, whatsapp.id!, 0, companyId);
+
+    if (medias) {
+      await Promise.all(
+        medias.map(async (media: Express.Multer.File) => {
+          await req.app.get("queues").messageQueue.add(
+            "SendMessage",
+            {
+              whatsappId,
+              data: {
+                number,
+                body: body ? formatBody(body, contact) : media.originalname,
+                mediaPath: media.path,
+                fileName: media.originalname
+              }
+            },
+            { removeOnComplete: true, attempts: 3 }
+          );
+        })
+      );
+    } else {
+      await SendWhatsAppMessage({ body: formatBody(body, contact), ticket });
+
+      await ticket.update({
+        lastMessage: body,
+      });
+
+    }
+
+    if (messageData.closeTicket) {
+      setTimeout(async () => {
+        await UpdateTicketService({
+          ticketId: ticket.id,
+          ticketData: { status: "closed" },
+          companyId
+        });
+      }, 1000);
+    }
+
+    SetTicketMessagesAsRead(ticket);
+
+    return res.send({ mensagem: "Mensagem enviada" });
+  } catch (err: any) {
+    if (Object.keys(err).length === 0) {
+      throw new AppError(
+        "Não foi possível enviar a mensagem, tente novamente em alguns instantes"
+      );
+    } else {
+      throw new AppError(err.message);
+    }
+  }
+};

+ 136 - 0
backend/src/controllers/PlanController.ts

@@ -0,0 +1,136 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+// import { getIO } from "../libs/socket";
+import AppError from "../errors/AppError";
+import Plan from "../models/Plan";
+
+import ListPlansService from "../services/PlanService/ListPlansService";
+import CreatePlanService from "../services/PlanService/CreatePlanService";
+import UpdatePlanService from "../services/PlanService/UpdatePlanService";
+import ShowPlanService from "../services/PlanService/ShowPlanService";
+import FindAllPlanService from "../services/PlanService/FindAllPlanService";
+import DeletePlanService from "../services/PlanService/DeletePlanService";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+type StorePlanData = {
+  name: string;
+  id?: number | string;
+  users: number | 0;
+  connections: number | 0;
+  queues: number | 0;
+  value: number;
+  useCampaigns?: boolean;
+  useSchedules?: boolean;
+  useInternalChat?: boolean;
+  useExternalApi?: boolean;
+  useKanban?: boolean;
+  useOpenAi?: boolean;
+  useIntegrations?: boolean;
+};
+
+type UpdatePlanData = {
+  name: string;
+  id?: number | string;
+  users?: number;
+  connections?: number;
+  queues?: number;
+  value?: number;
+  useCampaigns?: boolean;
+  useSchedules?: boolean;
+  useInternalChat?: boolean;
+  useExternalApi?: boolean;
+  useKanban?: boolean;
+  useOpenAi?: boolean;
+  useIntegrations?: boolean;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { plans, count, hasMore } = await ListPlansService({
+    searchParam,
+    pageNumber
+  });
+
+  return res.json({ plans, count, hasMore });
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const plans: Plan[] = await FindAllPlanService();
+
+  return res.status(200).json(plans);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const newPlan: StorePlanData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(newPlan);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const plan = await CreatePlanService(newPlan);
+
+  // const io = getIO();
+  // io.emit("plan", {
+  //   action: "create",
+  //   plan
+  // });
+
+  return res.status(200).json(plan);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const plan = await ShowPlanService(id);
+
+  return res.status(200).json(plan);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const planData: UpdatePlanData = req.body;
+
+  const schema = Yup.object().shape({
+    name: Yup.string()
+  });
+
+  try {
+    await schema.validate(planData);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const plan = await UpdatePlanService(planData);
+
+  // const io = getIO();
+  // io.emit("plan", {
+  //   action: "update",
+  //   plan
+  // });
+
+  return res.status(200).json(plan);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  const plan = await DeletePlanService(id);
+
+  return res.status(200).json(plan);
+};

+ 114 - 0
backend/src/controllers/PromptController.ts

@@ -0,0 +1,114 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import CreatePromptService from "../services/PromptServices/CreatePromptService";
+import DeletePromptService from "../services/PromptServices/DeletePromptService";
+import ListPromptsService from "../services/PromptServices/ListPromptsService";
+import ShowPromptService from "../services/PromptServices/ShowPromptService";
+import UpdatePromptService from "../services/PromptServices/UpdatePromptService";
+import Whatsapp from "../models/Whatsapp";
+import { verify } from "jsonwebtoken";
+import authConfig from "../config/auth";
+
+interface TokenPayload {
+  id: string;
+  username: string;
+  profile: string;
+  companyId: number;
+  iat: number;
+  exp: number;
+}
+
+type IndexQuery = {
+  searchParam?: string;
+  pageNumber?: string | number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { pageNumber, searchParam } = req.query as IndexQuery;
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { companyId } = decoded as TokenPayload;
+  const { prompts, count, hasMore } = await ListPromptsService({ searchParam, pageNumber, companyId });
+
+  return res.status(200).json({ prompts, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { companyId } = decoded as TokenPayload;
+  const { name, apiKey, prompt, maxTokens, temperature, promptTokens, completionTokens, totalTokens, queueId, maxMessages, model} = req.body;
+  const promptTable = await CreatePromptService({ name, apiKey, prompt, maxTokens, temperature, promptTokens, completionTokens, totalTokens, queueId, maxMessages, companyId, model });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("prompt", {
+    action: "update",
+    prompt: promptTable
+  });
+
+  return res.status(200).json(promptTable);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { promptId } = req.params;
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { companyId } = decoded as TokenPayload;
+  const prompt = await ShowPromptService({ promptId, companyId });
+
+  return res.status(200).json(prompt);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { promptId } = req.params;
+  const promptData = req.body;
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { companyId } = decoded as TokenPayload;
+
+  const prompt = await UpdatePromptService({ promptData, promptId: promptId, companyId });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("prompt", {
+    action: "update",
+    prompt
+  });
+
+  return res.status(200).json(prompt);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { promptId } = req.params;
+  const authHeader = req.headers.authorization;
+  const [, token] = authHeader.split(" ");
+  const decoded = verify(token, authConfig.secret);
+  const { companyId } = decoded as TokenPayload;
+  try {
+    const { count } = await Whatsapp.findAndCountAll({ where: { promptId: +promptId, companyId } });
+
+    if (count > 0) return res.status(200).json({ message: "Não foi possível excluir! Verifique se este prompt está sendo usado nas conexões Whatsapp!" });
+
+    await DeletePromptService(promptId, companyId);
+
+    const io = getIO();
+    io.to(`company-${companyId}-mainchannel`).emit("prompt", {
+      action: "delete",
+      intelligenceId: +promptId
+    });
+
+    return res.status(200).json({ message: "Prompt deleted" });
+  } catch (err) {
+    return res.status(500).json({ message: "Não foi possível excluir! Verifique se este prompt está sendo usado!" });
+  }
+};
+

+ 107 - 0
backend/src/controllers/QueueController.ts

@@ -0,0 +1,107 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import CreateQueueService from "../services/QueueService/CreateQueueService";
+import DeleteQueueService from "../services/QueueService/DeleteQueueService";
+import ListQueuesService from "../services/QueueService/ListQueuesService";
+import ShowQueueService from "../services/QueueService/ShowQueueService";
+import UpdateQueueService from "../services/QueueService/UpdateQueueService";
+import { isNil } from "lodash";
+
+type QueueFilter = {
+  companyId: number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId: userCompanyId } = req.user;
+  const { companyId: queryCompanyId } = req.query as unknown as QueueFilter;
+  let companyId = userCompanyId;
+
+  if (!isNil(queryCompanyId)) {
+    companyId = +queryCompanyId;
+  }
+
+  const queues = await ListQueuesService({ companyId });
+
+  return res.status(200).json(queues);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { name, color, greetingMessage, outOfHoursMessage, schedules, orderQueue, integrationId, promptId } =
+    req.body;
+  const { companyId } = req.user;
+  console.log("queue", integrationId, promptId)
+  const queue = await CreateQueueService({
+    name,
+    color,
+    greetingMessage,
+    companyId,
+    outOfHoursMessage,
+    schedules,
+    orderQueue: orderQueue === "" ? null : orderQueue,
+    integrationId: integrationId === "" ? null : integrationId,
+    promptId: promptId === "" ? null : promptId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queue`, {
+    action: "update",
+    queue
+  });
+
+  return res.status(200).json(queue);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { queueId } = req.params;
+  const { companyId } = req.user;
+
+  const queue = await ShowQueueService(queueId, companyId);
+
+  return res.status(200).json(queue);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { queueId } = req.params;
+  const { companyId } = req.user;
+  const { name, color, greetingMessage, outOfHoursMessage, schedules, orderQueue, integrationId, promptId } =
+    req.body;
+  const queue = await UpdateQueueService(queueId, {
+    name,
+    color,
+    greetingMessage,
+    outOfHoursMessage,
+    schedules,
+    orderQueue: orderQueue === "" ? null : orderQueue,
+    integrationId: integrationId === "" ? null : integrationId,
+    promptId: promptId === "" ? null : promptId
+  }, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queue`, {
+    action: "update",
+    queue
+  });
+
+  return res.status(201).json(queue);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { queueId } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteQueueService(queueId, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queue`, {
+    action: "delete",
+    queueId: +queueId
+  });
+
+  return res.status(200).send();
+};

+ 99 - 0
backend/src/controllers/QueueIntegrationController.ts

@@ -0,0 +1,99 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import CreateQueueIntegrationService from "../services/QueueIntegrationServices/CreateQueueIntegrationService";
+import DeleteQueueIntegrationService from "../services/QueueIntegrationServices/DeleteQueueIntegrationService";
+import ListQueueIntegrationService from "../services/QueueIntegrationServices/ListQueueIntegrationService";
+import ShowQueueIntegrationService from "../services/QueueIntegrationServices/ShowQueueIntegrationService";
+import UpdateQueueIntegrationService from "../services/QueueIntegrationServices/UpdateQueueIntegrationService";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { queueIntegrations, count, hasMore } = await ListQueueIntegrationService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.status(200).json({ queueIntegrations, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { type, name, projectName, jsonContent, language, urlN8N,
+    typebotExpires,
+    typebotKeywordFinish,
+    typebotSlug,
+    typebotUnknownMessage,
+    typebotKeywordRestart,
+    typebotRestartMessage } = req.body;
+  const { companyId } = req.user;
+  const queueIntegration = await CreateQueueIntegrationService({
+    type, name, projectName, jsonContent, language, urlN8N, companyId,
+    typebotExpires,
+    typebotKeywordFinish,
+    typebotSlug,
+    typebotUnknownMessage,
+    typebotKeywordRestart,
+    typebotRestartMessage
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queueIntegration`, {
+    action: "create",
+    queueIntegration
+  });
+
+  return res.status(200).json(queueIntegration);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { integrationId } = req.params;
+  const { companyId } = req.user;
+
+  const queueIntegration = await ShowQueueIntegrationService(integrationId, companyId);
+
+  return res.status(200).json(queueIntegration);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { integrationId } = req.params;
+  const integrationData = req.body;
+  const { companyId } = req.user;
+
+  const queueIntegration = await UpdateQueueIntegrationService({ integrationData, integrationId, companyId });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queueIntegration`, {
+    action: "update",
+    queueIntegration
+  });
+
+  return res.status(201).json(queueIntegration);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { integrationId } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteQueueIntegrationService(integrationId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-queueIntegration`, {
+    action: "delete",
+    integrationId: +integrationId
+  });
+
+  return res.status(200).send();
+};

+ 60 - 0
backend/src/controllers/QueueOptionController.ts

@@ -0,0 +1,60 @@
+import { Request, Response } from "express";
+
+import CreateService from "../services/QueueOptionService/CreateService";
+import ListService from "../services/QueueOptionService/ListService";
+import UpdateService from "../services/QueueOptionService/UpdateService";
+import ShowService from "../services/QueueOptionService/ShowService";
+import DeleteService from "../services/QueueOptionService/DeleteService";
+
+type FilterList = {
+  queueId: string | number;
+  queueOptionId: string | number;
+  parentId: string | number | boolean;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { queueId, queueOptionId, parentId } = req.query as FilterList;
+
+  const queueOptions = await ListService({ queueId, queueOptionId, parentId });
+
+  return res.json(queueOptions);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const queueOptionData = req.body;
+
+  const queueOption = await CreateService(queueOptionData);
+
+  return res.status(200).json(queueOption);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { queueOptionId } = req.params;
+
+  const queueOption = await ShowService(queueOptionId);
+
+  return res.status(200).json(queueOption);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { queueOptionId } = req.params
+  const queueOptionData = req.body;
+
+  const queueOption = await UpdateService(queueOptionId, queueOptionData);
+
+  return res.status(200).json(queueOption);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { queueOptionId } = req.params
+
+  await DeleteService(queueOptionId);
+
+  return res.status(200).json({ message: "Option Delected" });
+};

+ 197 - 0
backend/src/controllers/QuickMessageController.ts

@@ -0,0 +1,197 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/QuickMessageService/ListService";
+import CreateService from "../services/QuickMessageService/CreateService";
+import ShowService from "../services/QuickMessageService/ShowService";
+import UpdateService from "../services/QuickMessageService/UpdateService";
+import DeleteService from "../services/QuickMessageService/DeleteService";
+import FindService from "../services/QuickMessageService/FindService";
+
+import QuickMessage from "../models/QuickMessage";
+
+import { head } from "lodash";
+import fs from "fs";
+import path from "path";
+
+import AppError from "../errors/AppError";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  userId: string | number;
+};
+
+type StoreData = {
+  shortcode: string;
+  message: string;
+  userId: number | number;
+};
+
+type FindParams = {
+  companyId: string;
+  userId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber, userId } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId,
+    userId
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    shortcode: Yup.string().required(),
+    message: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data,
+    companyId,
+    userId: req.user.id
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    shortcode: Yup.string().required(),
+    message: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    userId: req.user.id,
+    id,
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Contact deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as FindParams;
+  const records: QuickMessage[] = await FindService(params);
+
+  return res.status(200).json(records);
+};
+
+export const mediaUpload = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const files = req.files as Express.Multer.File[];
+  const file = head(files);
+
+  try {
+    const quickmessage = await QuickMessage.findByPk(id);
+    
+    quickmessage.update ({
+      mediaPath: file.filename,
+      mediaName: file.originalname
+    });
+
+    return res.send({ mensagem: "Arquivo Anexado" });
+    } catch (err: any) {
+      throw new AppError(err.message);
+  }
+};
+
+export const deleteMedia = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user
+
+  try {
+    const quickmessage = await QuickMessage.findByPk(id);
+    const filePath = path.resolve("public","quickMessage",quickmessage.mediaName);
+    const fileExists = fs.existsSync(filePath);
+    if (fileExists) {
+      fs.unlinkSync(filePath);
+    }
+    quickmessage.update ({
+      mediaPath: null,
+      mediaName: null
+    });
+
+    return res.send({ mensagem: "Arquivo Excluído" });
+    } catch (err: any) {
+      throw new AppError(err.message);
+  }
+};

+ 146 - 0
backend/src/controllers/QuickMessageController_OLD.ts

@@ -0,0 +1,146 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import ListService from "../services/QuickMessageService/ListService";
+import CreateService from "../services/QuickMessageService/CreateService";
+import ShowService from "../services/QuickMessageService/ShowService";
+import UpdateService from "../services/QuickMessageService/UpdateService";
+import DeleteService from "../services/QuickMessageService/DeleteService";
+import FindService from "../services/QuickMessageService/FindService";
+
+import QuickMessage from "../models/QuickMessage";
+
+import AppError from "../errors/AppError";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  userId: string | number;
+};
+
+type StoreData = {
+  shortcode: string;
+  message: string;
+  userId: number | number;
+};
+
+type FindParams = {
+  companyId: string;
+  userId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber, userId } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { records, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId,
+    userId
+  });
+
+  return res.json({ records, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const data = req.body as StoreData;
+
+  const schema = Yup.object().shape({
+    shortcode: Yup.string().required(),
+    message: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const record = await CreateService({
+    ...data,
+    companyId,
+    userId: req.user.id
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "create",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const record = await ShowService(id);
+
+  return res.status(200).json(record);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body as StoreData;
+  const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    shortcode: Yup.string().required(),
+    message: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(data);
+  } catch (err: any) {
+    throw new AppError(err.message);
+  }
+
+  const { id } = req.params;
+
+  const record = await UpdateService({
+    ...data,
+    userId: req.user.id,
+    id,
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "update",
+    record
+  });
+
+  return res.status(200).json(record);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(id);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-quickmessage`, {
+    action: "delete",
+    id
+  });
+
+  return res.status(200).json({ message: "Contact deleted" });
+};
+
+export const findList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const params = req.query as FindParams;
+  const records: QuickMessage[] = await FindService(params);
+
+  return res.status(200).json(records);
+};

+ 154 - 0
backend/src/controllers/ScheduleController.ts

@@ -0,0 +1,154 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import AppError from "../errors/AppError";
+
+import CreateService from "../services/ScheduleServices/CreateService";
+import ListService from "../services/ScheduleServices/ListService";
+import UpdateService from "../services/ScheduleServices/UpdateService";
+import ShowService from "../services/ScheduleServices/ShowService";
+import DeleteService from "../services/ScheduleServices/DeleteService";
+import Schedule from "../models/Schedule";
+import path from "path";
+import fs from "fs";
+import { head } from "lodash";
+
+type IndexQuery = {
+  searchParam?: string;
+  contactId?: number | string;
+  userId?: number | string;
+  pageNumber?: string | number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { contactId, userId, pageNumber, searchParam } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { schedules, count, hasMore } = await ListService({
+    searchParam,
+    contactId,
+    userId,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ schedules, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const {
+    body,
+    sendAt,
+    contactId,
+    userId
+  } = req.body;
+  const { companyId } = req.user;
+
+  const schedule = await CreateService({
+    body,
+    sendAt,
+    contactId,
+    companyId,
+    userId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("schedule", {
+    action: "create",
+    schedule
+  });
+
+  return res.status(200).json(schedule);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { scheduleId } = req.params;
+  const { companyId } = req.user;
+
+  const schedule = await ShowService(scheduleId, companyId);
+
+  return res.status(200).json(schedule);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  const { scheduleId } = req.params;
+  const scheduleData = req.body;
+  const { companyId } = req.user;
+
+  const schedule = await UpdateService({ scheduleData, id: scheduleId, companyId });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("schedule", {
+    action: "update",
+    schedule
+  });
+
+  return res.status(200).json(schedule);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { scheduleId } = req.params;
+  const { companyId } = req.user;
+
+  await DeleteService(scheduleId, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("schedule", {
+    action: "delete",
+    scheduleId
+  });
+
+  return res.status(200).json({ message: "Schedule deleted" });
+};
+
+export const mediaUpload = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+  const files = req.files as Express.Multer.File[];
+  const file = head(files);
+
+  try {
+    const schedule = await Schedule.findByPk(id);
+    schedule.mediaPath = file.filename;
+    schedule.mediaName = file.originalname;
+
+    await schedule.save();
+    return res.send({ mensagem: "Arquivo Anexado" });
+    } catch (err: any) {
+      throw new AppError(err.message);
+  }
+};
+
+export const deleteMedia = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  try {
+    const schedule = await Schedule.findByPk(id);
+    const filePath = path.resolve("public", schedule.mediaPath);
+    const fileExists = fs.existsSync(filePath);
+    if (fileExists) {
+      fs.unlinkSync(filePath);
+    }
+    schedule.mediaPath = null;
+    schedule.mediaName = null;
+    await schedule.save();
+    return res.send({ mensagem: "Arquivo Excluído" });
+    } catch (err: any) {
+      throw new AppError(err.message);
+  }
+};

+ 81 - 0
backend/src/controllers/SessionController.ts

@@ -0,0 +1,81 @@
+import { Request, Response } from "express";
+import AppError from "../errors/AppError";
+import { getIO } from "../libs/socket";
+
+import AuthUserService from "../services/UserServices/AuthUserService";
+import { SendRefreshToken } from "../helpers/SendRefreshToken";
+import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService";
+import FindUserFromToken from "../services/AuthServices/FindUserFromToken";
+import User from "../models/User";
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { email, password } = req.body;
+
+  const { token, serializedUser, refreshToken } = await AuthUserService({
+    email,
+    password
+  });
+
+  SendRefreshToken(res, refreshToken);
+
+  const io = getIO();
+  io.to(`user-${serializedUser.id}`).emit(`company-${serializedUser.companyId}-auth`, {
+    action: "update",
+    user: {
+      id: serializedUser.id,
+      email: serializedUser.email,
+      companyId: serializedUser.companyId
+    }
+  });
+
+  return res.status(200).json({
+    token,
+    user: serializedUser
+  });
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+
+  const token: string = req.cookies.jrt;
+
+  if (!token) {
+    throw new AppError("ERR_SESSION_EXPIRED", 401);
+  }
+
+  const { user, newToken, refreshToken } = await RefreshTokenService(
+    res,
+    token
+  );
+
+  SendRefreshToken(res, refreshToken);
+
+  return res.json({ token: newToken, user });
+};
+
+export const me = async (req: Request, res: Response): Promise<Response> => {
+  const token: string = req.cookies.jrt;
+  const user = await FindUserFromToken(token);
+  const { id, profile, super: superAdmin } = user;
+
+  if (!token) {
+    throw new AppError("ERR_SESSION_EXPIRED", 401);
+  }
+
+  return res.json({ id, profile, super: superAdmin });
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.user;
+  const user = await User.findByPk(id);
+  await user.update({ online: false });
+
+  res.clearCookie("jrt");
+
+  return res.send();
+};

+ 45 - 0
backend/src/controllers/SettingController.ts

@@ -0,0 +1,45 @@
+import { Request, Response } from "express";
+
+import { getIO } from "../libs/socket";
+import AppError from "../errors/AppError";
+
+import UpdateSettingService from "../services/SettingServices/UpdateSettingService";
+import ListSettingsService from "../services/SettingServices/ListSettingsService";
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+
+  // if (req.user.profile !== "admin") {
+  //   throw new AppError("ERR_NO_PERMISSION", 403);
+  // }
+
+  const settings = await ListSettingsService({ companyId });
+
+  return res.status(200).json(settings);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+  const { settingKey: key } = req.params;
+  const { value } = req.body;
+  const { companyId } = req.user;
+
+  const setting = await UpdateSettingService({
+    key,
+    value,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-settings`, {
+    action: "update",
+    setting
+  });
+
+  return res.status(200).json(setting);
+};

+ 198 - 0
backend/src/controllers/SubscriptionController.ts

@@ -0,0 +1,198 @@
+import { Request, Response } from "express";
+import express from "express";
+import * as Yup from "yup";
+import Gerencianet from "gn-api-sdk-typescript";
+import AppError from "../errors/AppError";
+
+import options from "../config/Gn";
+import Company from "../models/Company";
+import Invoices from "../models/Invoices";
+import Subscriptions from "../models/Subscriptions";
+import { getIO } from "../libs/socket";
+import UpdateUserService from "../services/UserServices/UpdateUserService";
+
+const app = express();
+
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const gerencianet = Gerencianet(options);
+  return res.json(gerencianet.getSubscriptions());
+};
+
+export const createSubscription = async (
+  req: Request,
+  res: Response
+  ): Promise<Response> => {
+    const gerencianet = Gerencianet(options);
+    const { companyId } = req.user;
+
+  const schema = Yup.object().shape({
+    price: Yup.string().required(),
+    users: Yup.string().required(),
+    connections: Yup.string().required()
+  });
+
+  if (!(await schema.isValid(req.body))) {
+    throw new AppError("Validation fails", 400);
+  }
+
+  const {
+    firstName,
+    price,
+    users,
+    connections,
+    address2,
+    city,
+    state,
+    zipcode,
+    country,
+    plan,
+    invoiceId
+  } = req.body;
+
+  const body = {
+    calendario: {
+      expiracao: 3600
+    },
+    valor: {
+      original: price.toLocaleString("pt-br", { minimumFractionDigits: 2 }).replace(",", ".")
+    },
+    chave: process.env.GERENCIANET_PIX_KEY,
+    solicitacaoPagador: `#Fatura:${invoiceId}`
+    };
+  try {
+    const pix = await gerencianet.pixCreateImmediateCharge(null, body);
+
+    const qrcode = await gerencianet.pixGenerateQRCode({
+      id: pix.loc.id
+    });
+
+    const updateCompany = await Company.findOne();
+
+    if (!updateCompany) {
+      throw new AppError("Company not found", 404);
+    }
+
+
+/*     await Subscriptions.create({
+      companyId,
+      isActive: false,
+      userPriceCents: users,
+      whatsPriceCents: connections,
+      lastInvoiceUrl: pix.location,
+      lastPlanChange: new Date(),
+      providerSubscriptionId: pix.loc.id,
+      expiresAt: new Date()
+    }); */
+
+/*     const { id } = req.user;
+    const userData = {};
+    const userId = id;
+    const requestUserId = parseInt(id);
+    const user = await UpdateUserService({ userData, userId, companyId, requestUserId }); */
+
+    /*     const io = getIO();
+        io.emit("user", {
+          action: "update",
+          user
+        }); */
+
+
+    return res.json({
+      ...pix,
+      qrcode,
+
+    });
+  } catch (error) {
+    throw new AppError("Validation fails", 400);
+  }
+};
+
+export const createWebhook = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const schema = Yup.object().shape({
+    chave: Yup.string().required(),
+    url: Yup.string().required()
+  });
+
+  if (!(await schema.isValid(req.body))) {
+    throw new AppError("Validation fails", 400);
+  }
+
+  const { chave, url } = req.body;
+
+  const body = {
+    webhookUrl: url
+  };
+
+  const params = {
+    chave
+  };
+
+  try {
+    const gerencianet = Gerencianet(options);
+    const create = await gerencianet.pixConfigWebhook(params, body);
+    return res.json(create);
+  } catch (error) {
+    console.log(error);
+  }
+};
+
+export const webhook = async (
+  req: Request,
+  res: Response
+  ): Promise<Response> => {
+  const { type } = req.params;
+  const { evento } = req.body;
+  if (evento === "teste_webhook") {
+    return res.json({ ok: true });
+  }
+  if (req.body.pix) {
+    const gerencianet = Gerencianet(options);
+    req.body.pix.forEach(async (pix: any) => {
+      const detahe = await gerencianet.pixDetailCharge({
+        txid: pix.txid
+      });
+
+      if (detahe.status === "CONCLUIDA") {
+        const { solicitacaoPagador } = detahe;
+        const invoiceID = solicitacaoPagador.replace("#Fatura:", "");
+        const invoices = await Invoices.findByPk(invoiceID);
+        const companyId =invoices.companyId;
+        const company = await Company.findByPk(companyId);
+
+        const expiresAt = new Date(company.dueDate);
+        expiresAt.setDate(expiresAt.getDate() + 30);
+        const date = expiresAt.toISOString().split("T")[0];
+
+        if (company) {
+          await company.update({
+            dueDate: date
+          });
+         const invoi = await invoices.update({
+            id: invoiceID,
+            status: 'paid'
+          });
+          await company.reload();
+          const io = getIO();
+          const companyUpdate = await Company.findOne({
+            where: {
+              id: companyId
+            }
+          });
+
+          io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-payment`, {
+            action: detahe.status,
+            company: companyUpdate
+          });
+        }
+
+      }
+    });
+
+  }
+
+  return res.json({ ok: true });
+};

+ 128 - 0
backend/src/controllers/TagController.ts

@@ -0,0 +1,128 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import AppError from "../errors/AppError";
+
+import CreateService from "../services/TagServices/CreateService";
+import ListService from "../services/TagServices/ListService";
+import UpdateService from "../services/TagServices/UpdateService";
+import ShowService from "../services/TagServices/ShowService";
+import DeleteService from "../services/TagServices/DeleteService";
+import SimpleListService from "../services/TagServices/SimpleListService";
+import SyncTagService from "../services/TagServices/SyncTagsService";
+import KanbanListService from "../services/TagServices/KanbanListService";
+
+type IndexQuery = {
+  searchParam?: string;
+  pageNumber?: string | number;
+  kanban?: number;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { pageNumber, searchParam } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const { tags, count, hasMore } = await ListService({
+    searchParam,
+    pageNumber,
+    companyId
+  });
+
+  return res.json({ tags, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { name, color, kanban } = req.body;
+  const { companyId } = req.user;
+
+  const tag = await CreateService({
+    name,
+    color,
+    companyId,
+    kanban
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit("tag", {
+    action: "create",
+    tag
+  });
+
+  return res.status(200).json(tag);
+};
+
+export const kanban = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+
+  const tags = await KanbanListService({ companyId });
+
+  return res.json({lista:tags});
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { tagId } = req.params;
+
+  const tag = await ShowService(tagId);
+
+  return res.status(200).json(tag);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  const { tagId } = req.params;
+  const tagData = req.body;
+
+  const tag = await UpdateService({ tagData, id: tagId });
+
+  const io = getIO();
+  io.to(`company-${req.user.companyId}-mainchannel`).emit("tag", {
+    action: "update",
+    tag
+  });
+
+  return res.status(200).json(tag);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { tagId } = req.params;
+
+  await DeleteService(tagId);
+
+  const io = getIO();
+  io.to(`company-${req.user.companyId}-mainchannel`).emit("tag", {
+    action: "delete",
+    tagId
+  });
+
+  return res.status(200).json({ message: "Tag deleted" });
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam } = req.query as IndexQuery;
+  const { companyId } = req.user;
+
+  const tags = await SimpleListService({ searchParam, companyId });
+
+  return res.json(tags);
+};
+
+export const syncTags = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const data = req.body;
+  const { companyId } = req.user;
+
+  const tags = await SyncTagService({ ...data, companyId });
+
+  return res.json(tags);
+};

+ 224 - 0
backend/src/controllers/TicketController.ts

@@ -0,0 +1,224 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import Ticket from "../models/Ticket";
+
+import CreateTicketService from "../services/TicketServices/CreateTicketService";
+import DeleteTicketService from "../services/TicketServices/DeleteTicketService";
+import ListTicketsService from "../services/TicketServices/ListTicketsService";
+import ShowTicketUUIDService from "../services/TicketServices/ShowTicketFromUUIDService";
+import ShowTicketService from "../services/TicketServices/ShowTicketService";
+import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
+import ListTicketsServiceKanban from "../services/TicketServices/ListTicketsServiceKanban";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+  status: string;
+  date: string;
+  updatedAt?: string;
+  showAll: string;
+  withUnreadMessages: string;
+  queueIds: string;
+  tags: string;
+  users: string;
+};
+
+interface TicketData {
+  contactId: number;
+  status: string;
+  queueId: number;
+  userId: number;
+  whatsappId: string;
+  useIntegration: boolean;
+  promptId: number;
+  integrationId: number;
+}
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const {
+    pageNumber,
+    status,
+    date,
+    updatedAt,
+    searchParam,
+    showAll,
+    queueIds: queueIdsStringified,
+    tags: tagIdsStringified,
+    users: userIdsStringified,
+    withUnreadMessages
+  } = req.query as IndexQuery;
+
+  const userId = req.user.id;
+  const { companyId } = req.user;
+
+  let queueIds: number[] = [];
+  let tagsIds: number[] = [];
+  let usersIds: number[] = [];
+
+  if (queueIdsStringified) {
+    queueIds = JSON.parse(queueIdsStringified);
+  }
+
+  if (tagIdsStringified) {
+    tagsIds = JSON.parse(tagIdsStringified);
+  }
+
+  if (userIdsStringified) {
+    usersIds = JSON.parse(userIdsStringified);
+  }
+
+  const { tickets, count, hasMore } = await ListTicketsService({
+    searchParam,
+    tags: tagsIds,
+    users: usersIds,
+    pageNumber,
+    status,
+    date,
+    updatedAt,
+    showAll,
+    userId,
+    queueIds,
+    withUnreadMessages,
+    companyId,
+
+
+  });
+  return res.status(200).json({ tickets, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { contactId, status, userId, queueId, whatsappId }: TicketData = req.body;
+  const { companyId } = req.user;
+
+  const ticket = await CreateTicketService({
+    contactId,
+    status,
+    userId,
+    companyId,
+    queueId,
+    whatsappId
+  });
+
+  const io = getIO();
+  io.to(ticket.status).emit(`company-${companyId}-ticket`, {
+    action: "update",
+    ticket
+  });
+  return res.status(200).json(ticket);
+};
+
+export const kanban = async (req: Request, res: Response): Promise<Response> => {
+  const {
+    pageNumber,
+    status,
+    date,
+    updatedAt,
+    searchParam,
+    showAll,
+    queueIds: queueIdsStringified,
+    tags: tagIdsStringified,
+    users: userIdsStringified,
+    withUnreadMessages
+  } = req.query as IndexQuery;
+
+
+  const userId = req.user.id;
+  const { companyId } = req.user;
+
+  let queueIds: number[] = [];
+  let tagsIds: number[] = [];
+  let usersIds: number[] = [];
+
+  if (queueIdsStringified) {
+    queueIds = JSON.parse(queueIdsStringified);
+  }
+
+  if (tagIdsStringified) {
+    tagsIds = JSON.parse(tagIdsStringified);
+  }
+
+  if (userIdsStringified) {
+    usersIds = JSON.parse(userIdsStringified);
+  }
+
+  const { tickets, count, hasMore } = await ListTicketsServiceKanban({
+    searchParam,
+    tags: tagsIds,
+    users: usersIds,
+    pageNumber,
+    status,
+    date,
+    updatedAt,
+    showAll,
+    userId,
+    queueIds,
+    withUnreadMessages,
+    companyId
+
+  });
+
+  return res.status(200).json({ tickets, count, hasMore });
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId } = req.params;
+  const { companyId } = req.user;
+
+  const contact = await ShowTicketService(ticketId, companyId);
+  return res.status(200).json(contact);
+};
+
+export const showFromUUID = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { uuid } = req.params;
+
+  const ticket: Ticket = await ShowTicketUUIDService(uuid);
+
+  return res.status(200).json(ticket);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { ticketId } = req.params;
+  const ticketData: TicketData = req.body;
+  const { companyId, id} = req.user;
+
+  const { ticket } = await UpdateTicketService({
+    ticketData,
+    ticketId,
+    companyId,
+    actionUserId: id
+  });
+
+
+  return res.status(200).json(ticket);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { ticketId } = req.params;
+  const { companyId } = req.user;
+
+  await ShowTicketService(ticketId, companyId);
+
+  const ticket = await DeleteTicketService(ticketId);
+
+  const io = getIO();
+  io.to(ticketId)
+    .to(`company-${companyId}-${ticket.status}`)
+    .to(`company-${companyId}-notification`)
+    .to(`queue-${ticket.queueId}-${ticket.status}`)
+    .to(`queue-${ticket.queueId}-notification`)
+    .emit(`company-${companyId}-ticket`, {
+      action: "delete",
+      ticketId: +ticketId
+    });
+
+  return res.status(200).json({ message: "ticket deleted" });
+};

+ 138 - 0
backend/src/controllers/TicketNoteController.ts

@@ -0,0 +1,138 @@
+import * as Yup from "yup";
+import { Request, Response } from "express";
+import AppError from "../errors/AppError";
+import TicketNote from "../models/TicketNote";
+
+import ListTicketNotesService from "../services/TicketNoteService/ListTicketNotesService";
+import CreateTicketNoteService from "../services/TicketNoteService/CreateTicketNoteService";
+import UpdateTicketNoteService from "../services/TicketNoteService/UpdateTicketNoteService";
+import ShowTicketNoteService from "../services/TicketNoteService/ShowTicketNoteService";
+import FindAllTicketNotesService from "../services/TicketNoteService/FindAllTicketNotesService";
+import DeleteTicketNoteService from "../services/TicketNoteService/DeleteTicketNoteService";
+import FindNotesByContactIdAndTicketId from "../services/TicketNoteService/FindNotesByContactIdAndTicketId";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+type StoreTicketNoteData = {
+  note: string;
+  userId: number;
+  contactId: number | 0;
+  ticketId: number | 0;
+  id?: number | string;
+};
+
+type UpdateTicketNoteData = {
+  note: string;
+  id?: number | string;
+  userId?: number | 0;
+  contactId?: number | 0;
+  ticketId?: number | 0;
+};
+
+type QueryFilteredNotes = {
+  contactId: number | string;
+  ticketId: number | string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+
+  const { ticketNotes, count, hasMore } = await ListTicketNotesService({
+    searchParam,
+    pageNumber
+  });
+
+  return res.json({ ticketNotes, count, hasMore });
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const ticketNotes: TicketNote[] = await FindAllTicketNotesService();
+
+  return res.status(200).json(ticketNotes);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const newTicketNote: StoreTicketNoteData = req.body;
+  const { id: userId } = req.user;
+
+  const schema = Yup.object().shape({
+    note: Yup.string().required()
+  });
+
+  try {
+    await schema.validate(newTicketNote);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const ticketNote = await CreateTicketNoteService({
+    ...newTicketNote,
+    userId
+  });
+
+  return res.status(200).json(ticketNote);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { id } = req.params;
+
+  const ticketNote = await ShowTicketNoteService(id);
+
+  return res.status(200).json(ticketNote);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const ticketNote: UpdateTicketNoteData = req.body;
+
+  const schema = Yup.object().shape({
+    note: Yup.string()
+  });
+
+  try {
+    await schema.validate(ticketNote);
+  } catch (err) {
+    throw new AppError(err.message);
+  }
+
+  const recordUpdated = await UpdateTicketNoteService(ticketNote);
+
+  return res.status(200).json(recordUpdated);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { id } = req.params;
+
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  await DeleteTicketNoteService(id);
+
+  return res.status(200).json({ message: "Observação removida" });
+};
+
+export const findFilteredList = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  try {
+    const { contactId, ticketId } = req.query as QueryFilteredNotes;
+    const notes: TicketNote[] = await FindNotesByContactIdAndTicketId({
+      contactId,
+      ticketId
+    });
+
+    return res.status(200).json(notes);
+  } catch (e) {
+    return res.status(500).json({ message: e });
+  }
+};

+ 57 - 0
backend/src/controllers/TicketTagController.ts

@@ -0,0 +1,57 @@
+import { Request, Response } from "express";
+import AppError from "../errors/AppError";
+import TicketTag from '../models/TicketTag';
+import Tag from '../models/Tag'
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId, tagId } = req.params;
+
+  try {
+    const ticketTag = await TicketTag.create({ ticketId, tagId });
+    return res.status(201).json(ticketTag);
+  } catch (error) {
+    return res.status(500).json({ error: 'Failed to store ticket tag.' });
+  }
+};
+
+/*
+export const remove = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId } = req.params;
+
+
+
+  try {
+    await TicketTag.destroy({ where: { ticketId } });
+    return res.status(200).json({ message: 'Ticket tags removed successfully.' });
+  } catch (error) {
+    return res.status(500).json({ error: 'Failed to remove ticket tags.' });
+  }
+};
+*/
+export const remove = async (req: Request, res: Response): Promise<Response> => {
+  const { ticketId } = req.params;
+
+
+  try {
+    // Retrieve tagIds associated with the provided ticketId from TicketTags
+    const ticketTags = await TicketTag.findAll({ where: { ticketId } });
+    const tagIds = ticketTags.map((ticketTag) => ticketTag.tagId);
+
+    // Find the tagIds with kanban = 1 in the Tags table
+    const tagsWithKanbanOne = await Tag.findAll({
+      where: {
+        id: tagIds,
+        kanban: 1,
+      },
+    });
+
+    // Remove the tagIds with kanban = 1 from TicketTags
+    const tagIdsWithKanbanOne = tagsWithKanbanOne.map((tag) => tag.id);
+    if (tagIdsWithKanbanOne)
+    await TicketTag.destroy({ where: { ticketId, tagId: tagIdsWithKanbanOne } });
+
+    return res.status(200).json({ message: 'Ticket tags removed successfully.' });
+  } catch (error) {
+    return res.status(500).json({ error: 'Failed to remove ticket tags.' });
+  }
+};

+ 171 - 0
backend/src/controllers/UserController.ts

@@ -0,0 +1,171 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+
+import CheckSettingsHelper from "../helpers/CheckSettings";
+import AppError from "../errors/AppError";
+
+import CreateUserService from "../services/UserServices/CreateUserService";
+import ListUsersService from "../services/UserServices/ListUsersService";
+import UpdateUserService from "../services/UserServices/UpdateUserService";
+import ShowUserService from "../services/UserServices/ShowUserService";
+import DeleteUserService from "../services/UserServices/DeleteUserService";
+import SimpleListService from "../services/UserServices/SimpleListService";
+import User from "../models/User";
+import SetLanguageCompanyService from "../services/UserServices/SetLanguageCompanyService";
+
+type IndexQuery = {
+  searchParam: string;
+  pageNumber: string;
+};
+
+type ListQueryParams = {
+  companyId: string;
+};
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { searchParam, pageNumber } = req.query as IndexQuery;
+  const { companyId, profile } = req.user;
+
+  const { users, count, hasMore } = await ListUsersService({
+    searchParam,
+    pageNumber,
+    companyId,
+    profile
+  });
+
+  return res.json({ users, count, hasMore });
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const {
+    email,
+    password,
+    name,
+    profile,
+    companyId: bodyCompanyId,
+    queueIds,
+    whatsappId,
+	allTicket
+  } = req.body;
+  let userCompanyId: number | null = null;
+
+  let requestUser: User = null;
+
+  if (req.user !== undefined) {
+    const { companyId: cId } = req.user;
+    userCompanyId = cId;
+    requestUser = await User.findByPk(req.user.id);
+  }
+
+  const newUserCompanyId = bodyCompanyId || userCompanyId;
+
+  if (req.url === "/signup") {
+    if (await CheckSettingsHelper("userCreation") === "disabled") {
+      throw new AppError("ERR_USER_CREATION_DISABLED", 403);
+    }
+  } else if (req.user?.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  } else if (newUserCompanyId !== req.user?.companyId && !requestUser?.super) {
+    throw new AppError("ERR_NO_SUPER", 403);
+  }
+
+  const user = await CreateUserService({
+    email,
+    password,
+    name,
+    profile,
+    companyId: newUserCompanyId,
+    queueIds,
+    whatsappId,
+	allTicket
+  });
+
+  const io = getIO();
+  io.to(`company-${userCompanyId}-mainchannel`).emit(`company-${userCompanyId}-user`, {
+    action: "create",
+    user
+  });
+
+  return res.status(200).json(user);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { userId } = req.params;
+
+  const user = await ShowUserService(userId);
+
+  return res.status(200).json(user);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  const { id: requestUserId, companyId } = req.user;
+  const { userId } = req.params;
+  const userData = req.body;
+
+  const user = await UpdateUserService({
+    userData,
+    userId,
+    companyId,
+    requestUserId: +requestUserId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-user`, {
+    action: "update",
+    user
+  });
+
+  return res.status(200).json(user);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { userId } = req.params;
+  const { companyId } = req.user;
+
+  if (req.user.profile !== "admin") {
+    throw new AppError("ERR_NO_PERMISSION", 403);
+  }
+
+  await DeleteUserService(userId, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-user`, {
+    action: "delete",
+    userId
+  });
+
+  return res.status(200).json({ message: "User deleted" });
+};
+
+export const list = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.query;
+  const { companyId: userCompanyId } = req.user;
+
+  const users = await SimpleListService({
+    companyId: companyId ? +companyId : userCompanyId
+  });
+
+  return res.status(200).json(users);
+};
+
+export const setLanguage = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const {newLanguage} = req.params;
+
+  if( newLanguage !== "pt" && newLanguage !== "en" && newLanguage !== "es" )
+    throw new AppError("ERR_INTERNAL_SERVER_ERROR", 500);
+
+  await SetLanguageCompanyService( companyId, newLanguage );
+
+  return res.status(200).json({message: "Language updated successfully"});
+}

+ 7 - 0
backend/src/controllers/VersionController.ts

@@ -0,0 +1,7 @@
+import { Request, Response } from "express";
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  return res.status(200).json({
+    version: process.env.npm_package_version
+  });
+};

+ 166 - 0
backend/src/controllers/WhatsAppController.ts

@@ -0,0 +1,166 @@
+import { Request, Response } from "express";
+import { getIO } from "../libs/socket";
+import { removeWbot } from "../libs/wbot";
+import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
+
+import CreateWhatsAppService from "../services/WhatsappService/CreateWhatsAppService";
+import DeleteWhatsAppService from "../services/WhatsappService/DeleteWhatsAppService";
+import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService";
+import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
+import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
+
+interface WhatsappData {
+  name: string;
+  queueIds: number[];
+  companyId: number;
+  greetingMessage?: string;
+  complationMessage?: string;
+  outOfHoursMessage?: string;
+  ratingMessage?: string;
+  status?: string;
+  isDefault?: boolean;
+  token?: string;
+  //sendIdQueue?: number;
+  //timeSendQueue?: number;
+  transferQueueId?: number;
+  timeToTransfer?: number;  
+  promptId?: number;
+  maxUseBotQueues?: number;
+  timeUseBotQueues?: number;
+  expiresTicket?: number;
+  expiresInactiveMessage?: string;
+}
+
+interface QueryParams {
+  session?: number | string;
+}
+
+export const index = async (req: Request, res: Response): Promise<Response> => {
+  const { companyId } = req.user;
+  const { session } = req.query as QueryParams;
+  const whatsapps = await ListWhatsAppsService({ companyId, session });
+
+  return res.status(200).json(whatsapps);
+};
+
+export const store = async (req: Request, res: Response): Promise<Response> => {
+  const {
+    name,
+    status,
+    isDefault,
+    greetingMessage,
+    complationMessage,
+    outOfHoursMessage,
+    queueIds,
+    token,
+    //timeSendQueue,
+    //sendIdQueue,
+	transferQueueId,
+	timeToTransfer,
+    promptId,
+    maxUseBotQueues,
+    timeUseBotQueues,
+    expiresTicket,
+    expiresInactiveMessage
+  }: WhatsappData = req.body;
+  const { companyId } = req.user;
+
+  const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({
+    name,
+    status,
+    isDefault,
+    greetingMessage,
+    complationMessage,
+    outOfHoursMessage,
+    queueIds,
+    companyId,
+    token,
+    //timeSendQueue,
+    //sendIdQueue,
+	transferQueueId,
+	timeToTransfer,	
+    promptId,
+    maxUseBotQueues,
+    timeUseBotQueues,
+    expiresTicket,
+    expiresInactiveMessage
+  });
+
+  StartWhatsAppSession(whatsapp, companyId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-whatsapp`, {
+    action: "update",
+    whatsapp
+  });
+
+  if (oldDefaultWhatsapp) {
+    io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-whatsapp`, {
+      action: "update",
+      whatsapp: oldDefaultWhatsapp
+    });
+  }
+
+  return res.status(200).json(whatsapp);
+};
+
+export const show = async (req: Request, res: Response): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const { companyId } = req.user;
+  const { session } = req.query;
+
+  const whatsapp = await ShowWhatsAppService(whatsappId, companyId, session);
+
+  return res.status(200).json(whatsapp);
+};
+
+export const update = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const whatsappData = req.body;
+  const { companyId } = req.user;
+
+  const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({
+    whatsappData,
+    whatsappId,
+    companyId
+  });
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-whatsapp`, {
+    action: "update",
+    whatsapp
+  });
+
+  if (oldDefaultWhatsapp) {
+    io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-whatsapp`, {
+      action: "update",
+      whatsapp: oldDefaultWhatsapp
+    });
+  }
+
+  return res.status(200).json(whatsapp);
+};
+
+export const remove = async (
+  req: Request,
+  res: Response
+): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const { companyId } = req.user;
+
+  await ShowWhatsAppService(whatsappId, companyId);
+
+  await DeleteWhatsAppService(whatsappId);
+  removeWbot(+whatsappId);
+
+  const io = getIO();
+  io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-whatsapp`, {
+    action: "delete",
+    whatsappId: +whatsappId
+  });
+
+  return res.status(200).json({ message: "Whatsapp deleted." });
+};

+ 46 - 0
backend/src/controllers/WhatsAppSessionController.ts

@@ -0,0 +1,46 @@
+import { Request, Response } from "express";
+import { getWbot } from "../libs/wbot";
+import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
+import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
+import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
+
+const store = async (req: Request, res: Response): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const { companyId } = req.user;
+
+  const whatsapp = await ShowWhatsAppService(whatsappId, companyId);
+  await StartWhatsAppSession(whatsapp, companyId);
+
+  return res.status(200).json({ message: "Starting session." });
+};
+
+const update = async (req: Request, res: Response): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const { companyId } = req.user;
+
+  const { whatsapp } = await UpdateWhatsAppService({
+    whatsappId,
+    companyId,
+    whatsappData: { session: "" }
+  });
+
+  await StartWhatsAppSession(whatsapp, companyId);
+
+  return res.status(200).json({ message: "Starting session." });
+};
+
+const remove = async (req: Request, res: Response): Promise<Response> => {
+  const { whatsappId } = req.params;
+  const { companyId } = req.user;
+  const whatsapp = await ShowWhatsAppService(whatsappId, companyId);
+
+  if (whatsapp.session) {
+    await whatsapp.update({ status: "DISCONNECTED", session: "" });
+    const wbot = getWbot(whatsapp.id);
+    await wbot.logout();
+  }
+
+  return res.status(200).json({ message: "Session disconnected." });
+};
+
+export default { store, remove, update };

+ 90 - 0
backend/src/database/index.ts

@@ -0,0 +1,90 @@
+import { Sequelize } from "sequelize-typescript";
+import User from "../models/User";
+import Setting from "../models/Setting";
+import Contact from "../models/Contact";
+import Ticket from "../models/Ticket";
+import Whatsapp from "../models/Whatsapp";
+import ContactCustomField from "../models/ContactCustomField";
+import Message from "../models/Message";
+import Queue from "../models/Queue";
+import WhatsappQueue from "../models/WhatsappQueue";
+import UserQueue from "../models/UserQueue";
+import Company from "../models/Company";
+import Plan from "../models/Plan";
+import TicketNote from "../models/TicketNote";
+import QuickMessage from "../models/QuickMessage";
+import Help from "../models/Help";
+import TicketTraking from "../models/TicketTraking";
+import UserRating from "../models/UserRating";
+import QueueOption from "../models/QueueOption";
+import Schedule from "../models/Schedule";
+import Tag from "../models/Tag";
+import TicketTag from "../models/TicketTag";
+import ContactList from "../models/ContactList";
+import ContactListItem from "../models/ContactListItem";
+import Campaign from "../models/Campaign";
+import CampaignSetting from "../models/CampaignSetting";
+import Baileys from "../models/Baileys";
+import CampaignShipping from "../models/CampaignShipping";
+import Announcement from "../models/Announcement";
+import Chat from "../models/Chat";
+import ChatUser from "../models/ChatUser";
+import ChatMessage from "../models/ChatMessage";
+import Invoices from "../models/Invoices";
+import Subscriptions from "../models/Subscriptions";
+import BaileysChats from "../models/BaileysChats";
+import Files from "../models/Files";
+import FilesOptions from "../models/FilesOptions";
+import Prompt from "../models/Prompt";
+import QueueIntegrations from "../models/QueueIntegrations";
+
+// eslint-disable-next-line
+const dbConfig = require("../config/database");
+// import dbConfig from "../config/database";
+
+const sequelize = new Sequelize(dbConfig);
+
+const models = [
+  Company,
+  User,
+  Contact,
+  Ticket,
+  Message,
+  Whatsapp,
+  ContactCustomField,
+  Setting,
+  Queue,
+  WhatsappQueue,
+  UserQueue,
+  Plan,
+  TicketNote,
+  QuickMessage,
+  Help,
+  TicketTraking,
+  UserRating,
+  QueueOption,
+  Schedule,
+  Tag,
+  TicketTag,
+  ContactList,
+  ContactListItem,
+  Campaign,
+  CampaignSetting,
+  Baileys,
+  CampaignShipping,
+  Announcement,
+  Chat,
+  ChatUser,
+  ChatMessage,
+  Invoices,
+  Subscriptions,
+  BaileysChats,
+  Files,
+  FilesOptions,
+  Prompt,
+  QueueIntegrations,
+];
+
+sequelize.addModels(models);
+
+export default sequelize;

+ 10 - 0
backend/src/database/migrations/20200717133431-add-uuid-ossp.ts

@@ -0,0 +1,10 @@
+import { QueryInterface, DataTypes, Sequelize } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return Promise.all([
+      queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'),
+    ]);
+  },
+
+};

+ 39 - 0
backend/src/database/migrations/20200717133438-create-users.ts

@@ -0,0 +1,39 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Users", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      email: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        unique: true
+      },
+      passwordHash: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Users");
+  }
+};

+ 37 - 0
backend/src/database/migrations/20200717144403-create-contacts.ts

@@ -0,0 +1,37 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Contacts", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      number: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      profilePicUrl: {
+        type: DataTypes.STRING
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Contacts");
+  }
+};

+ 46 - 0
backend/src/database/migrations/20200717145643-create-tickets.ts

@@ -0,0 +1,46 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Tickets", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      status: {
+        type: DataTypes.STRING,
+        defaultValue: "pending",
+        allowNull: false
+      },
+      lastMessage: {
+        type: DataTypes.STRING
+      },
+      contactId: {
+        type: DataTypes.INTEGER,
+        references: { model: "Contacts", key: "id" },
+        onUpdate: "CASCADE",
+        onDelete: "CASCADE"
+      },
+      userId: {
+        type: DataTypes.INTEGER,
+        references: { model: "Users", key: "id" },
+        onUpdate: "CASCADE",
+        onDelete: "SET NULL"
+      },
+      createdAt: {
+        type: DataTypes.DATE(6),
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE(6),
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Tickets");
+  }
+};

+ 58 - 0
backend/src/database/migrations/20200717151645-create-messages.ts

@@ -0,0 +1,58 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Messages", {
+      id: {
+        type: DataTypes.STRING,
+        primaryKey: true,
+        allowNull: false
+      },
+      body: {
+        type: DataTypes.TEXT,
+        allowNull: false
+      },
+      ack: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        defaultValue: 0
+      },
+      read: {
+        type: DataTypes.BOOLEAN,
+        allowNull: false,
+        defaultValue: false
+      },
+      mediaType: {
+        type: DataTypes.STRING
+      },
+      mediaUrl: {
+        type: DataTypes.STRING
+      },
+      userId: {
+        type: DataTypes.INTEGER,
+        references: { model: "Users", key: "id" },
+        onUpdate: "CASCADE",
+        onDelete: "SET NULL"
+      },
+      ticketId: {
+        type: DataTypes.INTEGER,
+        references: { model: "Tickets", key: "id" },
+        onUpdate: "CASCADE",
+        onDelete: "CASCADE",
+        allowNull: false
+      },
+      createdAt: {
+        type: DataTypes.DATE(6),
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE(6),
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Messages");
+  }
+};

+ 41 - 0
backend/src/database/migrations/20200717170223-create-whatsapps.ts

@@ -0,0 +1,41 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Whatsapps", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      session: {
+        type: DataTypes.TEXT
+      },
+      qrcode: {
+        type: DataTypes.TEXT
+      },
+      status: {
+        type: DataTypes.STRING
+      },
+      battery: {
+        type: DataTypes.STRING
+      },
+      plugged: {
+        type: DataTypes.BOOLEAN
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Whatsapps");
+  }
+};

+ 41 - 0
backend/src/database/migrations/20200723200315-create-contacts-custom-fields.ts

@@ -0,0 +1,41 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("ContactCustomFields", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      value: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      contactId: {
+        type: DataTypes.INTEGER,
+        references: { model: "Contacts", key: "id" },
+        onUpdate: "CASCADE",
+        onDelete: "CASCADE",
+        allowNull: false
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("ContactCustomFields");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200723202116-add-email-field-to-contacts.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Contacts", "email", {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: ""
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Contacts", "email");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20200730153237-remove-user-association-from-messages.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "userId");
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "userId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Users", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200730153545-add-fromMe-to-messages.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "fromMe", {
+      type: DataTypes.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "fromMe");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200813114236-change-ticket-lastMessage-column-type.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.changeColumn("Tickets", "lastMessage", {
+      type: DataTypes.TEXT
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.changeColumn("Tickets", "lastMessage", {
+      type: DataTypes.STRING
+    });
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200901235509-add-profile-column-to-users.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Users", "profile", {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: "admin"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Users", "profile");
+  }
+};

+ 29 - 0
backend/src/database/migrations/20200903215941-create-settings.ts

@@ -0,0 +1,29 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Settings", {
+      key: {
+        type: DataTypes.STRING,
+        primaryKey: true,
+        allowNull: false
+      },
+      value: {
+        type: DataTypes.TEXT,
+        allowNull: false
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Settings");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200904220257-add-name-to-whatsapp.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Whatsapps", "name", {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Whatsapps", "name");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200906122228-add-name-default-field-to-whatsapp.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Whatsapps", "default", {
+      type: DataTypes.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Whatsapps", "default");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20200906155658-add-whatsapp-field-to-tickets.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Tickets", "whatsappId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Whatsapps", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Tickets", "whatsappId");
+  }
+};

+ 11 - 0
backend/src/database/migrations/20200919124112-update-default-column-name-on-whatsappp.ts

@@ -0,0 +1,11 @@
+import { QueryInterface } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.renameColumn("Whatsapps", "default", "isDefault");
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.renameColumn("Whatsapps", "isDefault", "default");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200927220708-add-isDeleted-column-to-messages.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "isDeleted", {
+      type: DataTypes.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "isDeleted");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200929145451-add-user-tokenVersion-column.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Users", "tokenVersion", {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+      defaultValue: 0
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Users", "tokenVersion");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Tickets", "isGroup", {
+      type: DataTypes.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Tickets", "isGroup");
+  }
+};

+ 15 - 0
backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Contacts", "isGroup", {
+      type: DataTypes.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Contacts", "isGroup");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20201004150008-add-contactId-column-to-messages.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "contactId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Contacts", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "CASCADE"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "contactId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20201004155719-add-vcardContactId-column-to-messages.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "vcardContactId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Contacts", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "CASCADE"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "vcardContactId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20201004955719-remove-vcardContactId-column-to-messages.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "vcardContactId");
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "vcardContactId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Contacts", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "CASCADE"
+    });
+  }
+};

+ 15 - 0
backend/src/database/migrations/20201026215410-add-retries-to-whatsapps.ts

@@ -0,0 +1,15 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Whatsapps", "retries", {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+      allowNull: false
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Whatsapps", "retries");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20201028124427-add-quoted-msg-to-messages.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "quotedMsgId", {
+      type: DataTypes.STRING,
+      references: { model: "Messages", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "quotedMsgId");
+  }
+};

+ 13 - 0
backend/src/database/migrations/20210108001431-add-unreadMessages-to-tickets.ts

@@ -0,0 +1,13 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Tickets", "unreadMessages", {
+      type: DataTypes.INTEGER
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Tickets", "unreadMessages");
+  }
+};

+ 39 - 0
backend/src/database/migrations/20210108164404-create-queues.ts

@@ -0,0 +1,39 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Queues", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        unique: true
+      },
+      color: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        unique: true
+      },
+      greetingMessage: {
+        type: DataTypes.TEXT
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Queues");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210108164504-add-queueId-to-tickets.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Tickets", "queueId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Queues", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Tickets", "queueId");
+  }
+};

+ 28 - 0
backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts

@@ -0,0 +1,28 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("WhatsappQueues", {
+      whatsappId: {
+        type: DataTypes.INTEGER,
+        primaryKey: true
+      },
+      queueId: {
+        type: DataTypes.INTEGER,
+        primaryKey: true
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("WhatsappQueues");
+  }
+};

+ 28 - 0
backend/src/database/migrations/20210108204708-associate-users-queue.ts

@@ -0,0 +1,28 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("UserQueues", {
+      userId: {
+        type: DataTypes.INTEGER,
+        primaryKey: true
+      },
+      queueId: {
+        type: DataTypes.INTEGER,
+        primaryKey: true
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("UserQueues");
+  }
+};

+ 13 - 0
backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts

@@ -0,0 +1,13 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Whatsapps", "greetingMessage", {
+      type: DataTypes.TEXT
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Whatsapps", "greetingMessage");
+  }
+};

+ 39 - 0
backend/src/database/migrations/20210109192514-create-companies-table.ts

@@ -0,0 +1,39 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.createTable("Companies", {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true,
+        allowNull: false
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        unique: true
+      },
+      phone: {
+        type: DataTypes.STRING,
+        allowNull: true
+      },
+      email: {
+        type: DataTypes.STRING,
+        allowNull: true
+      },
+      createdAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      updatedAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.dropTable("Companies");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192515-add-column-companyId-to-Settings-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Settings", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Settings", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192516-add-column-companyId-to-Users-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Users", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Users", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192517-add-column-companyId-to-Contacts-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Contacts", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Contacts", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192518-add-column-companyId-to-Messages-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Messages", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Messages", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192519-add-column-companyId-to-Queues-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Queues", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Queues", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192520-add-column-companyId-to-Whatsapps-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Whatsapps", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Whatsapps", "companyId");
+  }
+};

+ 16 - 0
backend/src/database/migrations/20210109192521-add-column-companyId-to-Tickets-table.ts

@@ -0,0 +1,16 @@
+import { QueryInterface, DataTypes } from "sequelize";
+
+module.exports = {
+  up: (queryInterface: QueryInterface) => {
+    return queryInterface.addColumn("Tickets", "companyId", {
+      type: DataTypes.INTEGER,
+      references: { model: "Companies", key: "id" },
+      onUpdate: "CASCADE",
+      onDelete: "SET NULL"
+    });
+  },
+
+  down: (queryInterface: QueryInterface) => {
+    return queryInterface.removeColumn("Tickets", "companyId");
+  }
+};

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov