├── .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 | }
--------------------------------------------------------------------------------