├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── Procfile ├── README.md ├── docs └── Capa do curso.png ├── jest.config.ts ├── package.json ├── src ├── index.ts └── server │ ├── Server.ts │ ├── controllers │ ├── cidades │ │ ├── Create.ts │ │ ├── DeleteById.ts │ │ ├── GetAll.ts │ │ ├── GetById.ts │ │ ├── UpdateById.ts │ │ └── index.ts │ ├── index.ts │ ├── pessoas │ │ ├── Create.ts │ │ ├── DeleteById.ts │ │ ├── GetAll.ts │ │ ├── GetById.ts │ │ ├── UpdateById.ts │ │ └── index.ts │ └── usuarios │ │ ├── SignIn.ts │ │ ├── SignUp.ts │ │ └── index.ts │ ├── database │ ├── ETableNames.ts │ ├── index.ts │ ├── knex │ │ ├── @types │ │ │ └── knex.d.ts │ │ ├── Environment.ts │ │ └── index.ts │ ├── migrations │ │ ├── 0000_create_cidade.ts │ │ ├── 0001_create_pessoa.ts │ │ └── 0002_create_usuario.ts │ ├── models │ │ ├── Cidade.ts │ │ ├── Pessoa.ts │ │ ├── Usuario.ts │ │ └── index.ts │ ├── providers │ │ ├── cidades │ │ │ ├── Count.ts │ │ │ ├── Create.ts │ │ │ ├── DeleteById.ts │ │ │ ├── GetAll.ts │ │ │ ├── GetById.ts │ │ │ ├── UpdateById.ts │ │ │ └── index.ts │ │ ├── pessoas │ │ │ ├── Count.ts │ │ │ ├── Create.ts │ │ │ ├── DeleteById.ts │ │ │ ├── GetAll.ts │ │ │ ├── GetById.ts │ │ │ ├── UpdateById.ts │ │ │ └── index.ts │ │ └── usuarios │ │ │ ├── Create.ts │ │ │ ├── GetByEmail.ts │ │ │ └── index.ts │ └── seeds │ │ └── 0000_insert_cidades.ts │ ├── routes │ └── index.ts │ └── shared │ ├── middleware │ ├── EnsureAuthenticated.ts │ ├── JSONParseError.ts │ ├── Validation.ts │ └── index.ts │ └── services │ ├── JWTService.ts │ ├── PasswordCrypto.ts │ ├── TranslationsYup.ts │ └── index.ts ├── tests ├── cidades │ ├── Create.test.ts │ ├── DeleteById.test.ts │ ├── GetAll.test.ts │ ├── GetById.test.ts │ └── UpdateById.test.ts ├── jest.setup.ts ├── pessoas │ ├── Create.test.ts │ ├── DeleteById.test.ts │ ├── GetAll.test.ts │ ├── GetById.test.ts │ └── UpdateById.test.ts └── usuarios │ ├── SignIn.test.ts │ └── SignUp.test.ts ├── tsconfig.json ├── vercel.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | NODE_ENV= 3 | 4 | IS_LOCALHOST=false 5 | 6 | ENABLED_CORS= 7 | JWT_SECRET=[alguma chave secreta e única] 8 | 9 | DATABASE_HOST= 10 | DATABASE_USER= 11 | DATABASE_NAME= 12 | DATABASE_PORT=5432 13 | DATABASE_PASSWORD= 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | "indent": [ 20 | "error", 21 | 2, 22 | { 23 | "SwitchCase": 1 24 | } 25 | ], 26 | "linebreak-style": [ 27 | "error", 28 | "unix" 29 | ], 30 | "quotes": [ 31 | "error", 32 | "single" 33 | ], 34 | "semi": [ 35 | "error", 36 | "always" 37 | ], 38 | "@typescript-eslint/ban-types": "off", 39 | "@typescript-eslint/no-empty-interface": "off" 40 | } 41 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | yarn-error.log 3 | 4 | /build 5 | /coverage 6 | 7 | .env 8 | *.sqlite 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "usuario", 4 | "usuarios", 5 | "Usuarios" 6 | ] 7 | } -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: yarn production 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | Este projeto é uma API Rest em NodeJS e Typescript desenvolvida para fins de aprendizado no curso de **[API Rest em NodeJS e Typescript](https://youtu.be/SVepTuBK4V0)** do canal **[Lucas Souza Dev](https://www.youtube.com/c/LucasSouzaDev)** no YouTube. 6 | 7 | 8 | # Sobre o projeto 9 | 10 | No projeto é abordado conceitos importantes sobre o mundo do backend. 11 | 12 | Tem conceitos como: 13 | - Endpoints 14 | - Controllers 15 | - Banco de dados SQL 16 | - Query builder 17 | - Migrations de banco 18 | - Seeds de banco 19 | - Controle de usuário com email e senha 20 | - Criptografia de senha 21 | - Login de usuários 22 | - Geração e utilização de tokens JWT 23 | - Validação minuciosa de dados que entram nos endpoints 24 | - Paginação de consultas 25 | - Filtros de consultas 26 | - Testes de código para garantir qualidade das entregas 27 | - Uso de diferentes bancos de dados com um mesmo código 28 | - Boas práticas de código, com conceitos do clean code 29 | 30 | 31 | Está é uma API Rest, então não tem interface nesse repositório. Porém, é possível conectar um interface a ele. A interface está em outro repositório. 32 | 33 | No repositório **[youtube-curso-react-materialui-typescript](https://github.com/lvsouza/youtube-curso-react-materialui-typescript/tree/integracao-curso-api-node)** á um projeto em react que se conecta com este backend. 34 | 35 | 36 | # Como rodar 37 | 38 | Você vai precisar do nodens instalado no seu computador para rodar o projeto. 39 | 40 | Clone o repositório: 41 | ``` 42 | $ git clone https://github.com/lvsouza/youtube-curso-react-materialui-typescript.git 43 | ``` 44 | 45 | Entre na pasta 46 | ``` 47 | $ cd youtube-curso-react-materialui-typescript 48 | ``` 49 | 50 | Instale as dependências 51 | ``` 52 | $ yarn install 53 | ``` 54 | 55 | Configure as variáveis ambiente, crie o arquivo `.env` na pasta raiz do projeto coloque o conteúdo a seguir dentro 56 | ``` 57 | PORT=3333 58 | NODE_ENV=dev 59 | 60 | IS_LOCALHOST=true 61 | 62 | ENABLED_CORS=[Lista de endereços separados por ";"] 63 | JWT_SECRET=[Uma string qualquer] 64 | ``` 65 | 66 | Rode o projeto 67 | ``` 68 | $ yarn start 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /docs/Capa do curso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvsouza/youtube-curso-api-rest-node-e-typescript/66f0ae5a75ebde706eb0f5fa6ef8dd0822e9ba0c/docs/Capa do curso.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/6f/sx94l4l91vbck2kw5s2_q27c0000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: 'coverage', 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: 'v8', 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | coverageReporters: [ 38 | 'json' 39 | ], 40 | 41 | // An object that configures minimum threshold enforcement for coverage results 42 | // coverageThreshold: undefined, 43 | 44 | // A path to a custom dependency extractor 45 | // dependencyExtractor: undefined, 46 | 47 | // Make calling deprecated APIs throw helpful error messages 48 | // errorOnDeprecated: false, 49 | 50 | // The default configuration for fake timers 51 | // fakeTimers: { 52 | // "enableGlobally": false 53 | // }, 54 | 55 | // Force coverage collection from ignored files using an array of glob patterns 56 | // forceCoverageMatch: [], 57 | 58 | // A path to a module which exports an async function that is triggered once before all test suites 59 | // globalSetup: undefined, 60 | 61 | // A path to a module which exports an async function that is triggered once after all test suites 62 | // globalTeardown: undefined, 63 | 64 | // A set of global variables that need to be available in all test environments 65 | // globals: {}, 66 | 67 | // 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. 68 | // maxWorkers: "50%", 69 | 70 | // An array of directory names to be searched recursively up from the requiring module's location 71 | // moduleDirectories: [ 72 | // "node_modules" 73 | // ], 74 | 75 | // An array of file extensions your modules use 76 | // moduleFileExtensions: [ 77 | // "js", 78 | // "mjs", 79 | // "cjs", 80 | // "jsx", 81 | // "ts", 82 | // "tsx", 83 | // "json", 84 | // "node" 85 | // ], 86 | 87 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 88 | // moduleNameMapper: {}, 89 | 90 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 91 | // modulePathIgnorePatterns: [], 92 | 93 | // Activates notifications for test results 94 | // notify: false, 95 | 96 | // An enum that specifies notification mode. Requires { notify: true } 97 | // notifyMode: "failure-change", 98 | 99 | // A preset that is used as a base for Jest's configuration 100 | // preset: undefined, 101 | 102 | // Run tests from one or more projects 103 | // projects: undefined, 104 | 105 | // Use this configuration option to add custom reporters to Jest 106 | // reporters: undefined, 107 | 108 | // Automatically reset mock state before every test 109 | // resetMocks: false, 110 | 111 | // Reset the module registry before running each individual test 112 | // resetModules: false, 113 | 114 | // A path to a custom resolver 115 | // resolver: undefined, 116 | 117 | // Automatically restore mock state and implementation before every test 118 | // restoreMocks: false, 119 | 120 | // The root directory that Jest should scan for tests and modules within 121 | // rootDir: undefined, 122 | 123 | // A list of paths to directories that Jest should use to search for files in 124 | // roots: [ 125 | // "" 126 | // ], 127 | 128 | // Allows you to use a custom runner instead of Jest's default test runner 129 | // runner: "jest-runner", 130 | 131 | // The paths to modules that run some code to configure or set up the testing environment before each test 132 | // setupFiles: [], 133 | 134 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 135 | setupFilesAfterEnv: [ 136 | './tests/jest.setup.ts' 137 | ], 138 | 139 | // The number of seconds after which a test is considered as slow and reported as such in the results. 140 | // slowTestThreshold: 5, 141 | 142 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 143 | // snapshotSerializers: [], 144 | 145 | // The test environment that will be used for testing 146 | // testEnvironment: "jest-environment-node", 147 | 148 | // Options that will be passed to the testEnvironment 149 | // testEnvironmentOptions: {}, 150 | 151 | // Adds a location field to test results 152 | // testLocationInResults: false, 153 | 154 | // The glob patterns Jest uses to detect test files 155 | testMatch: [ 156 | '/tests/**/*.test.ts' 157 | ], 158 | 159 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 160 | // testPathIgnorePatterns: [ 161 | // "/node_modules/" 162 | // ], 163 | 164 | // The regexp pattern or array of patterns that Jest uses to detect test files 165 | // testRegex: [], 166 | 167 | // This option allows the use of a custom results processor 168 | // testResultsProcessor: undefined, 169 | 170 | // This option allows use of a custom test runner 171 | // testRunner: "jest-circus/runner", 172 | 173 | // A map from regular expressions to paths to transformers 174 | transform: { 175 | '^.+\\.(ts|tsx)$': 'ts-jest' 176 | }, 177 | 178 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 179 | // transformIgnorePatterns: [ 180 | // "/node_modules/", 181 | // "\\.pnp\\.[^\\/]+$" 182 | // ], 183 | 184 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 185 | // unmockedModulePathPatterns: undefined, 186 | 187 | // Indicates whether each individual test should be reported during the run 188 | // verbose: undefined, 189 | 190 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 191 | // watchPathIgnorePatterns: [], 192 | 193 | // Whether to use watchman for file crawling 194 | // watchman: true, 195 | }; 196 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-rest-node-typescript", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Lucas Souza", 6 | "license": "MIT", 7 | "private": false, 8 | "scripts": { 9 | "start": "ts-node-dev ./src/index.ts", 10 | "production": "node ./build/index.js", 11 | "build": "tsc", 12 | "knex:rollback-all": "knex --knexfile ./src/server/database/knex/Environment.ts migrate:rollback --all", 13 | "knex:rollback": "knex --knexfile ./src/server/database/knex/Environment.ts migrate:rollback", 14 | "knex:migrate": "knex --knexfile ./src/server/database/knex/Environment.ts migrate:latest", 15 | "knex:seed": "knex --knexfile ./src/server/database/knex/Environment.ts seed:run", 16 | "test": "jest" 17 | }, 18 | "dependencies": { 19 | "bcryptjs": "^2.4.3", 20 | "cors": "^2.8.5", 21 | "dotenv": "^16.0.1", 22 | "express": "^4.18.1", 23 | "http-status-codes": "^2.2.0", 24 | "yup": "^1.0.2", 25 | "jsonwebtoken": "^8.5.1", 26 | "knex": "^2.2.0", 27 | "pg": "^8.7.3" 28 | }, 29 | "devDependencies": { 30 | "@types/bcryptjs": "^2.4.2", 31 | "@types/cors": "^2.8.12", 32 | "@types/express": "^4.17.13", 33 | "@types/jest": "^28.1.6", 34 | "@types/jsonwebtoken": "^8.5.8", 35 | "@types/pg": "^8.6.5", 36 | "@types/supertest": "^2.0.12", 37 | "@typescript-eslint/eslint-plugin": "^5.30.6", 38 | "@typescript-eslint/parser": "^5.30.6", 39 | "eslint": "^8.19.0", 40 | "jest": "^28.1.3", 41 | "sqlite3": "^5.0.10", 42 | "supertest": "^6.2.4", 43 | "ts-jest": "^28.0.7", 44 | "ts-node-dev": "^2.0.0", 45 | "typescript": "^4.7.4" 46 | }, 47 | "engines": { 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from './server/database/knex'; 2 | import { server } from './server/Server'; 3 | 4 | 5 | const startServer = () => { 6 | server.listen(process.env.PORT || 3333, () => { 7 | console.log(`App rodando na porta ${process.env.PORT || 3333}`); 8 | }); 9 | }; 10 | 11 | 12 | if (process.env.IS_LOCALHOST !== 'true') { 13 | console.log('Rodando migrations'); 14 | 15 | Knex.migrate 16 | .latest() 17 | .then(() => { 18 | Knex.seed.run() 19 | .then(() => startServer()) 20 | .catch(console.log); 21 | }) 22 | .catch(console.log); 23 | } else { 24 | startServer(); 25 | } 26 | -------------------------------------------------------------------------------- /src/server/Server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import 'dotenv/config'; 4 | 5 | import { JSONParseError } from './shared/middleware'; 6 | import './shared/services/TranslationsYup'; 7 | import { router } from './routes'; 8 | 9 | 10 | const server = express(); 11 | 12 | 13 | server.use(cors({ 14 | origin: process.env.ENABLED_CORS?.split(';') || [] 15 | })); 16 | 17 | server.use(express.json()); 18 | 19 | server.use(JSONParseError); 20 | 21 | server.use(router); 22 | 23 | 24 | export { server }; 25 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/Create.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { CidadesProvider } from '../../database/providers/cidades'; 6 | import { validation } from '../../shared/middleware'; 7 | import { ICidade } from '../../database/models'; 8 | 9 | 10 | interface IBodyProps extends Omit { } 11 | 12 | export const createValidation = validation((getSchema) => ({ 13 | body: getSchema(yup.object().shape({ 14 | nome: yup.string().required().min(3).max(150), 15 | })), 16 | })); 17 | 18 | export const create = async (req: Request<{}, {}, ICidade>, res: Response) => { 19 | const result = await CidadesProvider.create(req.body); 20 | 21 | if (result instanceof Error) { 22 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 23 | errors: { 24 | default: result.message 25 | } 26 | }); 27 | } 28 | 29 | return res.status(StatusCodes.CREATED).json(result); 30 | }; 31 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/DeleteById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { CidadesProvider } from '../../database/providers/cidades'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IParamProps { 10 | id?: number; 11 | } 12 | export const deleteByIdValidation = validation(getSchema => ({ 13 | params: getSchema(yup.object().shape({ 14 | id: yup.number().integer().required().moreThan(0), 15 | })), 16 | })); 17 | 18 | export const deleteById = async (req: Request, res: Response) => { 19 | if (!req.params.id) { 20 | return res.status(StatusCodes.BAD_REQUEST).json({ 21 | errors: { 22 | default: 'O parâmetro "id" precisa ser informado.' 23 | } 24 | }); 25 | } 26 | 27 | const result = await CidadesProvider.deleteById(req.params.id); 28 | if (result instanceof Error) { 29 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 30 | errors: { 31 | default: result.message 32 | } 33 | }); 34 | } 35 | 36 | return res.status(StatusCodes.NO_CONTENT).send(); 37 | }; 38 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/GetAll.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { CidadesProvider } from '../../database/providers/cidades'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IQueryProps { 10 | id?: number; 11 | page?: number; 12 | limit?: number; 13 | filter?: string; 14 | } 15 | export const getAllValidation = validation((getSchema) => ({ 16 | query: getSchema(yup.object().shape({ 17 | page: yup.number().optional().moreThan(0), 18 | limit: yup.number().optional().moreThan(0), 19 | id: yup.number().integer().optional().default(0), 20 | filter: yup.string().optional(), 21 | })), 22 | })); 23 | 24 | export const getAll = async (req: Request<{}, {}, {}, IQueryProps>, res: Response) => { 25 | const result = await CidadesProvider.getAll(req.query.page || 1, req.query.limit || 7, req.query.filter || '', Number(req.query.id || 0)); 26 | const count = await CidadesProvider.count(req.query.filter); 27 | 28 | if (result instanceof Error) { 29 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 30 | errors: { default: result.message } 31 | }); 32 | } else if (count instanceof Error) { 33 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 34 | errors: { default: count.message } 35 | }); 36 | } 37 | 38 | res.setHeader('access-control-expose-headers', 'x-total-count'); 39 | res.setHeader('x-total-count', count); 40 | 41 | return res.status(StatusCodes.OK).json(result); 42 | }; 43 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/GetById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { CidadesProvider } from '../../database/providers/cidades'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IParamProps { 10 | id?: number; 11 | } 12 | export const getByIdValidation = validation(getSchema => ({ 13 | params: getSchema(yup.object().shape({ 14 | id: yup.number().integer().required().moreThan(0), 15 | })), 16 | })); 17 | 18 | export const getById = async (req: Request, res: Response) => { 19 | if (!req.params.id) { 20 | return res.status(StatusCodes.BAD_REQUEST).json({ 21 | errors: { 22 | default: 'O parâmetro "id" precisa ser informado.' 23 | } 24 | }); 25 | } 26 | 27 | const result = await CidadesProvider.getById(req.params.id); 28 | if (result instanceof Error) { 29 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 30 | errors: { 31 | default: result.message 32 | } 33 | }); 34 | } 35 | 36 | return res.status(StatusCodes.OK).json(result); 37 | }; 38 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/UpdateById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { CidadesProvider } from '../../database/providers/cidades'; 6 | import { validation } from '../../shared/middleware'; 7 | import { ICidade } from '../../database/models'; 8 | 9 | 10 | interface IParamProps { 11 | id?: number; 12 | } 13 | 14 | interface IBodyProps extends Omit { } 15 | 16 | export const updateByIdValidation = validation(getSchema => ({ 17 | body: getSchema(yup.object().shape({ 18 | nome: yup.string().required().min(3), 19 | })), 20 | params: getSchema(yup.object().shape({ 21 | id: yup.number().integer().required().moreThan(0), 22 | })), 23 | })); 24 | 25 | export const updateById = async (req: Request, res: Response) => { 26 | if (!req.params.id) { 27 | return res.status(StatusCodes.BAD_REQUEST).json({ 28 | errors: { 29 | default: 'O parâmetro "id" precisa ser informado.' 30 | } 31 | }); 32 | } 33 | 34 | const result = await CidadesProvider.updateById(req.params.id, req.body); 35 | if (result instanceof Error) { 36 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 37 | errors: { 38 | default: result.message 39 | } 40 | }); 41 | } 42 | 43 | return res.status(StatusCodes.NO_CONTENT).json(result); 44 | }; 45 | -------------------------------------------------------------------------------- /src/server/controllers/cidades/index.ts: -------------------------------------------------------------------------------- 1 | import * as deleteById from './DeleteById'; 2 | import * as updateById from './UpdateById'; 3 | import * as getById from './GetById'; 4 | import * as create from './Create'; 5 | import * as getAll from './GetAll'; 6 | 7 | 8 | export const CidadesController = { 9 | ...deleteById, 10 | ...updateById, 11 | ...getById, 12 | ...create, 13 | ...getAll, 14 | }; 15 | -------------------------------------------------------------------------------- /src/server/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './usuarios'; 2 | export * from './cidades'; 3 | export * from './pessoas'; 4 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/Create.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | import { Request, Response } from 'express'; 3 | import * as yup from 'yup'; 4 | 5 | import { PessoasProvider } from './../../database/providers/pessoas'; 6 | import { validation } from '../../shared/middleware'; 7 | import { IPessoa } from './../../database/models'; 8 | 9 | 10 | interface IBodyProps extends Omit { } 11 | 12 | export const createValidation = validation(get => ({ 13 | body: get(yup.object().shape({ 14 | email: yup.string().required().email(), 15 | cidadeId: yup.number().integer().required(), 16 | nomeCompleto: yup.string().required().min(3), 17 | })), 18 | })); 19 | 20 | export const create = async (req: Request<{}, {}, IBodyProps>, res: Response) => { 21 | const result = await PessoasProvider.create(req.body); 22 | 23 | if (result instanceof Error) { 24 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 25 | errors: { 26 | default: result.message 27 | } 28 | }); 29 | } 30 | 31 | return res.status(StatusCodes.CREATED).json(result); 32 | }; 33 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/DeleteById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { PessoasProvider } from './../../database/providers/pessoas'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IParamProps { 10 | id?: number; 11 | } 12 | export const deleteByIdValidation = validation(get => ({ 13 | params: get(yup.object().shape({ 14 | id: yup.number().integer().required().moreThan(0), 15 | })), 16 | })); 17 | 18 | export const deleteById = async (req: Request, res: Response) => { 19 | if (!req.params.id) { 20 | return res.status(StatusCodes.BAD_REQUEST).json({ 21 | errors: { 22 | default: 'O parâmetro "id" precisa ser informado.' 23 | } 24 | }); 25 | } 26 | 27 | const result = await PessoasProvider.deleteById(req.params.id); 28 | if (result instanceof Error) { 29 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 30 | errors: { 31 | default: result.message 32 | } 33 | }); 34 | } 35 | 36 | return res.status(StatusCodes.NO_CONTENT).send(); 37 | }; 38 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/GetAll.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { PessoasProvider } from './../../database/providers/pessoas'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IQueryProps { 10 | page?: number; 11 | limit?: number; 12 | filter?: string; 13 | } 14 | export const getAllValidation = validation(get => ({ 15 | query: get(yup.object().shape({ 16 | filter: yup.string().optional().default(''), 17 | page: yup.number().integer().optional().moreThan(0).default(1), 18 | limit: yup.number().integer().optional().moreThan(0).default(7), 19 | })), 20 | })); 21 | 22 | export const getAll = async (req: Request<{}, {}, {}, IQueryProps>, res: Response) => { 23 | const result = await PessoasProvider.getAll(req.query.page || 1, req.query.limit || 7, req.query.filter || ''); 24 | const count = await PessoasProvider.count(req.query.filter); 25 | 26 | if (result instanceof Error) { 27 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 28 | errors: { default: result.message } 29 | }); 30 | } else if (count instanceof Error) { 31 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 32 | errors: { default: count.message } 33 | }); 34 | } 35 | 36 | res.setHeader('access-control-expose-headers', 'x-total-count'); 37 | res.setHeader('x-total-count', count); 38 | 39 | return res.status(StatusCodes.OK).json(result); 40 | }; 41 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/GetById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { PessoasProvider } from './../../database/providers/pessoas'; 6 | import { validation } from '../../shared/middleware'; 7 | 8 | 9 | interface IParamProps { 10 | id?: number; 11 | } 12 | export const getByIdValidation = validation(get => ({ 13 | params: get(yup.object().shape({ 14 | id: yup.number().integer().required().moreThan(0), 15 | })), 16 | })); 17 | 18 | export const getById = async (req: Request, res: Response) => { 19 | if (!req.params.id) { 20 | return res.status(StatusCodes.BAD_REQUEST).json({ 21 | errors: { 22 | default: 'O parâmetro "id" precisa ser informado.' 23 | } 24 | }); 25 | } 26 | 27 | const result = await PessoasProvider.getById(req.params.id); 28 | if (result instanceof Error) { 29 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 30 | errors: { 31 | default: result.message 32 | } 33 | }); 34 | } 35 | 36 | return res.status(StatusCodes.OK).json(result); 37 | }; 38 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/UpdateById.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { PessoasProvider } from '../../database/providers/pessoas'; 6 | import { validation } from '../../shared/middleware'; 7 | import { IPessoa } from '../../database/models'; 8 | 9 | 10 | interface IParamProps { 11 | id?: number; 12 | } 13 | 14 | interface IBodyProps extends Omit { } 15 | 16 | export const updateByIdValidation = validation(get => ({ 17 | body: get(yup.object().shape({ 18 | email: yup.string().required().email(), 19 | cidadeId: yup.number().integer().required(), 20 | nomeCompleto: yup.string().required().min(3), 21 | })), 22 | params: get(yup.object().shape({ 23 | id: yup.number().integer().required().moreThan(0), 24 | })), 25 | })); 26 | 27 | export const updateById = async (req: Request, res: Response) => { 28 | if (!req.params.id) { 29 | return res.status(StatusCodes.BAD_REQUEST).json({ 30 | errors: { 31 | default: 'O parâmetro "id" precisa ser informado.' 32 | } 33 | }); 34 | } 35 | 36 | const result = await PessoasProvider.updateById(req.params.id, req.body); 37 | if (result instanceof Error) { 38 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 39 | errors: { 40 | default: result.message 41 | } 42 | }); 43 | } 44 | 45 | return res.status(StatusCodes.NO_CONTENT).json(result); 46 | }; 47 | -------------------------------------------------------------------------------- /src/server/controllers/pessoas/index.ts: -------------------------------------------------------------------------------- 1 | import * as updateById from './UpdateById'; 2 | import * as deleteById from './DeleteById'; 3 | import * as getById from './GetById'; 4 | import * as create from './Create'; 5 | import * as getAll from './GetAll'; 6 | 7 | 8 | export const PessoasController = { 9 | ...updateById, 10 | ...deleteById, 11 | ...getById, 12 | ...getAll, 13 | ...create, 14 | }; 15 | -------------------------------------------------------------------------------- /src/server/controllers/usuarios/SignIn.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { UsuariosProvider } from '../../database/providers/usuarios'; 6 | import { JWTService, PasswordCrypto } from '../../shared/services'; 7 | import { validation } from '../../shared/middleware'; 8 | import { IUsuario } from '../../database/models'; 9 | 10 | 11 | interface IBodyProps extends Omit { } 12 | 13 | export const signInValidation = validation((getSchema) => ({ 14 | body: getSchema(yup.object().shape({ 15 | senha: yup.string().required().min(6), 16 | email: yup.string().required().email().min(5), 17 | })), 18 | })); 19 | 20 | export const signIn = async (req: Request<{}, {}, IBodyProps>, res: Response) => { 21 | const { email, senha } = req.body; 22 | 23 | 24 | const usuario = await UsuariosProvider.getByEmail(email); 25 | if (usuario instanceof Error) { 26 | return res.status(StatusCodes.UNAUTHORIZED).json({ 27 | errors: { 28 | default: 'Email ou senha são inválidos' 29 | } 30 | }); 31 | } 32 | 33 | const passwordMatch = await PasswordCrypto.verifyPassword(senha, usuario.senha); 34 | if (!passwordMatch) { 35 | return res.status(StatusCodes.UNAUTHORIZED).json({ 36 | errors: { 37 | default: 'Email ou senha são inválidos' 38 | } 39 | }); 40 | } else { 41 | 42 | const accessToken = JWTService.sign({ uid: usuario.id }); 43 | if (accessToken === 'JWT_SECRET_NOT_FOUND') { 44 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 45 | errors: { 46 | default: 'Erro ao gerar o token de acesso' 47 | } 48 | }); 49 | } 50 | 51 | return res.status(StatusCodes.OK).json({ accessToken }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/server/controllers/usuarios/SignUp.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import * as yup from 'yup'; 4 | 5 | import { UsuariosProvider } from '../../database/providers/usuarios'; 6 | import { validation } from '../../shared/middleware'; 7 | import { IUsuario } from '../../database/models'; 8 | 9 | 10 | interface IBodyProps extends Omit { } 11 | 12 | export const signUpValidation = validation((getSchema) => ({ 13 | body: getSchema(yup.object().shape({ 14 | nome: yup.string().required().min(3), 15 | senha: yup.string().required().min(6), 16 | email: yup.string().required().email().min(5), 17 | })), 18 | })); 19 | 20 | export const signUp = async (req: Request<{}, {}, IBodyProps>, res: Response) => { 21 | const result = await UsuariosProvider.create(req.body); 22 | 23 | if (result instanceof Error) { 24 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 25 | errors: { 26 | default: result.message 27 | } 28 | }); 29 | } 30 | 31 | return res.status(StatusCodes.CREATED).json(result); 32 | }; 33 | -------------------------------------------------------------------------------- /src/server/controllers/usuarios/index.ts: -------------------------------------------------------------------------------- 1 | import * as signIn from './SignIn'; 2 | import * as signUp from './SignUp'; 3 | 4 | 5 | export const UsuariosController = { 6 | ...signIn, 7 | ...signUp, 8 | }; 9 | -------------------------------------------------------------------------------- /src/server/database/ETableNames.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export enum ETableNames { 4 | cidade = 'cidade', 5 | pessoa = 'pessoa', 6 | usuario = 'usuario', 7 | } 8 | -------------------------------------------------------------------------------- /src/server/database/index.ts: -------------------------------------------------------------------------------- 1 | export { }; -------------------------------------------------------------------------------- /src/server/database/knex/@types/knex.d.ts: -------------------------------------------------------------------------------- 1 | import { ICidade, IPessoa, IUsuario } from '../../models'; 2 | 3 | 4 | declare module 'knex/types/tables' { 5 | interface Tables { 6 | pessoa: IPessoa; 7 | cidade: ICidade; 8 | usuario: IUsuario; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/server/database/knex/Environment.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import path from 'path'; 3 | 4 | 5 | export const development: Knex.Config = { 6 | client: 'sqlite3', 7 | useNullAsDefault: true, 8 | connection: { 9 | filename: path.resolve(__dirname, '..', '..', '..', '..', 'database.sqlite') 10 | }, 11 | migrations: { 12 | directory: path.resolve(__dirname, '..', 'migrations'), 13 | }, 14 | seeds: { 15 | directory: path.resolve(__dirname, '..', 'seeds'), 16 | }, 17 | pool: { 18 | afterCreate: (connection: any, done: Function) => { 19 | connection.run('PRAGMA foreign_keys = ON'); 20 | done(); 21 | } 22 | } 23 | }; 24 | 25 | export const test: Knex.Config = { 26 | ...development, 27 | connection: ':memory:', 28 | }; 29 | 30 | export const production: Knex.Config = { 31 | client: 'pg', 32 | migrations: { 33 | directory: path.resolve(__dirname, '..', 'migrations'), 34 | }, 35 | seeds: { 36 | directory: path.resolve(__dirname, '..', 'seeds'), 37 | }, 38 | connection: { 39 | host: process.env.DATABASE_HOST, 40 | user: process.env.DATABASE_USER, 41 | database: process.env.DATABASE_NAME, 42 | password: process.env.DATABASE_PASSWORD, 43 | port: Number(process.env.DATABASE_PORT || 5432), 44 | ssl: { rejectUnauthorized: false }, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/server/database/knex/index.ts: -------------------------------------------------------------------------------- 1 | import { knex } from 'knex'; 2 | import 'dotenv/config'; 3 | import pg from 'pg'; 4 | 5 | import { development, production, test } from './Environment'; 6 | 7 | 8 | if (process.env.NODE_ENV === 'production') { 9 | pg.types.setTypeParser(20, 'text', parseInt); 10 | } 11 | 12 | const getEnvironment = () => { 13 | switch (process.env.NODE_ENV) { 14 | case 'production': return production; 15 | case 'test': return test; 16 | 17 | default: return development; 18 | } 19 | }; 20 | 21 | export const Knex = knex(getEnvironment()); 22 | -------------------------------------------------------------------------------- /src/server/database/migrations/0000_create_cidade.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | 3 | import { ETableNames } from '../ETableNames'; 4 | 5 | 6 | export async function up(knex: Knex) { 7 | return knex 8 | .schema 9 | .createTable(ETableNames.cidade, table => { 10 | table.bigIncrements('id').primary().index(); 11 | table.string('nome', 150).checkLength('<=', 150).index().notNullable(); 12 | 13 | table.comment('Tabela usada para armazenar cidades do sistema.'); 14 | }) 15 | .then(() => { 16 | console.log(`# Created table ${ETableNames.cidade}`); 17 | }); 18 | } 19 | 20 | export async function down(knex: Knex) { 21 | return knex 22 | .schema 23 | .dropTable(ETableNames.cidade) 24 | .then(() => { 25 | console.log(`# Dropped table ${ETableNames.cidade}`); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/server/database/migrations/0001_create_pessoa.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | 3 | import { ETableNames } from '../ETableNames'; 4 | 5 | 6 | export async function up(knex: Knex) { 7 | return knex 8 | .schema 9 | .createTable(ETableNames.pessoa, table => { 10 | table.bigIncrements('id').primary().index(); 11 | table.string('nomeCompleto').index().notNullable(); 12 | table.string('email').unique().notNullable(); 13 | 14 | table 15 | .bigInteger('cidadeId') 16 | .index() 17 | .notNullable() 18 | .references('id') 19 | .inTable(ETableNames.cidade) 20 | .onUpdate('CASCADE') 21 | .onDelete('RESTRICT'); 22 | 23 | 24 | table.comment('Tabela usada para armazenar pessoas do sistema.'); 25 | }) 26 | .then(() => { 27 | console.log(`# Created table ${ETableNames.pessoa}`); 28 | }); 29 | } 30 | 31 | export async function down(knex: Knex) { 32 | return knex 33 | .schema 34 | .dropTable(ETableNames.pessoa) 35 | .then(() => { 36 | console.log(`# Dropped table ${ETableNames.pessoa}`); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/server/database/migrations/0002_create_usuario.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | 3 | import { ETableNames } from '../ETableNames'; 4 | 5 | 6 | export async function up(knex: Knex) { 7 | return knex 8 | .schema 9 | .createTable(ETableNames.usuario, table => { 10 | table.bigIncrements('id').primary().index(); 11 | table.string('nome').notNullable().checkLength('>=', 3); 12 | table.string('senha').notNullable().checkLength('>=', 6); 13 | table.string('email').index().unique().notNullable().checkLength('>=', 5); 14 | 15 | 16 | table.comment('Tabela usada para armazenar usuários do sistema.'); 17 | }) 18 | .then(() => { 19 | console.log(`# Created table ${ETableNames.usuario}`); 20 | }); 21 | } 22 | 23 | export async function down(knex: Knex) { 24 | return knex 25 | .schema 26 | .dropTable(ETableNames.usuario) 27 | .then(() => { 28 | console.log(`# Dropped table ${ETableNames.usuario}`); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/server/database/models/Cidade.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface ICidade { 4 | id: number; 5 | nome: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/server/database/models/Pessoa.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IPessoa { 3 | id: number; 4 | email: string; 5 | cidadeId: number; 6 | nomeCompleto: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/server/database/models/Usuario.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IUsuario { 3 | id: number; 4 | nome: string; 5 | email: string; 6 | senha: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/server/database/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Usuario'; 2 | export * from './Cidade'; 3 | export * from './Pessoa'; 4 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/Count.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { Knex } from '../../knex'; 3 | 4 | 5 | export const count = async (filter = ''): Promise => { 6 | try { 7 | const [{ count }] = await Knex(ETableNames.cidade) 8 | .where('nome', 'like', `%${filter}%`) 9 | .count<[{ count: number }]>('* as count'); 10 | 11 | if (Number.isInteger(Number(count))) return Number(count); 12 | 13 | return new Error('Erro ao consultar a quantidade total de registros'); 14 | } catch (error) { 15 | console.log(error); 16 | return new Error('Erro ao consultar a quantidade total de registros'); 17 | } 18 | }; -------------------------------------------------------------------------------- /src/server/database/providers/cidades/Create.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { ICidade } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const create = async (cidade: Omit): Promise => { 7 | try { 8 | const [result] = await Knex(ETableNames.cidade).insert(cidade).returning('id'); 9 | 10 | if (typeof result === 'object') { 11 | return result.id; 12 | } else if (typeof result === 'number') { 13 | return result; 14 | } 15 | 16 | return new Error('Erro ao cadastrar o registro'); 17 | } catch (error) { 18 | console.log(error); 19 | return new Error('Erro ao cadastrar o registro'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/DeleteById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { Knex } from '../../knex'; 3 | 4 | 5 | export const deleteById = async (id: number): Promise => { 6 | try { 7 | const result = await Knex(ETableNames.cidade) 8 | .where('id', '=', id) 9 | .del(); 10 | 11 | if (result > 0) return; 12 | 13 | return new Error('Erro ao apagar o registro'); 14 | } catch (error) { 15 | console.log(error); 16 | return new Error('Erro ao apagar o registro'); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/GetAll.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { ICidade } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const getAll = async (page: number, limit: number, filter: string, id = 0): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.cidade) 9 | .select('*') 10 | .where('id', Number(id)) 11 | .orWhere('nome', 'like', `%${filter}%`) 12 | .offset((page - 1) * limit) 13 | .limit(limit); 14 | 15 | if (id > 0 && result.every(item => item.id !== id)) { 16 | const resultById = await Knex(ETableNames.cidade) 17 | .select('*') 18 | .where('id', '=', id) 19 | .first(); 20 | 21 | if (resultById) return [...result, resultById]; 22 | } 23 | 24 | return result; 25 | } catch (error) { 26 | console.log(error); 27 | return new Error('Erro ao consultar os registros'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/GetById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { ICidade } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const getById = async (id: number): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.cidade) 9 | .select('*') 10 | .where('id', '=', id) 11 | .first(); 12 | 13 | if (result) return result; 14 | 15 | return new Error('Registro não encontrado'); 16 | } catch (error) { 17 | console.log(error); 18 | return new Error('Erro ao consultar o registro'); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/UpdateById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { ICidade } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const updateById = async (id: number, cidade: Omit): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.cidade) 9 | .update(cidade) 10 | .where('id', '=', id); 11 | 12 | if (result > 0) return; 13 | 14 | return new Error('Erro ao atualizar o registro'); 15 | } catch (error) { 16 | console.log(error); 17 | return new Error('Erro ao atualizar o registro'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/server/database/providers/cidades/index.ts: -------------------------------------------------------------------------------- 1 | import * as deleteById from './DeleteById'; 2 | import * as updateById from './UpdateById'; 3 | import * as getById from './GetById'; 4 | import * as create from './Create'; 5 | import * as getAll from './GetAll'; 6 | import * as count from './Count'; 7 | 8 | 9 | export const CidadesProvider = { 10 | ...deleteById, 11 | ...updateById, 12 | ...getById, 13 | ...create, 14 | ...getAll, 15 | ...count, 16 | }; 17 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/Count.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { Knex } from '../../knex'; 3 | 4 | 5 | export const count = async (filter = ''): Promise => { 6 | try { 7 | const [{ count }] = await Knex(ETableNames.pessoa) 8 | .where('nomeCompleto', 'like', `%${filter}%`) 9 | .count<[{ count: number }]>('* as count'); 10 | 11 | if (Number.isInteger(Number(count))) return Number(count); 12 | 13 | return new Error('Erro ao consultar a quantidade total de registros'); 14 | } catch (error) { 15 | console.log(error); 16 | return new Error('Erro ao consultar a quantidade total de registros'); 17 | } 18 | }; -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/Create.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { IPessoa } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const create = async (pessoa: Omit): Promise => { 7 | try { 8 | const [{ count }] = await Knex(ETableNames.cidade) 9 | .where('id', '=', pessoa.cidadeId) 10 | .count<[{ count: number }]>('* as count'); 11 | 12 | if (count === 0) { 13 | return new Error('A cidade usada no cadastro não foi encontrada'); 14 | } 15 | 16 | 17 | const [result] = await Knex(ETableNames.pessoa).insert(pessoa).returning('id'); 18 | if (typeof result === 'object') { 19 | return result.id; 20 | } else if (typeof result === 'number') { 21 | return result; 22 | } 23 | 24 | return new Error('Erro ao cadastrar o registro'); 25 | } catch (error) { 26 | console.log(error); 27 | return new Error('Erro ao cadastrar o registro'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/DeleteById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { Knex } from '../../knex'; 3 | 4 | 5 | export const deleteById = async (id: number): Promise => { 6 | try { 7 | const result = await Knex(ETableNames.pessoa) 8 | .where('id', '=', id) 9 | .del(); 10 | 11 | if (result > 0) return; 12 | 13 | return new Error('Erro ao apagar o registro'); 14 | } catch (error) { 15 | console.log(error); 16 | return new Error('Erro ao apagar o registro'); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/GetAll.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { IPessoa } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const getAll = async (page: number, limit: number, filter: string): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.pessoa) 9 | .select('*') 10 | .where('nomeCompleto', 'like', `%${filter}%`) 11 | .offset((page - 1) * limit) 12 | .limit(limit); 13 | 14 | return result; 15 | } catch (error) { 16 | console.log(error); 17 | return new Error('Erro ao consultar os registros'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/GetById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { IPessoa } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const getById = async (id: number): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.pessoa) 9 | .select('*') 10 | .where('id', '=', id) 11 | .first(); 12 | 13 | if (result) return result; 14 | 15 | return new Error('Registro não encontrado'); 16 | } catch (error) { 17 | console.log(error); 18 | return new Error('Erro ao consultar o registro'); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/UpdateById.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { IPessoa } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const updateById = async (id: number, pessoa: Omit): Promise => { 7 | try { 8 | const [{ count }] = await Knex(ETableNames.cidade) 9 | .where('id', '=', pessoa.cidadeId) 10 | .count<[{ count: number }]>('* as count'); 11 | 12 | if (count === 0) { 13 | return new Error('A cidade usada no cadastro não foi encontrada'); 14 | } 15 | 16 | const result = await Knex(ETableNames.pessoa) 17 | .update(pessoa) 18 | .where('id', '=', id); 19 | 20 | if (result > 0) return; 21 | 22 | return new Error('Erro ao atualizar o registro'); 23 | } catch (error) { 24 | console.log(error); 25 | return new Error('Erro ao atualizar o registro'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/server/database/providers/pessoas/index.ts: -------------------------------------------------------------------------------- 1 | import * as deleteById from './DeleteById'; 2 | import * as updateById from './UpdateById'; 3 | import * as getById from './GetById'; 4 | import * as create from './Create'; 5 | import * as getAll from './GetAll'; 6 | import * as count from './Count'; 7 | 8 | 9 | export const PessoasProvider = { 10 | ...deleteById, 11 | ...updateById, 12 | ...getById, 13 | ...create, 14 | ...getAll, 15 | ...count, 16 | }; 17 | -------------------------------------------------------------------------------- /src/server/database/providers/usuarios/Create.ts: -------------------------------------------------------------------------------- 1 | import { PasswordCrypto } from '../../../shared/services'; 2 | import { ETableNames } from '../../ETableNames'; 3 | import { IUsuario } from '../../models'; 4 | import { Knex } from '../../knex'; 5 | 6 | 7 | export const create = async (usuario: Omit): Promise => { 8 | try { 9 | const hashedPassword = await PasswordCrypto.hashPassword(usuario.senha); 10 | 11 | const [result] = await Knex(ETableNames.usuario).insert({ ...usuario, senha: hashedPassword }).returning('id'); 12 | 13 | if (typeof result === 'object') { 14 | return result.id; 15 | } else if (typeof result === 'number') { 16 | return result; 17 | } 18 | 19 | return new Error('Erro ao cadastrar o registro'); 20 | } catch (error) { 21 | console.log(error); 22 | return new Error('Erro ao cadastrar o registro'); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/server/database/providers/usuarios/GetByEmail.ts: -------------------------------------------------------------------------------- 1 | import { ETableNames } from '../../ETableNames'; 2 | import { IUsuario } from '../../models'; 3 | import { Knex } from '../../knex'; 4 | 5 | 6 | export const getByEmail = async (email: string): Promise => { 7 | try { 8 | const result = await Knex(ETableNames.usuario) 9 | .select('*') 10 | .where('email', '=', email) 11 | .first(); 12 | 13 | if (result) return result; 14 | 15 | return new Error('Registro não encontrado'); 16 | } catch (error) { 17 | console.log(error); 18 | return new Error('Erro ao consultar o registro'); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/server/database/providers/usuarios/index.ts: -------------------------------------------------------------------------------- 1 | import * as getByEmail from './GetByEmail'; 2 | import * as create from './Create'; 3 | 4 | 5 | export const UsuariosProvider = { 6 | ...getByEmail, 7 | ...create, 8 | }; 9 | -------------------------------------------------------------------------------- /src/server/database/seeds/0000_insert_cidades.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | 3 | import { ETableNames } from '../ETableNames'; 4 | 5 | 6 | export const seed = async (knex: Knex) => { 7 | const [{ count }] = await knex(ETableNames.cidade).count<[{ count: number }]>('* as count'); 8 | if (!Number.isInteger(count) || Number(count) > 0) return; 9 | 10 | const cidadesToInsert = cidadesDoRioGrandeDoSul.map(nomeDaCidade => ({ nome: nomeDaCidade })); 11 | await knex(ETableNames.cidade).insert(cidadesToInsert); 12 | }; 13 | 14 | 15 | const cidadesDoRioGrandeDoSul = [ 16 | 'Aceguá', 17 | 'Água Santa', 18 | 'Agudo', 19 | 'Ajuricaba', 20 | 'Alecrim', 21 | 'Alegrete', 22 | 'Alegria', 23 | 'Almirante Tamandaré do Sul', 24 | 'Alpestre', 25 | 'Alto Alegre', 26 | 'Alto Feliz', 27 | 'Alvorada', 28 | 'Amaral Ferrador', 29 | 'Ametista do Sul', 30 | 'André da Rocha', 31 | 'Anta Gorda', 32 | 'Antônio Prado', 33 | 'Arambaré', 34 | 'Araricá', 35 | 'Aratiba', 36 | 'Arroio do Meio', 37 | 'Arroio do Padre', 38 | 'Arroio do Sal', 39 | 'Arroio do Tigre', 40 | 'Arroio dos Ratos', 41 | 'Arroio Grande', 42 | 'Arvorezinha', 43 | 'Augusto Pestana', 44 | 'Áurea', 45 | 'Bagé', 46 | 'Balneário Pinhal', 47 | 'Barão', 48 | 'Barão de Cotegipe', 49 | 'Barão do Triunfo', 50 | 'Barra do Guarita', 51 | 'Barra do Quaraí', 52 | 'Barra do Ribeiro', 53 | 'Barra do Rio Azul', 54 | 'Barra Funda', 55 | 'Barracão', 56 | 'Barros Cassal', 57 | 'Benjamin Constan do Sul', 58 | 'Bento Gonçalves', 59 | 'Boa Vista das Missões', 60 | 'Boa Vista do Buricá', 61 | 'Boa Vista do Cadeado', 62 | 'Boa Vista do Incra', 63 | 'Boa Vista do Sul', 64 | 'Bom Jesus', 65 | 'Bom Princípio', 66 | 'Bom Progresso', 67 | 'Bom Retiro do Sul', 68 | 'Boqueirão do Leão', 69 | 'Bossoroca', 70 | 'Bozano', 71 | 'Braga', 72 | 'Brochier', 73 | 'Butiá', 74 | 'Caçapava do Sul', 75 | 'Cacequi', 76 | 'Cachoeira do Sul', 77 | 'Cachoeirinha', 78 | 'Cacique Doble', 79 | 'Caibaté', 80 | 'Caiçara', 81 | 'Camaquã', 82 | 'Camargo', 83 | 'Cambará do Sul', 84 | 'Campestre da Serra', 85 | 'Campina das Missões', 86 | 'Campinas do Sul', 87 | 'Campo Bom', 88 | 'Campo Novo', 89 | 'Campos Borges', 90 | 'Candelária', 91 | 'Cândido Godói', 92 | 'Candiota', 93 | 'Canela', 94 | 'Canguçu', 95 | 'Canoas', 96 | 'Canudos do Vale', 97 | 'Capão Bonito do Sul', 98 | 'Capão da Canoa', 99 | 'Capão do Cipó', 100 | 'Capão do Leão', 101 | 'Capela de Santana', 102 | 'Capitão', 103 | 'Capivari do Sul', 104 | 'Caraá', 105 | 'Carazinho', 106 | 'Carlos Barbosa', 107 | 'Carlos Gomes', 108 | 'Casca', 109 | 'Caseiros', 110 | 'Catuípe', 111 | 'Caxias do Sul', 112 | 'Centenário', 113 | 'Cerrito', 114 | 'Cerro Branco', 115 | 'Cerro Grande', 116 | 'Cerro Grande do Sul', 117 | 'Cerro Largo', 118 | 'Chapada', 119 | 'Charqueadas', 120 | 'Charrua', 121 | 'Chiapeta', 122 | 'Chuí', 123 | 'Chuvisca', 124 | 'Cidreira', 125 | 'Ciríaco', 126 | 'Colinas', 127 | 'Colorado', 128 | 'Condor', 129 | 'Constantina', 130 | 'Coqueiro Baixo', 131 | 'Coqueiros do Sul', 132 | 'Coronel Barros', 133 | 'Coronel Bicaco', 134 | 'Coronel Pilar', 135 | 'Cotiporã', 136 | 'Coxilha', 137 | 'Crissiumal', 138 | 'Cristal', 139 | 'Cristal do Sul', 140 | 'Cruz Alta', 141 | 'Cruzaltense', 142 | 'Cruzeiro do Sul', 143 | 'David Canabarro', 144 | 'Derrubadas', 145 | 'Dezesseis de Novembro', 146 | 'Dilermando de Aguiar', 147 | 'Dois Irmãos', 148 | 'Dois Irmãos das Missões', 149 | 'Dois Lajeados', 150 | 'Dom Feliciano', 151 | 'Dom Pedrito', 152 | 'Dom Pedro de Alcântara', 153 | 'Dona Francisca', 154 | 'Doutor Maurício Cardoso', 155 | 'Doutor Ricardo', 156 | 'Eldorado do Sul', 157 | 'Encantado', 158 | 'Encruzilhada do Sul', 159 | 'Engenho Velho', 160 | 'Entre Rios do Sul', 161 | 'Entre-Ijuís', 162 | 'Erebango', 163 | 'Erechim', 164 | 'Ernestina', 165 | 'Erval Grande', 166 | 'Erval Seco', 167 | 'Esmeralda', 168 | 'Esperança do Sul', 169 | 'Espumoso', 170 | 'Estação', 171 | 'Estância Velha', 172 | 'Esteio', 173 | 'Estrela', 174 | 'Estrela Velha', 175 | 'Eugênio de Castro', 176 | 'Fagundes Varela', 177 | 'Farroupilha', 178 | 'Faxinal do Soturno', 179 | 'Faxinalzinho', 180 | 'Fazenda Vilanova', 181 | 'Feliz', 182 | 'Flores da Cunha', 183 | 'Floriano Peixoto', 184 | 'Fontoura Xavier', 185 | 'Formigueiro', 186 | 'Forquetinha', 187 | 'Fortaleza dos Valos', 188 | 'Frederico Westphalen', 189 | 'Garibaldi', 190 | 'Garruchos', 191 | 'Gaurama', 192 | 'General Câmara', 193 | 'Gentil', 194 | 'Getúlio Vargas', 195 | 'Giruá', 196 | 'Glorinha', 197 | 'Gramado', 198 | 'Gramado dos Loureiros', 199 | 'Gramado Xavier', 200 | 'Gravataí', 201 | 'Guabiju', 202 | 'Guaíba', 203 | 'Guaporé', 204 | 'Guarani das Missões', 205 | 'Harmonia', 206 | 'Herval', 207 | 'Herveiras', 208 | 'Horizontina', 209 | 'Hulha Negra', 210 | 'Humaitá', 211 | 'Ibarama', 212 | 'Ibiaçá', 213 | 'Ibiraiaras', 214 | 'Ibirapuitã', 215 | 'Ibirubá', 216 | 'Igrejinha', 217 | 'Ijuí', 218 | 'Ilópolis', 219 | 'Imbé', 220 | 'Imigrante', 221 | 'Independência', 222 | 'Inhacorá', 223 | 'Ipê', 224 | 'Ipiranga do Sul', 225 | 'Iraí', 226 | 'Itaara', 227 | 'Itacurubi', 228 | 'Itapuca', 229 | 'Itaqui', 230 | 'Itati', 231 | 'Itatiba do Sul', 232 | 'Ivorá', 233 | 'Ivoti', 234 | 'Jaboticaba', 235 | 'Jacuizinho', 236 | 'Jacutinga', 237 | 'Jaguarão', 238 | 'Jaguari', 239 | 'Jaquirana', 240 | 'Jari', 241 | 'Jóia', 242 | 'Júlio de Castilhos', 243 | 'Lagoa Bonita do Sul', 244 | 'Lagoa dos Três Cantos', 245 | 'Lagoa Vermelha', 246 | 'Lagoão', 247 | 'Lajeado', 248 | 'Lajeado do Bugre', 249 | 'Lavras do Sul', 250 | 'Liberato Salzano', 251 | 'Lindolfo Collor', 252 | 'Linha Nova', 253 | 'Maçambara', 254 | 'Machadinho', 255 | 'Mampituba', 256 | 'Manoel Viana', 257 | 'Maquiné', 258 | 'Maratá', 259 | 'Marau', 260 | 'Marcelino Ramos', 261 | 'Mariana Pimentel', 262 | 'Mariano Moro', 263 | 'Marques de Souza', 264 | 'Mata', 265 | 'Mato Castelhano', 266 | 'Mato Leitão', 267 | 'Mato Queimado', 268 | 'Maximiliano de Almeida', 269 | 'Minas do Leão', 270 | 'Miraguaí', 271 | 'Montauri', 272 | 'Monte Alegre dos Campos', 273 | 'Monte Belo do Sul', 274 | 'Montenegro', 275 | 'Mormaço', 276 | 'Morrinhos do Sul', 277 | 'Morro Redondo', 278 | 'Morro Reuter', 279 | 'Mostardas', 280 | 'Muçum', 281 | 'Muitos Capões', 282 | 'Muliterno', 283 | 'Não-Me-Toque', 284 | 'Nicolau Vergueiro', 285 | 'Nonoai', 286 | 'Nova Alvorada', 287 | 'Nova Araçá', 288 | 'Nova Bassano', 289 | 'Nova Boa Vista', 290 | 'Nova Bréscia', 291 | 'Nova Candelária', 292 | 'Nova Esperança do Sul', 293 | 'Nova Hartz', 294 | 'Nova Pádua', 295 | 'Nova Palma', 296 | 'Nova Petrópolis', 297 | 'Nova Prata', 298 | 'Nova Ramada', 299 | 'Nova Roma do Sul', 300 | 'Nova Santa Rita', 301 | 'Novo Barreiro', 302 | 'Novo Cabrais', 303 | 'Novo Hamburgo', 304 | 'Novo Machado', 305 | 'Novo Tiradentes', 306 | 'Novo Xingu', 307 | 'Osório', 308 | 'Paim Filho', 309 | 'Palmares do Sul', 310 | 'Palmeira das Missões', 311 | 'Palmitinho', 312 | 'Panambi', 313 | 'Pântano Grande', 314 | 'Paraí', 315 | 'Paraíso do Sul', 316 | 'Pareci Novo', 317 | 'Parobé', 318 | 'Passa Sete', 319 | 'Passo do Sobrado', 320 | 'Passo Fundo', 321 | 'Paulo Bento', 322 | 'Paverama', 323 | 'Pedras Altas', 324 | 'Pedro Osório', 325 | 'Pejuçara', 326 | 'Pelotas', 327 | 'Picada Café', 328 | 'Pinhal', 329 | 'Pinhal da Serra', 330 | 'Pinhal Grande', 331 | 'Pinheirinho do Vale', 332 | 'Pinheiro Machado', 333 | 'Pirapó', 334 | 'Piratini', 335 | 'Planalto', 336 | 'Poço das Antas', 337 | 'Pontão', 338 | 'Ponte Preta', 339 | 'Portão', 340 | 'Porto Alegre', 341 | 'Porto Lucena', 342 | 'Porto Mauá', 343 | 'Porto Vera Cruz', 344 | 'Porto Xavier', 345 | 'Pouso Novo', 346 | 'Presidente Lucena', 347 | 'Progresso', 348 | 'Protásio Alves', 349 | 'Putinga', 350 | 'Quaraí', 351 | 'Quatro Irmãos', 352 | 'Quevedos', 353 | 'Quinze de Novembro', 354 | 'Redentora', 355 | 'Relvado', 356 | 'Restinga Seca', 357 | 'Rio dos Índios', 358 | 'Rio Grande', 359 | 'Rio Pardo', 360 | 'Riozinho', 361 | 'Roca Sales', 362 | 'Rodeio Bonito', 363 | 'Rolador', 364 | 'Rolante', 365 | 'Ronda Alta', 366 | 'Rondinha', 367 | 'Roque Gonzales', 368 | 'Rosário do Sul', 369 | 'Sagrada Família', 370 | 'Saldanha Marinho', 371 | 'Salto do Jacuí', 372 | 'Salvador das Missões', 373 | 'Salvador do Sul', 374 | 'Sananduva', 375 | 'Santa Bárbara do Sul', 376 | 'Santa Cecília do Sul', 377 | 'Santa Clara do Sul', 378 | 'Santa Cruz do Sul', 379 | 'Santa Margarida do Sul', 380 | 'Santa Maria', 381 | 'Santa Maria do Herval', 382 | 'Santa Rosa', 383 | 'Santa Tereza', 384 | 'Santa Vitória do Palmar', 385 | 'Santana da Boa Vista', 386 | 'Santana do Livramento', 387 | 'Santiago', 388 | 'Santo Ângelo', 389 | 'Santo Antônio da Patrulha', 390 | 'Santo Antônio das Missões', 391 | 'Santo Antônio do Palma', 392 | 'Santo Antônio do Planalto', 393 | 'Santo Augusto', 394 | 'Santo Cristo', 395 | 'Santo Expedito do Sul', 396 | 'São Borja', 397 | 'São Domingos do Sul', 398 | 'São Francisco de Assis', 399 | 'São Francisco de Paula', 400 | 'São Gabriel', 401 | 'São Jerônimo', 402 | 'São João da Urtiga', 403 | 'São João do Polêsine', 404 | 'São Jorge', 405 | 'São José das Missões', 406 | 'São José do Herval', 407 | 'São José do Hortêncio', 408 | 'São José do Inhacorá', 409 | 'São José do Norte', 410 | 'São José do Ouro', 411 | 'São José do Sul', 412 | 'São José dos Ausentes', 413 | 'São Leopoldo', 414 | 'São Lourenço do Sul', 415 | 'São Luiz Gonzaga', 416 | 'São Marcos', 417 | 'São Martinho', 418 | 'São Martinho da Serra', 419 | 'São Miguel das Missões', 420 | 'São Nicolau', 421 | 'São Paulo das Missões', 422 | 'São Pedro da Serra', 423 | 'São Pedro das Missões', 424 | 'São Pedro do Butiá', 425 | 'São Pedro do Sul', 426 | 'São Sebastião do Caí', 427 | 'São Sepé', 428 | 'São Valentim', 429 | 'São Valentim do Sul', 430 | 'São Valério do Sul', 431 | 'São Vendelino', 432 | 'São Vicente do Sul', 433 | 'Sapiranga', 434 | 'Sapucaia do Sul', 435 | 'Sarandi', 436 | 'Seberi', 437 | 'Sede Nova', 438 | 'Segredo', 439 | 'Selbach', 440 | 'Senador Salgado Filho', 441 | 'Sentinela do Sul', 442 | 'Serafina Corrêa', 443 | 'Sério', 444 | 'Sertão', 445 | 'Sertão Santana', 446 | 'Sete de Setembro', 447 | 'Severiano de Almeida', 448 | 'Silveira Martins', 449 | 'Sinimbu', 450 | 'Sobradinho', 451 | 'Soledade', 452 | 'Tabaí', 453 | 'Tapejara', 454 | 'Tapera', 455 | 'Tapes', 456 | 'Taquara', 457 | 'Taquari', 458 | 'Taquaruçu do Sul', 459 | 'Tavares', 460 | 'Tenente Portela', 461 | 'Terra de Areia', 462 | 'Teutônia', 463 | 'Tio Hugo', 464 | 'Tiradentes do Sul', 465 | 'Toropi', 466 | 'Torres', 467 | 'Tramandaí', 468 | 'Travesseiro', 469 | 'Três Arroios', 470 | 'Três Cachoeiras', 471 | 'Três Coroas', 472 | 'Três de Maio', 473 | 'Três Forquilhas', 474 | 'Três Palmeiras', 475 | 'Três Passos', 476 | 'Trindade do Sul', 477 | 'Triunfo', 478 | 'Tucunduva', 479 | 'Tunas', 480 | 'Tupanci do Sul', 481 | 'Tupanciretã', 482 | 'Tupandi', 483 | 'Tuparendi', 484 | 'Turuçu', 485 | 'Ubiretama', 486 | 'União da Serra', 487 | 'Unistalda', 488 | 'Uruguaiana', 489 | 'Vacaria', 490 | 'Vale do Sol', 491 | 'Vale Real', 492 | 'Vale Verde', 493 | 'Vanini', 494 | 'Venâncio Aires', 495 | 'Vera Cruz', 496 | 'Veranópolis', 497 | 'Vespasiano Correa', 498 | 'Viadutos', 499 | 'Viamão', 500 | 'Vicente Dutra', 501 | 'Victor Graeff', 502 | 'Vila Flores', 503 | 'Vila Lângaro', 504 | 'Vila Maria', 505 | 'Vila Nova do Sul', 506 | 'Vista Alegre', 507 | 'Vista Alegre do Prata', 508 | 'Vista Gaúcha', 509 | 'Vitória das Missões', 510 | 'Westfália', 511 | 'Xangri-lá' 512 | ]; 513 | -------------------------------------------------------------------------------- /src/server/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import { CidadesController, PessoasController, UsuariosController } from './../controllers'; 4 | import { ensureAuthenticated } from '../shared/middleware'; 5 | 6 | 7 | 8 | const router = Router(); 9 | 10 | 11 | 12 | router.get('/', (_, res) => { 13 | return res.send('Olá, DEV!'); 14 | }); 15 | 16 | router.get('/cidades', ensureAuthenticated, CidadesController.getAllValidation, CidadesController.getAll); 17 | router.post('/cidades', ensureAuthenticated, CidadesController.createValidation, CidadesController.create); 18 | router.get('/cidades/:id', ensureAuthenticated, CidadesController.getByIdValidation, CidadesController.getById); 19 | router.put('/cidades/:id', ensureAuthenticated, CidadesController.updateByIdValidation, CidadesController.updateById); 20 | router.delete('/cidades/:id', ensureAuthenticated, CidadesController.deleteByIdValidation, CidadesController.deleteById); 21 | 22 | router.get('/pessoas', ensureAuthenticated, PessoasController.getAllValidation, PessoasController.getAll); 23 | router.post('/pessoas', ensureAuthenticated, PessoasController.createValidation, PessoasController.create); 24 | router.get('/pessoas/:id', ensureAuthenticated, PessoasController.getByIdValidation, PessoasController.getById); 25 | router.put('/pessoas/:id', ensureAuthenticated, PessoasController.updateByIdValidation, PessoasController.updateById); 26 | router.delete('/pessoas/:id', ensureAuthenticated, PessoasController.deleteByIdValidation, PessoasController.deleteById); 27 | 28 | router.post('/entrar', UsuariosController.signInValidation, UsuariosController.signIn); 29 | router.post('/cadastrar', UsuariosController.signUpValidation, UsuariosController.signUp); 30 | 31 | 32 | 33 | export { router }; 34 | -------------------------------------------------------------------------------- /src/server/shared/middleware/EnsureAuthenticated.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | 4 | import { JWTService } from '../services'; 5 | 6 | 7 | export const ensureAuthenticated: RequestHandler = async (req, res, next) => { 8 | const { authorization } = req.headers; 9 | 10 | if (!authorization) { 11 | return res.status(StatusCodes.UNAUTHORIZED).json({ 12 | errors: { default: 'Não autenticado' } 13 | }); 14 | } 15 | 16 | const [type, token] = authorization.split(' '); 17 | 18 | if (type !== 'Bearer') { 19 | return res.status(StatusCodes.UNAUTHORIZED).json({ 20 | errors: { default: 'Não autenticado' } 21 | }); 22 | } 23 | 24 | const jwtData = JWTService.verify(token); 25 | if (jwtData === 'JWT_SECRET_NOT_FOUND') { 26 | return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 27 | errors: { default: 'Erro ao verificar o token' } 28 | }); 29 | } else if (jwtData === 'INVALID_TOKEN') { 30 | return res.status(StatusCodes.UNAUTHORIZED).json({ 31 | errors: { default: 'Não autenticado' } 32 | }); 33 | } 34 | 35 | req.headers.idUsuario = jwtData.uid.toString(); 36 | 37 | return next(); 38 | }; 39 | -------------------------------------------------------------------------------- /src/server/shared/middleware/JSONParseError.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { NextFunction, Response } from 'express'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | 5 | 6 | export const JSONParseError = (err: any, _: any, res: Response, next: NextFunction) => { 7 | if (err instanceof SyntaxError) { 8 | res.status(StatusCodes.BAD_REQUEST).json({ 9 | errors: { 10 | default: 'Formato enviado é incorreto' 11 | } 12 | }); 13 | } else { 14 | next(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/server/shared/middleware/Validation.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express'; 2 | import { AnyObject, Maybe, ObjectSchema, ValidationError } from 'yup'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | 5 | 6 | type TProperty = 'body' | 'header' | 'params' | 'query'; 7 | 8 | type TGetSchema = >(schema: ObjectSchema) => ObjectSchema 9 | 10 | type TAllSchemas = Record>; 11 | 12 | type TGetAllSchemas = (getSchema: TGetSchema) => Partial; 13 | 14 | type TValidation = (getAllSchemas: TGetAllSchemas) => RequestHandler; 15 | 16 | export const validation: TValidation = (getAllSchemas) => async (req, res, next) => { 17 | const schemas = getAllSchemas((schema) => schema); 18 | 19 | 20 | const errorsResult: Record> = {}; 21 | 22 | Object.entries(schemas).forEach(([key, schema]) => { 23 | try { 24 | schema.validateSync(req[key as TProperty], { abortEarly: false }); 25 | } catch (err) { 26 | const yupError = err as ValidationError; 27 | const errors: Record = {}; 28 | 29 | yupError.inner.forEach(error => { 30 | if (error.path === undefined) return; 31 | errors[error.path] = error.message; 32 | }); 33 | 34 | errorsResult[key] = errors; 35 | } 36 | }); 37 | 38 | 39 | if (Object.entries(errorsResult).length === 0) { 40 | return next(); 41 | } else { 42 | return res.status(StatusCodes.BAD_REQUEST).json({ errors: errorsResult }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/server/shared/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EnsureAuthenticated'; 2 | export * from './JSONParseError'; 3 | export * from './Validation'; 4 | -------------------------------------------------------------------------------- /src/server/shared/services/JWTService.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | 3 | 4 | interface IJwtData { 5 | uid: number; 6 | } 7 | 8 | const sign = (data: IJwtData): string | 'JWT_SECRET_NOT_FOUND' => { 9 | if (!process.env.JWT_SECRET) return 'JWT_SECRET_NOT_FOUND'; 10 | 11 | return jwt.sign(data, process.env.JWT_SECRET, { expiresIn: '24h' }); 12 | }; 13 | 14 | const verify = (token: string): IJwtData | 'JWT_SECRET_NOT_FOUND' | 'INVALID_TOKEN' => { 15 | if (!process.env.JWT_SECRET) return 'JWT_SECRET_NOT_FOUND'; 16 | 17 | try { 18 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 19 | if (typeof decoded === 'string') { 20 | return 'INVALID_TOKEN'; 21 | } 22 | 23 | return decoded as IJwtData; 24 | } catch (error) { 25 | return 'INVALID_TOKEN'; 26 | } 27 | }; 28 | 29 | export const JWTService = { 30 | sign, 31 | verify, 32 | }; 33 | -------------------------------------------------------------------------------- /src/server/shared/services/PasswordCrypto.ts: -------------------------------------------------------------------------------- 1 | import { genSalt, hash, compare } from 'bcryptjs'; 2 | 3 | 4 | const SALT_RANDOMS = 8; 5 | 6 | const hashPassword = async (password: string) => { 7 | const saltGenerated = await genSalt(SALT_RANDOMS); 8 | 9 | return await hash(password, saltGenerated); 10 | }; 11 | 12 | const verifyPassword = async (password: string, hashedPassword: string) => { 13 | return await compare(password, hashedPassword); 14 | }; 15 | 16 | 17 | export const PasswordCrypto = { 18 | hashPassword, 19 | verifyPassword, 20 | }; 21 | -------------------------------------------------------------------------------- /src/server/shared/services/TranslationsYup.ts: -------------------------------------------------------------------------------- 1 | import { setLocale } from 'yup'; 2 | 3 | setLocale({ 4 | mixed: { 5 | required: 'Este campo é obrigatório', 6 | notType: 'Formato digitado é invalido', 7 | defined: 'Este campo precisa ter um valor definido', 8 | oneOf: 'Deve ser um dos seguintes valores: ${values}', 9 | notOneOf: 'Não pode ser um dos seguintes valores: ${values}', 10 | }, 11 | string: { 12 | lowercase: 'Deve estar em maiúsculo', 13 | uppercase: 'Deve estar em minúsculo', 14 | url: 'Deve ter um formato de URL válida', 15 | max: 'Deve ter no máximo ${max} caracteres', 16 | min: 'Deve ter pelo menos ${min} caracteres', 17 | email: 'Formato de e-mail digitado não é valido', 18 | length: 'Deve ter exatamente ${length} caracteres', 19 | uuid: 'Valor digitado não confere a um UUID valido', 20 | trim: 'Não deve conter espaços no início ou no fim.', 21 | matches: 'O valor deve corresponder ao padrão: ${regex}', 22 | }, 23 | number: { 24 | min: 'Deve ser no mínimo ${min}', 25 | max: 'Deve ser no máximo ${max}', 26 | integer: 'Deve ser um número inteiro', 27 | lessThan: 'Deve ser menor que ${less}', 28 | moreThan: 'Deve ser maior que ${more}', 29 | positive: 'Deve ser um número positivo', 30 | negative: 'Deve ser um número negativo', 31 | }, 32 | date: { 33 | min: 'Deve ser maior que a data ${min}', 34 | max: 'Deve ser menor que a data ${max}', 35 | }, 36 | array: { 37 | min: 'Deve ter no mínimo ${min} itens', 38 | max: 'Deve ter no máximo ${max} itens', 39 | length: 'Deve conter exatamente ${length} itens', 40 | }, 41 | object: { 42 | noUnknown: 'Deve ser passado um valor definido', 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/server/shared/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PasswordCrypto'; 2 | export * from './JWTService'; 3 | -------------------------------------------------------------------------------- /tests/cidades/Create.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Cidades - Create', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'create-cidades@gmail.com'; 10 | await testServer.post('/cadastrar').send({ nome: 'Teste', email, senha: '123456' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | 17 | it('Tenta criar um registro sem token de acesso', async () => { 18 | const res1 = await testServer 19 | .post('/cidades') 20 | .send({ nome: 'Caxias do Sul' }); 21 | 22 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 23 | expect(res1.body).toHaveProperty('errors.default'); 24 | }); 25 | it('Cria registro', async () => { 26 | const res1 = await testServer 27 | .post('/cidades') 28 | .set({ Authorization: `Bearer ${accessToken}` }) 29 | .send({ nome: 'Caxias do Sul' }); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 32 | expect(typeof res1.body).toEqual('number'); 33 | }); 34 | it('Tenta criar um registro com nome muito curto', async () => { 35 | const res1 = await testServer 36 | .post('/cidades') 37 | .set({ Authorization: `Bearer ${accessToken}` }) 38 | .send({ nome: 'Ca' }); 39 | 40 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 41 | expect(res1.body).toHaveProperty('errors.body.nome'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/cidades/DeleteById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Cidades - DeleteById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'deletebuid-cidades@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | 17 | it('Tenta apagar registro sem usar token de autenticação', async () => { 18 | const res1 = await testServer 19 | .delete('/cidades/1') 20 | .send(); 21 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 22 | expect(res1.body).toHaveProperty('errors.default'); 23 | }); 24 | it('Apaga registro', async () => { 25 | 26 | const res1 = await testServer 27 | .post('/cidades') 28 | .set({ Authorization: `Bearer ${accessToken}` }) 29 | .send({ nome: 'Caxias do sul' }); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 32 | 33 | const resApagada = await testServer 34 | .delete(`/cidades/${res1.body}`) 35 | .set({ Authorization: `Bearer ${accessToken}` }) 36 | .send(); 37 | 38 | expect(resApagada.statusCode).toEqual(StatusCodes.NO_CONTENT); 39 | }); 40 | it('Tenta apagar registro que não existe', async () => { 41 | 42 | const res1 = await testServer 43 | .delete('/cidades/99999') 44 | .set({ Authorization: `Bearer ${accessToken}` }) 45 | .send(); 46 | 47 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 48 | expect(res1.body).toHaveProperty('errors.default'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/cidades/GetAll.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Cidades - GetAll', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'getall-cidades@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | 17 | it('Tenta consultar sem usar token de autenticação', async () => { 18 | const res1 = await testServer 19 | .get('/cidades') 20 | .send(); 21 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 22 | expect(res1.body).toHaveProperty('errors.default'); 23 | }); 24 | it('Buscar todos os registros', async () => { 25 | 26 | const res1 = await testServer 27 | .post('/cidades') 28 | .set({ Authorization: `Bearer ${accessToken}` }) 29 | .send({ nome: 'Caxias do sul' }); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 32 | 33 | const resBuscada = await testServer 34 | .get('/cidades') 35 | .set({ Authorization: `Bearer ${accessToken}` }) 36 | .send(); 37 | 38 | expect(Number(resBuscada.header['x-total-count'])).toBeGreaterThan(0); 39 | expect(resBuscada.statusCode).toEqual(StatusCodes.OK); 40 | expect(resBuscada.body.length).toBeGreaterThan(0); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/cidades/GetById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Cidades - GetById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'getbyid-cidades@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | 17 | it('Tenta consultar sem usar token de autenticação', async () => { 18 | const res1 = await testServer 19 | .get('/cidades/1') 20 | .send(); 21 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 22 | expect(res1.body).toHaveProperty('errors.default'); 23 | }); 24 | it('Busca registro por id', async () => { 25 | 26 | const res1 = await testServer 27 | .post('/cidades') 28 | .set({ Authorization: `Bearer ${accessToken}` }) 29 | .send({ nome: 'Caxias do sul' }); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 32 | 33 | const resBuscada = await testServer 34 | .get(`/cidades/${res1.body}`) 35 | .set({ Authorization: `Bearer ${accessToken}` }) 36 | .send(); 37 | 38 | expect(resBuscada.statusCode).toEqual(StatusCodes.OK); 39 | expect(resBuscada.body).toHaveProperty('nome'); 40 | }); 41 | it('Tenta buscar registro que não existe', async () => { 42 | 43 | const res1 = await testServer 44 | .get('/cidades/99999') 45 | .set({ Authorization: `Bearer ${accessToken}` }) 46 | .send(); 47 | 48 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 49 | expect(res1.body).toHaveProperty('errors.default'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/cidades/UpdateById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Cidades - UpdateById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'updatebyid-cidades@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | 17 | it('Tenta atualizar sem usar token de autenticação', async () => { 18 | const res1 = await testServer 19 | .put('/cidades/1') 20 | .send({ nome: 'Teste' }); 21 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 22 | expect(res1.body).toHaveProperty('errors.default'); 23 | }); 24 | it('Atualiza registro', async () => { 25 | 26 | const res1 = await testServer 27 | .post('/cidades') 28 | .set({ Authorization: `Bearer ${accessToken}` }) 29 | .send({ nome: 'Caxias do sul' }); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 32 | 33 | const resAtualizada = await testServer 34 | .put(`/cidades/${res1.body}`) 35 | .set({ Authorization: `Bearer ${accessToken}` }) 36 | .send({ nome: 'Caxias' }); 37 | 38 | expect(resAtualizada.statusCode).toEqual(StatusCodes.NO_CONTENT); 39 | }); 40 | it('Tenta atualizar registro que não existe', async () => { 41 | 42 | const res1 = await testServer 43 | .put('/cidades/99999') 44 | .set({ Authorization: `Bearer ${accessToken}` }) 45 | .send({ nome: 'Caxias' }); 46 | 47 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 48 | expect(res1.body).toHaveProperty('errors.default'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import supertest from 'supertest'; 2 | import { Knex } from '../src/server/database/knex'; 3 | 4 | import { server } from '../src/server/Server'; 5 | 6 | 7 | beforeAll(async () => { 8 | await Knex.migrate.latest(); 9 | await Knex.seed.run(); 10 | }); 11 | 12 | afterAll(async () => { 13 | await Knex.destroy(); 14 | }); 15 | 16 | 17 | export const testServer = supertest(server); 18 | -------------------------------------------------------------------------------- /tests/pessoas/Create.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Pessoas - Create', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'create-pessoas@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | let cidadeId: number | undefined = undefined; 17 | beforeAll(async () => { 18 | const resCidade = await testServer 19 | .post('/cidades') 20 | .set({ Authorization: `Bearer ${accessToken}` }) 21 | .send({ nome: 'Teste' }); 22 | 23 | cidadeId = resCidade.body; 24 | }); 25 | 26 | 27 | it('Criar sem usar token de autenticação', async () => { 28 | const res1 = await testServer 29 | .post('/pessoas') 30 | .send({ 31 | cidadeId: 1, 32 | email: 'juca@gmail.com', 33 | nomeCompleto: 'Juca silva', 34 | }); 35 | 36 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 37 | expect(res1.body).toHaveProperty('errors.default'); 38 | }); 39 | it('Cria registro', async () => { 40 | const res1 = await testServer 41 | .post('/pessoas') 42 | .set({ Authorization: `Bearer ${accessToken}` }) 43 | .send({ 44 | cidadeId, 45 | email: 'juca@gmail.com', 46 | nomeCompleto: 'Juca silva', 47 | }); 48 | 49 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 50 | expect(typeof res1.body).toEqual('number'); 51 | }); 52 | it('Cadastra registro 2', async () => { 53 | const res1 = await testServer 54 | .post('/pessoas') 55 | .set({ Authorization: `Bearer ${accessToken}` }) 56 | .send({ 57 | cidadeId, 58 | nomeCompleto: 'Juca silva', 59 | email: 'juca2@gmail.com', 60 | }); 61 | 62 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 63 | expect(typeof res1.body).toEqual('number'); 64 | }); 65 | it('Tenta criar registro com email duplicado', async () => { 66 | const res1 = await testServer 67 | .post('/pessoas') 68 | .set({ Authorization: `Bearer ${accessToken}` }) 69 | .send({ 70 | cidadeId, 71 | nomeCompleto: 'Juca silva', 72 | email: 'jucaduplicado@gmail.com', 73 | }); 74 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 75 | expect(typeof res1.body).toEqual('number'); 76 | 77 | const res2 = await testServer 78 | .post('/pessoas') 79 | .set({ Authorization: `Bearer ${accessToken}` }) 80 | .send({ 81 | cidadeId, 82 | email: 'jucaduplicado@gmail.com', 83 | nomeCompleto: 'duplicado', 84 | }); 85 | expect(res2.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 86 | expect(res2.body).toHaveProperty('errors.default'); 87 | }); 88 | it('Tenta criar registro com nomeCompleto muito curto', async () => { 89 | const res1 = await testServer 90 | .post('/pessoas') 91 | .set({ Authorization: `Bearer ${accessToken}` }) 92 | .send({ 93 | cidadeId, 94 | email: 'juca@gmail.com', 95 | nomeCompleto: 'Ju', 96 | }); 97 | 98 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 99 | expect(res1.body).toHaveProperty('errors.body.nomeCompleto'); 100 | }); 101 | it('Tenta criar registro sem nomeCompleto', async () => { 102 | const res1 = await testServer 103 | .post('/pessoas') 104 | .set({ Authorization: `Bearer ${accessToken}` }) 105 | .send({ 106 | cidadeId, 107 | email: 'juca@gmail.com', 108 | }); 109 | 110 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 111 | expect(res1.body).toHaveProperty('errors.body.nomeCompleto'); 112 | }); 113 | it('Tenta criar registro sem email', async () => { 114 | const res1 = await testServer 115 | .post('/pessoas') 116 | .set({ Authorization: `Bearer ${accessToken}` }) 117 | .send({ 118 | cidadeId, 119 | nomeCompleto: 'Juca da Silva', 120 | }); 121 | 122 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 123 | expect(res1.body).toHaveProperty('errors.body.email'); 124 | }); 125 | it('Tenta criar registro com email inválido', async () => { 126 | const res1 = await testServer 127 | .post('/pessoas') 128 | .set({ Authorization: `Bearer ${accessToken}` }) 129 | .send({ 130 | cidadeId, 131 | email: 'juca gmail.com', 132 | nomeCompleto: 'Juca da Silva', 133 | }); 134 | 135 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 136 | expect(res1.body).toHaveProperty('errors.body.email'); 137 | }); 138 | it('Tenta criar registro sem cidadeId', async () => { 139 | const res1 = await testServer 140 | .post('/pessoas') 141 | .set({ Authorization: `Bearer ${accessToken}` }) 142 | .send({ 143 | email: 'juca@gmail.com', 144 | nomeCompleto: 'Juca da Silva', 145 | }); 146 | 147 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 148 | expect(res1.body).toHaveProperty('errors.body.cidadeId'); 149 | }); 150 | it('Tenta criar registro com cidadeId inválido', async () => { 151 | const res1 = await testServer 152 | .post('/pessoas') 153 | .set({ Authorization: `Bearer ${accessToken}` }) 154 | .send({ 155 | cidadeId: 'teste', 156 | email: 'juca@gmail.com', 157 | nomeCompleto: 'Juca da Silva', 158 | }); 159 | 160 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 161 | expect(res1.body).toHaveProperty('errors.body.cidadeId'); 162 | }); 163 | it('Tenta criar registro sem enviar nenhuma propriedade', async () => { 164 | 165 | const res1 = await testServer 166 | .post('/pessoas') 167 | .set({ Authorization: `Bearer ${accessToken}` }) 168 | .send({}); 169 | 170 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 171 | expect(res1.body).toHaveProperty('errors.body.email'); 172 | expect(res1.body).toHaveProperty('errors.body.cidadeId'); 173 | expect(res1.body).toHaveProperty('errors.body.nomeCompleto'); 174 | }); 175 | }); 176 | -------------------------------------------------------------------------------- /tests/pessoas/DeleteById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Pessoas - DeleteById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'delete-pessoas@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | let cidadeId: number | undefined = undefined; 17 | beforeAll(async () => { 18 | const resCidade = await testServer 19 | .post('/cidades') 20 | .set({ Authorization: `Bearer ${accessToken}` }) 21 | .send({ nome: 'Teste' }); 22 | 23 | cidadeId = resCidade.body; 24 | }); 25 | 26 | it('Tenta apagar registro sem usar token de autenticação', async () => { 27 | const res1 = await testServer 28 | .delete('/pessoas/1') 29 | .send(); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 32 | expect(res1.body).toHaveProperty('errors.default'); 33 | }); 34 | it('Apaga registro', async () => { 35 | const res1 = await testServer 36 | .post('/pessoas') 37 | .set({ Authorization: `Bearer ${accessToken}` }) 38 | .send({ 39 | cidadeId, 40 | email: 'jucadelete@gmail.com', 41 | nomeCompleto: 'Juca silva', 42 | }); 43 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 44 | 45 | const resApagada = await testServer 46 | .delete(`/pessoas/${res1.body}`) 47 | .set({ Authorization: `Bearer ${accessToken}` }) 48 | .send(); 49 | expect(resApagada.statusCode).toEqual(StatusCodes.NO_CONTENT); 50 | }); 51 | it('Tenta apagar registro que não existe', async () => { 52 | const res1 = await testServer 53 | .delete('/pessoas/99999') 54 | .set({ Authorization: `Bearer ${accessToken}` }) 55 | .send(); 56 | 57 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 58 | expect(res1.body).toHaveProperty('errors.default'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/pessoas/GetAll.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Pessoas - GetAll', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'getall-pessoas@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | let cidadeId: number | undefined = undefined; 17 | beforeAll(async () => { 18 | const resCidade = await testServer 19 | .post('/cidades') 20 | .set({ Authorization: `Bearer ${accessToken}` }) 21 | .send({ nome: 'Teste' }); 22 | 23 | cidadeId = resCidade.body; 24 | }); 25 | 26 | it('Tenta consultar sem usar token de autenticação', async () => { 27 | const res1 = await testServer 28 | .get('/pessoas') 29 | .send(); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 32 | expect(res1.body).toHaveProperty('errors.default'); 33 | }); 34 | it('Busca registros', async () => { 35 | const res1 = await testServer 36 | .post('/pessoas') 37 | .set({ Authorization: `Bearer ${accessToken}` }) 38 | .send({ 39 | cidadeId, 40 | email: 'jucagetall@gmail.com', 41 | nomeCompleto: 'Juca silva', 42 | }); 43 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 44 | 45 | const resBuscada = await testServer 46 | .get('/pessoas') 47 | .set({ Authorization: `Bearer ${accessToken}` }) 48 | .send(); 49 | expect(Number(resBuscada.header['x-total-count'])).toBeGreaterThan(0); 50 | expect(resBuscada.statusCode).toEqual(StatusCodes.OK); 51 | expect(resBuscada.body.length).toBeGreaterThan(0); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /tests/pessoas/GetById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Pessoas - GetById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'getbyid-pessoas@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | let cidadeId: number | undefined = undefined; 17 | beforeAll(async () => { 18 | const resCidade = await testServer 19 | .post('/cidades') 20 | .set({ Authorization: `Bearer ${accessToken}` }) 21 | .send({ nome: 'Teste' }); 22 | 23 | cidadeId = resCidade.body; 24 | }); 25 | 26 | it('Tenta consultar sem usar token de autenticação', async () => { 27 | const res1 = await testServer 28 | .get('/pessoas/1') 29 | .send(); 30 | 31 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 32 | expect(res1.body).toHaveProperty('errors.default'); 33 | }); 34 | it('Busca registro por id', async () => { 35 | const res1 = await testServer 36 | .post('/pessoas') 37 | .set({ Authorization: `Bearer ${accessToken}` }) 38 | .send({ 39 | cidadeId, 40 | nomeCompleto: 'Juca silva', 41 | email: 'jucagetbyid@gmail.com', 42 | }); 43 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 44 | 45 | const resBuscada = await testServer 46 | .get(`/pessoas/${res1.body}`) 47 | .set({ Authorization: `Bearer ${accessToken}` }) 48 | .send(); 49 | expect(resBuscada.statusCode).toEqual(StatusCodes.OK); 50 | expect(resBuscada.body).toHaveProperty('nomeCompleto'); 51 | }); 52 | it('Tenta buscar registro que não existe', async () => { 53 | const res1 = await testServer 54 | .get('/pessoas/99999') 55 | .set({ Authorization: `Bearer ${accessToken}` }) 56 | .send(); 57 | 58 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 59 | expect(res1.body).toHaveProperty('errors.default'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /tests/pessoas/UpdateById.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Pessoas - UpdateById', () => { 7 | let accessToken = ''; 8 | beforeAll(async () => { 9 | const email = 'updatebyid-pessoas@gmail.com'; 10 | await testServer.post('/cadastrar').send({ email, senha: '123456', nome: 'Teste' }); 11 | const signInRes = await testServer.post('/entrar').send({ email, senha: '123456' }); 12 | 13 | accessToken = signInRes.body.accessToken; 14 | }); 15 | 16 | let cidadeId: number | undefined = undefined; 17 | beforeAll(async () => { 18 | const resCidade = await testServer 19 | .post('/cidades') 20 | .set({ Authorization: `Bearer ${accessToken}` }) 21 | .send({ nome: 'Teste' }); 22 | 23 | cidadeId = resCidade.body; 24 | }); 25 | 26 | it('Tenta atualizar sem usar token de autenticação', async () => { 27 | const res1 = await testServer 28 | .put('/pessoas/1') 29 | .send({ 30 | cidadeId: 1, 31 | email: 'juca@gmail.com', 32 | nomeCompleto: 'Juca silva', 33 | }); 34 | 35 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 36 | expect(res1.body).toHaveProperty('errors.default'); 37 | }); 38 | it('Atualiza registro', async () => { 39 | const res1 = await testServer 40 | .post('/pessoas') 41 | .set({ Authorization: `Bearer ${accessToken}` }) 42 | .send({ 43 | cidadeId, 44 | nomeCompleto: 'Juca silva', 45 | email: 'jucaupdate@gmail.com', 46 | }); 47 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 48 | 49 | const resAtualizada = await testServer 50 | .put(`/pessoas/${res1.body}`) 51 | .set({ Authorization: `Bearer ${accessToken}` }) 52 | .send({ 53 | cidadeId, 54 | nomeCompleto: 'Juca silva', 55 | email: 'jucaupdates@gmail.com', 56 | }); 57 | expect(resAtualizada.statusCode).toEqual(StatusCodes.NO_CONTENT); 58 | }); 59 | it('Tenta atualizar registro que não existe', async () => { 60 | const res1 = await testServer 61 | .put('/pessoas/99999') 62 | .set({ Authorization: `Bearer ${accessToken}` }) 63 | .send({ 64 | cidadeId, 65 | email: 'juca@gmail.com', 66 | nomeCompleto: 'Juca silva', 67 | }); 68 | 69 | expect(res1.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 70 | expect(res1.body).toHaveProperty('errors.default'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/usuarios/SignIn.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Usuários - SignIn', () => { 7 | beforeAll(async () => { 8 | await testServer.post('/cadastrar').send({ 9 | nome: 'Jorge', 10 | senha: '123456', 11 | email: 'jorge@gmail.com', 12 | }); 13 | }); 14 | 15 | it('Faz login', async () => { 16 | const res1 = await testServer 17 | .post('/entrar') 18 | .send({ 19 | senha: '123456', 20 | email: 'jorge@gmail.com', 21 | }); 22 | expect(res1.statusCode).toEqual(StatusCodes.OK); 23 | expect(res1.body).toHaveProperty('accessToken'); 24 | }); 25 | it('Senha errada', async () => { 26 | const res1 = await testServer 27 | .post('/entrar') 28 | .send({ 29 | senha: '1234567', 30 | email: 'jorge@gmail.com', 31 | }); 32 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 33 | expect(res1.body).toHaveProperty('errors.default'); 34 | }); 35 | it('Email errado', async () => { 36 | const res1 = await testServer 37 | .post('/entrar') 38 | .send({ 39 | senha: '123456', 40 | email: 'jorgeeeeeee@gmail.com', 41 | }); 42 | expect(res1.statusCode).toEqual(StatusCodes.UNAUTHORIZED); 43 | expect(res1.body).toHaveProperty('errors.default'); 44 | }); 45 | it('Formato de email inválido', async () => { 46 | const res1 = await testServer 47 | .post('/entrar') 48 | .send({ 49 | senha: '123456', 50 | email: 'jorge gmail.com', 51 | }); 52 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 53 | expect(res1.body).toHaveProperty('errors.body.email'); 54 | }); 55 | it('Senha muito pequena', async () => { 56 | const res1 = await testServer 57 | .post('/entrar') 58 | .send({ 59 | senha: '12', 60 | email: 'jorge@gmail.com', 61 | }); 62 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 63 | expect(res1.body).toHaveProperty('errors.body.senha'); 64 | }); 65 | it('Não informado a senha', async () => { 66 | const res1 = await testServer 67 | .post('/entrar') 68 | .send({ 69 | email: 'jorge@gmail.com', 70 | }); 71 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 72 | expect(res1.body).toHaveProperty('errors.body.senha'); 73 | }); 74 | it('Não informado email', async () => { 75 | const res1 = await testServer 76 | .post('/entrar') 77 | .send({ 78 | senha: '123456', 79 | }); 80 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 81 | expect(res1.body).toHaveProperty('errors.body.email'); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /tests/usuarios/SignUp.test.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | import { testServer } from '../jest.setup'; 4 | 5 | 6 | describe('Usuário - SignUp', () => { 7 | it('Cadastra usuário 1', async () => { 8 | const res1 = await testServer 9 | .post('/cadastrar') 10 | .send({ 11 | senha: '123456', 12 | nome: 'Juca da Silva', 13 | email: 'jucasilva@gmail.com', 14 | }); 15 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 16 | expect(typeof res1.body).toEqual('number'); 17 | }); 18 | it('Cadastra usuário 2', async () => { 19 | const res1 = await testServer 20 | .post('/cadastrar') 21 | .send({ 22 | senha: '123456', 23 | nome: 'Pedro da Rosa', 24 | email: 'pedro@gmail.com', 25 | }); 26 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 27 | expect(typeof res1.body).toEqual('number'); 28 | }); 29 | it('Erro ao cadastrar um usuário com email duplicado', async () => { 30 | const res1 = await testServer 31 | .post('/cadastrar') 32 | .send({ 33 | senha: '123456', 34 | nome: 'Pedro da Rosa', 35 | email: 'pedroduplicado@gmail.com', 36 | }); 37 | expect(res1.statusCode).toEqual(StatusCodes.CREATED); 38 | expect(typeof res1.body).toEqual('number'); 39 | 40 | const res2 = await testServer 41 | .post('/cadastrar') 42 | .send({ 43 | senha: '123456', 44 | nome: 'Juca da Silva', 45 | email: 'pedroduplicado@gmail.com', 46 | }); 47 | expect(res2.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); 48 | expect(res2.body).toHaveProperty('errors.default'); 49 | }); 50 | it('Erro ao cadastrar um usuário sem email', async () => { 51 | const res1 = await testServer 52 | .post('/cadastrar') 53 | .send({ 54 | senha: '123456', 55 | nome: 'Juca da Silva', 56 | // email: 'jucasilva@gmail.com', 57 | }); 58 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 59 | expect(res1.body).toHaveProperty('errors.body.email'); 60 | }); 61 | it('Erro ao cadastrar um usuário sem nome', async () => { 62 | const res1 = await testServer 63 | .post('/cadastrar') 64 | .send({ 65 | senha: '123456', 66 | // nome: 'Juca da Silva', 67 | email: 'jucasilva@gmail.com', 68 | }); 69 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 70 | expect(res1.body).toHaveProperty('errors.body.nome'); 71 | }); 72 | it('Erro ao cadastrar um usuário sem senha', async () => { 73 | const res1 = await testServer 74 | .post('/cadastrar') 75 | .send({ 76 | // senha: '123456', 77 | nome: 'Juca da Silva', 78 | email: 'jucasilva@gmail.com', 79 | }); 80 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 81 | expect(res1.body).toHaveProperty('errors.body.senha'); 82 | }); 83 | it('Erro ao cadastrar um usuário com email inválido', async () => { 84 | const res1 = await testServer 85 | .post('/cadastrar') 86 | .send({ 87 | senha: '123456', 88 | nome: 'Juca da Silva', 89 | email: 'jucasilva gmail.com', 90 | }); 91 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 92 | expect(res1.body).toHaveProperty('errors.body.email'); 93 | }); 94 | it('Erro ao cadastrar um usuário com senha muito pequena', async () => { 95 | const res1 = await testServer 96 | .post('/cadastrar') 97 | .send({ 98 | senha: '123', 99 | nome: 'Juca da Silva', 100 | email: 'jucasilva@gmail.com', 101 | }); 102 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 103 | expect(res1.body).toHaveProperty('errors.body.senha'); 104 | }); 105 | it('Erro ao cadastrar um usuário com nome muito pequeno', async () => { 106 | const res1 = await testServer 107 | .post('/cadastrar') 108 | .send({ 109 | senha: '123456', 110 | nome: 'Ju', 111 | email: 'jucasilva@gmail.com', 112 | }); 113 | expect(res1.statusCode).toEqual(StatusCodes.BAD_REQUEST); 114 | expect(res1.body).toHaveProperty('errors.body.nome'); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./build", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "exclude": [ 104 | "./jest.config.ts", 105 | "./node_modules", 106 | "./tests", 107 | "./build" 108 | ] 109 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{"source":"/(.*)","destination":"/"}] 3 | } --------------------------------------------------------------------------------