From 76f034b423b8db6b4bc733006cd4f4e4ae9c4e57 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Wed, 4 Aug 2021 18:51:39 -0300 Subject: [PATCH 001/129] feat: :sparkles: creating branch develop --- backend/.gitkeep | 1 - 1 file changed, 1 deletion(-) delete mode 100644 backend/.gitkeep diff --git a/backend/.gitkeep b/backend/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/backend/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - From c4a938f9e0fdcd37844f431dc6b65972f57426ec Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Wed, 4 Aug 2021 18:55:23 -0300 Subject: [PATCH 002/129] feat: :tada: starting backend --- backend/package.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/package.json diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..a04a29e --- /dev/null +++ b/backend/package.json @@ -0,0 +1,6 @@ +{ + "name": "backend", + "version": "1.0.0", + "main": "index.js", + "license": "MIT" +} From 181c55a106a80290949ff9e1424e6df94597573e Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:32:36 -0300 Subject: [PATCH 003/129] style: :lipstick: ESLint configuration ESLint configuration to improve code standardization --- backend/.eslintignore | 3 ++ backend/.eslintrc.json | 87 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 backend/.eslintignore create mode 100644 backend/.eslintrc.json diff --git a/backend/.eslintignore b/backend/.eslintignore new file mode 100644 index 0000000..cf6bd3c --- /dev/null +++ b/backend/.eslintignore @@ -0,0 +1,3 @@ +/*.js +node_modules +dist \ No newline at end of file diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json new file mode 100644 index 0000000..f3a79ff --- /dev/null +++ b/backend/.eslintrc.json @@ -0,0 +1,87 @@ +{ + "env": { + "es2021": true, + "node": true, + "jest": true + }, + "extends": [ + "airbnb-base", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "eslint-plugin-import-helpers", + "prettier" + ], + "rules": { + "camelcase": "off", + "import/no-unresolved": "error", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": [ + "PascalCase" + ], + "custom": { + "regex": "^I[A-Z]", + "match": true + } + } + ], + "class-methods-use-this": "off", + "import/prefer-default-export": "off", + "no-shadow": "off", + "no-console": "off", + "no-useless-constructor": "off", + "no-empty-function": "off", + "lines-between-class-members": "off", + "import/extensions": [ + "error", + "ignorePackages", + { + "ts": "never" + } + ], + "import-helpers/order-imports": [ + "warn", + { + "newlinesBetween": "always", + "groups": [ + "module", + "/^@/", + [ + "parent", + "sibling", + "index" + ] + ], + "alphabetize": { + "order": "asc", + "ignoreCase": true + } + } + ], + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "**/*.spec.js" + ] + } + ], + "prettier/prettier": "error" + }, + "settings": { + "import/resolver": { + "typescript": {} + } + } +} \ No newline at end of file From 152302824d8c04af9d3bb646315157c5f00aa497 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:33:40 -0300 Subject: [PATCH 004/129] style: :lipstick: prettier configuration Prettier configuration to improve code standardization --- backend/prettier.config.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backend/prettier.config.js diff --git a/backend/prettier.config.js b/backend/prettier.config.js new file mode 100644 index 0000000..7aedfd8 --- /dev/null +++ b/backend/prettier.config.js @@ -0,0 +1,3 @@ +module.exports = { + singleQuote: false, +}; \ No newline at end of file From e2e1032379fce011571ef4b6535b4c61571aadee Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:35:00 -0300 Subject: [PATCH 005/129] ci: :construction_worker: adding dockerfile and docker-compose --- backend/.dockerignore | 3 +++ backend/Dockerfile | 13 +++++++++++++ backend/docker-compose.yml | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 backend/docker-compose.yml diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..90b9f34 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +.vscode \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..361ab47 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,13 @@ +FROM node + +WORKDIR /usr/app + +COPY package.json ./ + +RUN npm install + +COPY . . + +EXPOSE 3333 + +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..ee16c8d --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.7" + +services: + database: + image: postgres + container_name: database_rentx + restart: always + ports: + - 5432:5432 + environment: + - POSTGRES_USER=docker + - POSTGRES_PASSWORD=ignite + - POSTGRES_DB=rentx + volumes: + - pgdata:/data/postgres + networks: + - rentx + + app: + build: . + container_name: rentx + restart: always + ports: + - 3333:3333 + - 9229:9229 + volumes: + - .:/usr/app + networks: + - rentx + +volumes: + pgdata: + driver: local + +networks: + rentx: + name: rentx-network From 16d4fead907e8b0bcb427a8e3945493f7cd16f88 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:36:37 -0300 Subject: [PATCH 006/129] feat: :tada: installing necessary libs --- backend/.gitignore | 7 +++++++ backend/package.json | 47 ++++++++++++++++++++++++++++++++++++++++++- backend/tsconfig.json | 32 +++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 backend/.gitignore create mode 100644 backend/tsconfig.json diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..b466216 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,7 @@ +dist +node_modules +yarn.lock +.env +ormconfig.json +.vscode +coverage \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index a04a29e..9a0d513 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,5 +2,50 @@ "name": "backend", "version": "1.0.0", "main": "index.js", - "license": "MIT" + "license": "MIT", + "scripts": { + "start:dev": "ts-node-dev -r tsconfig-paths/register --inspect --transpile-only --ignore-watch node_modules --respawn src/shared/infra/http/server.ts", + "typeorm": "ts-node-dev -r tsconfig-paths/register ./node_modules/typeorm/cli", + "test": "NODE_ENV=test jest --runInBand --detectOpenHandles", + "seed:admin": "ts-node-dev src/shared/infra/typeorm/seed/admin.ts" + }, + "dependencies": { + "bcrypt": "^5.0.1", + "csv-parse": "^4.15.3", + "dayjs": "^1.10.4", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "jest": "^26.6.3", + "jsonwebtoken": "^8.5.1", + "multer": "^1.4.2", + "pg": "^8.5.1", + "reflect-metadata": "^0.1.13", + "swagger-ui-express": "^4.1.6", + "tsyringe": "^4.5.0", + "typeorm": "^0.2.32", + "supertest": "^6.1.3", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/bcrypt": "^3.0.0", + "@types/express": "^4.17.11", + "@types/jest": "^26.0.22", + "@types/jsonwebtoken": "^8.5.1", + "@types/multer": "^1.4.5", + "@types/supertest": "^2.0.11", + "@types/swagger-ui-express": "^4.1.2", + "@types/uuid": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^4.19.0", + "eslint": "^7.22.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-prettier": "^8.1.0", + "eslint-import-resolver-typescript": "^2.4.0", + "eslint-plugin-import-helpers": "^1.1.0", + "eslint-plugin-prettier": "^3.3.1", + "prettier": "^2.2.1", + "ts-jest": "^26.5.4", + "ts-node-dev": "^1.1.6", + "tsconfig-paths": "^3.9.0", + "typescript": "^4.2.3" + } } diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..c36c788 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "rootDir": "./", + "baseUrl": "./src", + "outDir": "./dist", + "paths": { + "@modules/*": [ + "modules/*" + ], + "@config/*": [ + "config/*" + ], + "@shared/*": [ + "shared/*" + ], + "@errors/*": [ + "shared/errors/*" + ], + "@utils/*": [ + "utils/*" + ] + }, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + } +} \ No newline at end of file From bb849f99a13498a251f33eb77f063894fcfc3bc6 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:37:49 -0300 Subject: [PATCH 007/129] test: :white_check_mark: configuring jest file --- backend/jest.config.ts | 191 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 backend/jest.config.ts diff --git a/backend/jest.config.ts b/backend/jest.config.ts new file mode 100644 index 0000000..18a7442 --- /dev/null +++ b/backend/jest.config.ts @@ -0,0 +1,191 @@ +import { pathsToModuleNameMapper } from "ts-jest/utils"; +import { compilerOptions } from "./tsconfig.json"; + +export default { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + bail: true, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/tmp/jest_rs", + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + collectCoverageFrom: [ + '/src/modules/**/useCases/**/*.ts' + ], + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + coverageReporters: [ + "text-summary", + "lcov", + ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "json", + // "jsx", + // "ts", + // "tsx", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/src/" }), + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + preset: "ts-jest", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/*.spec.ts" + ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jasmine2", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; \ No newline at end of file From 124088715dbfa568021d4cca08108ea0004af4f0 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:40:46 -0300 Subject: [PATCH 008/129] docs: :memo: adding swagger adding swagger to the api route documentation --- backend/src/swagger.json | 185 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 backend/src/swagger.json diff --git a/backend/src/swagger.json b/backend/src/swagger.json new file mode 100644 index 0000000..ee8a7f7 --- /dev/null +++ b/backend/src/swagger.json @@ -0,0 +1,185 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Cert Im贸veis Documentation", + "description": "This is an API Cert Im贸veis", + "version": "1.0.0", + "contact": { + "name": "Josimar Junior", + "email": "josimarjr479@gmail.com" + } + }, + "paths": { + "/properties": { + "post": { + "tags": [ + "Properties" + ], + "summary": "Create a property", + "description": "Create new a property", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + "type": "number" + }, + "area": { + "type": "number" + }, + "address": { + "type": "string" + }, + "public_place": { + "type": "string" + }, + "house_number": { + "type": "number" + }, + "complement": { + "type": "string" + }, + "district": { + "type": "string" + }, + "cep": { + "type": "number" + }, + "city": { + "type": "string" + }, + "uf": { + "type": "string" + } + } + }, + "example": { + "title": "Im贸vel Martins", + "description": "Fazenda Martins", + "value": "1500", + "area": "600", + "address": "Rua fazenda Martins", + "public_place": "Rua Fazenda Martins", + "house_number": "5122", + "complement": "Fazenda", + "district": "Zona Leste", + "cep": "9999999", + "city": "Fazenda", + "uf": "FM" + } + } + } + }, + "responses": { + "201": { + "description": "Created" + }, + "500": { + "description": "Property already exist!" + } + } + }, + "get": { + "tags": [ + "Properties" + ], + "summary": "List all properties", + "description": "List all properties", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "default": "9kl23b4-23b3-l23n4-324kl3" + }, + "title": { + "type": "string", + "default": "Fazenda Martins" + }, + "description": { + "type": "string", + "default": "Fazenda Martins da zona leste" + }, + "value": { + "type": "number", + "default": 1500 + }, + "area": { + "type": "number", + "default": 616 + }, + "address": { + "type": "string", + "default": "Rua Fazenda Martins" + }, + "public_place": { + "type": "string", + "default": "Rua Fazenda Martins" + }, + "house_number": { + "type": "number", + "default": 943 + }, + "complement": { + "type": "string", + "default": "Fazenda" + }, + "district": { + "type": "string", + "default": "Zona Rural" + }, + "cep": { + "type": "number", + "default": 64664000 + }, + "city": { + "type": "string", + "default": "Martins" + }, + "uf": { + "type": "string", + "default": "FM" + }, + "created_at": { + "type": "string", + "default": "2021-08-05T00:00:00" + } + } + } + } + } + } + } + } + } + } + }, + "definitions": { + "Specification": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } +} \ No newline at end of file From 0c813ef7de1a55f4a0e50458d02c66532bdfedf0 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:41:53 -0300 Subject: [PATCH 009/129] feat: :sparkles: Adding index types renaming express type --- backend/src/@types/express/index.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 backend/src/@types/express/index.d.ts diff --git a/backend/src/@types/express/index.d.ts b/backend/src/@types/express/index.d.ts new file mode 100644 index 0000000..a47ccbd --- /dev/null +++ b/backend/src/@types/express/index.d.ts @@ -0,0 +1,7 @@ +declare namespace Express { + export interface Request { + user: { + id: string; + } + } +} \ No newline at end of file From 79988037ab19da85821f4bf11b9650ba56db6916 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:48:02 -0300 Subject: [PATCH 010/129] feat: :sparkles: creating a generic error class a class instantiated from the error, to perform all the application's error handling. --- backend/src/shared/errors/AppError.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 backend/src/shared/errors/AppError.ts diff --git a/backend/src/shared/errors/AppError.ts b/backend/src/shared/errors/AppError.ts new file mode 100644 index 0000000..dd9018e --- /dev/null +++ b/backend/src/shared/errors/AppError.ts @@ -0,0 +1,9 @@ +export class AppError { + public readonly message: string; + public readonly statusCode: number; + + constructor(message: string, statusCode = 400) { + this.message = message; + this.statusCode = statusCode; + } +} \ No newline at end of file From ea72bdf371821363ebeeddf5a7dbcf95acb7222d Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:50:23 -0300 Subject: [PATCH 011/129] feat: :sparkles: instantiating the entire app initiating database connection, routes and dependency injections --- backend/src/shared/infra/http/app.ts | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 backend/src/shared/infra/http/app.ts diff --git a/backend/src/shared/infra/http/app.ts b/backend/src/shared/infra/http/app.ts new file mode 100644 index 0000000..99dfca7 --- /dev/null +++ b/backend/src/shared/infra/http/app.ts @@ -0,0 +1,37 @@ +import 'reflect-metadata'; +import 'dotenv/config'; +import express, { NextFunction, Request, Response } from 'express'; +import 'express-async-errors'; +import swaggerUi from 'swagger-ui-express'; +import '@shared/container'; +import { AppError } from '@errors/AppError'; +import createConnection from '@shared/infra/typeorm'; +import { router } from './routes'; +import swaggerFile from '../../../swagger.json'; + +createConnection(); + +const app = express(); + +app.use(express.json()); + +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)); + +app.use(router) + +// errors +app.use((err: Error, request: Request, response: Response, next: NextFunction) => { + if (err instanceof AppError) { + return response.status(err.statusCode).json({ + statusCode: err.statusCode, + message: err.message + }); + } + + return response.status(500).json({ + status: "Error", + message: `Internal server error - ${err.message}` + }); +}); + +export { app } \ No newline at end of file From 8f1ff09b4ee7e8ae23db4d09e3e0fcea9ff66cfd Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:51:31 -0300 Subject: [PATCH 012/129] feat: :sparkles: creating the API service making the api listen on port 3333 --- backend/src/shared/infra/http/server.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 backend/src/shared/infra/http/server.ts diff --git a/backend/src/shared/infra/http/server.ts b/backend/src/shared/infra/http/server.ts new file mode 100644 index 0000000..144c152 --- /dev/null +++ b/backend/src/shared/infra/http/server.ts @@ -0,0 +1,5 @@ +import { app } from './app'; + +app.listen(process.env.APP_PORT || 3333, () => { + console.log(`Listening in port ${process.env.APP_PORT || 3333} 馃殌`); +}); \ No newline at end of file From 264069facccb821894bb7b3d9486c53e24308dd2 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:53:13 -0300 Subject: [PATCH 013/129] feat: :sparkles: creating a database instance starting database with your credentials --- backend/src/shared/infra/typeorm/index.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 backend/src/shared/infra/typeorm/index.ts diff --git a/backend/src/shared/infra/typeorm/index.ts b/backend/src/shared/infra/typeorm/index.ts new file mode 100644 index 0000000..9b633d1 --- /dev/null +++ b/backend/src/shared/infra/typeorm/index.ts @@ -0,0 +1,12 @@ +import { Connection, createConnection, getConnectionOptions } from 'typeorm'; + +export default async (host = 'database'): Promise => { + const defaultOptions = await getConnectionOptions(); + + return createConnection( + Object.assign(defaultOptions, { + host: process.env.NODE_ENV === 'test' ? 'localhost' : host, + database: process.env.NODE_ENV === 'test' ? 'cert_imoveis_test' : defaultOptions.database, + }) + ); +} \ No newline at end of file From 697caf17fd8b65bba8879c492c09064b258e1320 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:54:59 -0300 Subject: [PATCH 014/129] feat: :sparkles: Creating a test bank access Loading a test instance to perform integration tests on a fake database --- backend/src/shared/infra/typeorm/seed/admin.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/src/shared/infra/typeorm/seed/admin.ts diff --git a/backend/src/shared/infra/typeorm/seed/admin.ts b/backend/src/shared/infra/typeorm/seed/admin.ts new file mode 100644 index 0000000..9558477 --- /dev/null +++ b/backend/src/shared/infra/typeorm/seed/admin.ts @@ -0,0 +1,17 @@ +import { v4 as uuidV4 } from 'uuid'; +import { hash } from 'bcrypt'; + +import createConnection from '../index'; + +async function create() { + const connection = await createConnection('localhost'); + const id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + connection.close; +} + +create().then(() => console.log('User admin created!')); \ No newline at end of file From 475c082ec565fc031189c9da40a61057618f76f8 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 20:58:56 -0300 Subject: [PATCH 015/129] feat: :sparkles: creating migration to user entity creating the migration with the necessary fields to be able to login --- .../migrations/1628121901758-CreateUser.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 backend/src/shared/infra/typeorm/migrations/1628121901758-CreateUser.ts diff --git a/backend/src/shared/infra/typeorm/migrations/1628121901758-CreateUser.ts b/backend/src/shared/infra/typeorm/migrations/1628121901758-CreateUser.ts new file mode 100644 index 0000000..1571cc3 --- /dev/null +++ b/backend/src/shared/infra/typeorm/migrations/1628121901758-CreateUser.ts @@ -0,0 +1,42 @@ +import { MigrationInterface, QueryRunner, Table } from "typeorm"; + +export default class CreateUser1628121901758 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "users", + columns: [ + { + name: "id", + type: "uuid", + isPrimary: true + }, + { + name: "name", + type: "varchar" + }, + { + name: "password", + type: "varchar" + }, + { + name: "email", + type: "varchar", + isUnique: true + }, + { + name: "created_at", + type: "timestamp", + default: "now()" + } + ] + }) + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("users"); + } + +} From 0febb888da544d282d712bcddfcb8de1cd706b11 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:00:44 -0300 Subject: [PATCH 016/129] feat: :sparkles: creating migration to property entity creating the migration with the necessary fields to be able to make the crud property --- .../1628122047487-CreateProperty.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 backend/src/shared/infra/typeorm/migrations/1628122047487-CreateProperty.ts diff --git a/backend/src/shared/infra/typeorm/migrations/1628122047487-CreateProperty.ts b/backend/src/shared/infra/typeorm/migrations/1628122047487-CreateProperty.ts new file mode 100644 index 0000000..68743d3 --- /dev/null +++ b/backend/src/shared/infra/typeorm/migrations/1628122047487-CreateProperty.ts @@ -0,0 +1,77 @@ +import {MigrationInterface, QueryRunner, Table} from "typeorm"; + +export default class CreateProperty1628122047487 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "properties", + columns: [ + { + name: "id", + type: "uuid", + isPrimary: true + }, + { + name: "title", + type: "varchar" + }, + { + name: "description", + type: "varchar" + }, + { + name: "value", + type: "integer" + }, + { + name: "area", + type: "integer" + }, + { + name: "address", + type: "varchar" + }, + { + name: "public_place", + type: "varchar" + }, + { + name: "house_number", + type: "integer" + }, + { + name: "complement", + type: "varchar" + }, + { + name: "district", + type: "varchar" + }, + { + name: "cep", + type: "integer" + }, + { + name: "city", + type: "varchar" + }, + { + name: "uf", + type: "varchar" + }, + { + name: "created_at", + type: "timestamp", + default: "now()" + } + ] + }) + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("properties"); + } + +} From c3a0e48ad730de608aeea7b0046f9dabc0e1a251 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:03:51 -0300 Subject: [PATCH 017/129] feat: :sparkles: instantiating routes creating route instantiation file --- backend/src/shared/infra/http/routes/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 backend/src/shared/infra/http/routes/index.ts diff --git a/backend/src/shared/infra/http/routes/index.ts b/backend/src/shared/infra/http/routes/index.ts new file mode 100644 index 0000000..be7c16f --- /dev/null +++ b/backend/src/shared/infra/http/routes/index.ts @@ -0,0 +1,11 @@ +import { Router } from 'express'; +import { usersRoutes } from '@modules/accounts/infra/http/routes/users.routes'; +import { authenticateRouter } from '@modules/accounts/infra/http/routes/authenticate.routes'; +import { propertiesRoutes } from '@modules/properties/infra/http/routes/properties.routes'; +const router = Router(); + +router.use('/users', usersRoutes); +router.use('/sessions', authenticateRouter); +router.use('/properties', propertiesRoutes); + +export { router } \ No newline at end of file From 395554eb78d9f6fd4a348b3bb6a92c1e07d8daef Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:05:44 -0300 Subject: [PATCH 018/129] feat: :lock: creating function for route validation validate if the user placed in the token exists --- .../http/middlewares/ensureAuthenticated.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 backend/src/shared/infra/http/middlewares/ensureAuthenticated.ts diff --git a/backend/src/shared/infra/http/middlewares/ensureAuthenticated.ts b/backend/src/shared/infra/http/middlewares/ensureAuthenticated.ts new file mode 100644 index 0000000..b116032 --- /dev/null +++ b/backend/src/shared/infra/http/middlewares/ensureAuthenticated.ts @@ -0,0 +1,38 @@ +import { Request, Response, NextFunction } from 'express'; +import { verify } from 'jsonwebtoken'; +import { UsersRepository } from '@modules/accounts/infra/typeorm/repositories/UsersRepository'; +import { AppError } from '@errors/AppError'; + +interface IPayload { + sub: string; +} + +export async function ensureAuthenticated(request: Request, response: Response, next: NextFunction) { + const authHeader = request.headers.authorization; + + if (!authHeader) { + throw new AppError('Token missing'); + } + + const [, token] = authHeader.split(' '); + + try { + const { sub: user_id } = verify(token, process.env.APP_JWT_SECRET || '') as IPayload; + + const usersRepository = new UsersRepository(); + + const user = await usersRepository.findByID(user_id); + + if (!user) { + throw new AppError('User does not exists'); + } + + request.user = { + id: user_id + } + + next(); + } catch { + throw new AppError('Invalid token!'); + } +} \ No newline at end of file From cc807226ed27cde0182012c142ec888dc325daa2 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:09:46 -0300 Subject: [PATCH 019/129] feat: :sparkles: creating property entity adding additional fields that were in the document readme --- .../accounts/infra/typeorm/entities/User.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/src/modules/accounts/infra/typeorm/entities/User.ts diff --git a/backend/src/modules/accounts/infra/typeorm/entities/User.ts b/backend/src/modules/accounts/infra/typeorm/entities/User.ts new file mode 100644 index 0000000..21dce38 --- /dev/null +++ b/backend/src/modules/accounts/infra/typeorm/entities/User.ts @@ -0,0 +1,28 @@ +import { Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; + +@Entity('users') +class User { + @PrimaryColumn('uuid') + id: string; + + @Column() + name: string; + + @Column({ unique: true }) + email: string; + + @Column() + password: string; + + @CreateDateColumn() + created_at: Date; + + constructor() { + if (!this.id) { + this.id = uuidv4(); + } + } +} + +export { User } \ No newline at end of file From 2ad3d5096458d76e82b42e89c86c072938bde89e Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:14:27 -0300 Subject: [PATCH 020/129] feat: :sparkles: creating a contract to define the repository methods creating a contract to define which methods the repository will have to be mandatory, following a SOLID principle which is O open for implementations and closed for modifications --- .../repositories/IPropertiesRepository.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 backend/src/modules/properties/repositories/IPropertiesRepository.ts diff --git a/backend/src/modules/properties/repositories/IPropertiesRepository.ts b/backend/src/modules/properties/repositories/IPropertiesRepository.ts new file mode 100644 index 0000000..10e5dae --- /dev/null +++ b/backend/src/modules/properties/repositories/IPropertiesRepository.ts @@ -0,0 +1,13 @@ +import { ICreatePropertyDTO } from "../dtos/ICreatePropertyDTO"; +import { Property } from "../infra/typeorm/entities/Property"; + +interface IPropertiesRepository { + create(data: ICreatePropertyDTO): Promise; + findById(id: string): Promise; + findByTitle(title: string): Promise; + find(): Promise; + update(property: Property): Promise; + delete(id: string): Promise; +} + +export { IPropertiesRepository } \ No newline at end of file From 0b9477cb482cd7a0c72f9d36b3a15c53ded4d27d Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:16:24 -0300 Subject: [PATCH 021/129] feat: :sparkles: creating fake database creating fake database to be able to perform unit tests --- .../fakes/FakesPropertiesRepository.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts diff --git a/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts b/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts new file mode 100644 index 0000000..9ceccf5 --- /dev/null +++ b/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts @@ -0,0 +1,83 @@ +import { ICreatePropertyDTO } from "../../dtos/ICreatePropertyDTO"; +import { Property } from "../../infra/typeorm/entities/Property"; +import { IPropertiesRepository } from '../IPropertiesRepository'; + +class FakePropertiesRepository implements IPropertiesRepository { + private properties: Property[] = []; + + public async create({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }: ICreatePropertyDTO): Promise { + const property = new Property(); + + Object.assign(property, { + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + created_at: new Date() + }); + + this.properties.push(property); + + return property; + } + + public async findById(id: string): Promise { + return this.properties.find((model) => model.id === id); + } + + public async findByTitle(title: string): Promise { + return this.properties.find((model) => model.title === title); + } + + public async update( + data: Property + ): Promise { + const property = new Property(); + const propertyIndex = this.properties.findIndex( + p => p.id === data.id + ); + + Object.assign(property, { + data + }); + + this.properties[propertyIndex] = property; + return property; + } + + public async find(): Promise { + return this.properties; + } + + public async delete(id: string): Promise { + const properties = this.properties.filter( + p => p.id !== id + ); + + this.properties = properties; + return true; + } +} + +export { FakePropertiesRepository } \ No newline at end of file From 18847fae9ba2081726c69487fa44b7b48d584bb0 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:19:11 -0300 Subject: [PATCH 022/129] feat: :sparkles: creating a DTO for validation of date creating a data transfer object to be able to evaluate the data passed to the repository --- .../properties/dtos/ICreatePropertyDTO.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/src/modules/properties/dtos/ICreatePropertyDTO.ts diff --git a/backend/src/modules/properties/dtos/ICreatePropertyDTO.ts b/backend/src/modules/properties/dtos/ICreatePropertyDTO.ts new file mode 100644 index 0000000..2c8d9d8 --- /dev/null +++ b/backend/src/modules/properties/dtos/ICreatePropertyDTO.ts @@ -0,0 +1,17 @@ +interface ICreatePropertyDTO { + id?: string; + title: string; + description: string; + value: number; + area: number; + address: string; + public_place: string; + house_number: number; + complement: string; + district: string; + cep: number; + city: string; + uf: string; +} + +export { ICreatePropertyDTO } \ No newline at end of file From 6f9047889258761e16c92974c29c9036ba74c624 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:20:38 -0300 Subject: [PATCH 023/129] feat: :sparkles: creating property entity adding fields of readme --- .../infra/typeorm/entities/Property.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 backend/src/modules/properties/infra/typeorm/entities/Property.ts diff --git a/backend/src/modules/properties/infra/typeorm/entities/Property.ts b/backend/src/modules/properties/infra/typeorm/entities/Property.ts new file mode 100644 index 0000000..8bbfbc8 --- /dev/null +++ b/backend/src/modules/properties/infra/typeorm/entities/Property.ts @@ -0,0 +1,55 @@ +import { Column, CreateDateColumn, Double, Entity, PrimaryColumn } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; + +@Entity('properties') +class Property { + @PrimaryColumn('uuid') + id: string; + + @Column() + title: string; + + @Column() + description: string; + + @Column() + value: number; + + @Column() + area: number; + + @Column() + address: string; + + @Column() + public_place: string; + + @Column() + house_number: number; + + @Column() + complement: string; + + @Column() + district: string; + + @Column() + cep: number; + + @Column() + city: string; + + @Column() + uf: string; + + @CreateDateColumn() + created_at: Date; + + constructor() { + if (!this.id) { + this.id = uuidv4(); + } + } +} + +export { Property } \ No newline at end of file From 0723f2666021b173945950cdfb6bd245f2e47f9a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:22:37 -0300 Subject: [PATCH 024/129] feat: :sparkles: creating repository typeorm creating repository implementing the contract --- .../repositories/PropertiesRepository.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts diff --git a/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts b/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts new file mode 100644 index 0000000..c898784 --- /dev/null +++ b/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts @@ -0,0 +1,69 @@ +import { ICreatePropertyDTO } from "@modules/properties/dtos/ICreatePropertyDTO"; +import { IPropertiesRepository } from "@modules/properties/repositories/IPropertiesRepository"; +import { getRepository, Repository } from "typeorm"; +import { Property } from "../entities/Property"; + +class PropertiesRepository implements IPropertiesRepository { + private ormRepository: Repository + + constructor() { + this.ormRepository = getRepository(Property); + } + + public async create({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }: ICreatePropertyDTO): Promise { + const property = this.ormRepository.create({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }); + await this.ormRepository.save(property); + return property; + } + + public async findById(id: string): Promise { + return await this.ormRepository.findOne({ id }); + } + + public async findByTitle(title: string): Promise { + return await this.ormRepository.findOne({ title }); + } + + public async update( + property: Property + ): Promise { + await this.ormRepository.save(property); + return property; + } + + public async find(): Promise { + return this.ormRepository.find(); + } + public async delete(id: string): Promise { + const deleted = await this.ormRepository.delete(id); + return !!deleted; + } +} + +export { PropertiesRepository } \ No newline at end of file From 615d8fd9500f77019fbe8350c8c0efe0f24761df Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:29:27 -0300 Subject: [PATCH 025/129] test: :white_check_mark: creating controller integration test receiving body from the requisition to register the property --- .../createPropertyController.spec.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts new file mode 100644 index 0000000..18892b6 --- /dev/null +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts @@ -0,0 +1,91 @@ +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { hash } from 'bcrypt'; +import { v4 as uuidV4 } from 'uuid'; + +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; + +let connection: Connection; + +describe('Create Property Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + const id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to create a new property', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .post('/properties') + .send({ + title: 'Title Supertest', + description: 'Description Supertest', + value: 16156, + area: 3464, + address: 'Address Supertest', + public_place: 'Public Place Supertest', + house_number: 6161, + complement: 'Fazenda', + district: 'Supertest', + cep: 8461036, + city: 'Supertest', + uf: 'SU' + }).set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(201); + }); + + it('should not be able to create a new property with title exists', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .post('/properties') + .send({ + title: 'Title Supertest', + description: 'Description Supertest', + value: 16156, + area: 3464, + address: 'Address Supertest', + public_place: 'Public Place Supertest', + house_number: 6161, + complement: 'Fazenda', + district: 'Supertest', + cep: 8461036, + city: 'Supertest', + uf: 'SU' + }).set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(400); + }); +}); \ No newline at end of file From 60c43944805c2b1cbc0719d5ca2f3b859b15fcf2 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:30:17 -0300 Subject: [PATCH 026/129] feat: :sparkles: creating controller receiving body from the requisition to register the property --- .../createPropertyController.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backend/src/modules/properties/useCases/createProperty/createPropertyController.ts diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts new file mode 100644 index 0000000..bb157fb --- /dev/null +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts @@ -0,0 +1,41 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { CreatePropertyUseCase } from './createPropertyUseCase'; + +class CreatePropertyController { + async handle(request: Request, response: Response): Promise { + const { title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf } = request.body; + + const createPropertyUseCase = container.resolve(CreatePropertyUseCase); + + const property = await createPropertyUseCase.execute({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf + }); + + return response.status(201).json(property); + } +} + +export { CreatePropertyController } \ No newline at end of file From 1254676e88cfde6fd0fd762e823b0438cca2d600 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:31:27 -0300 Subject: [PATCH 027/129] test: :white_check_mark: create use case unit test for property registration --- .../createPropertyUseCase.spec.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts new file mode 100644 index 0000000..99db7a5 --- /dev/null +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts @@ -0,0 +1,64 @@ +import { AppError } from "@shared/errors/AppError"; +import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; +import { CreatePropertyUseCase } from "./createPropertyUseCase"; + +let fakePropertiesRepository: FakePropertiesRepository; +let createPropertyUseCase: CreatePropertyUseCase; + +describe('Create Propert', () => { + beforeEach(() => { + fakePropertiesRepository = new FakePropertiesRepository(); + createPropertyUseCase = new CreatePropertyUseCase(fakePropertiesRepository); + }); + + it('should be able to create new user', async () => { + const property = await createPropertyUseCase.execute({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + expect(property).toHaveProperty('id'); + }); + + it('should not be able to create new user, because email already exists', async () => { + await fakePropertiesRepository.create({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + await expect(createPropertyUseCase.execute({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + })).rejects.toEqual(new AppError('Property already exist!')); + }); +}); \ No newline at end of file From 60da9b21bab0e49b71084598792f6bafa95d64b5 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:31:57 -0300 Subject: [PATCH 028/129] feat: :sparkles: creating use case for property registration --- .../createProperty/createPropertyUseCase.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts new file mode 100644 index 0000000..1226d7f --- /dev/null +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts @@ -0,0 +1,53 @@ +import { inject, injectable } from 'tsyringe'; +import { ICreatePropertyDTO } from '@modules/properties/dtos/ICreatePropertyDTO'; +import { Property } from '@modules/properties/infra/typeorm/entities/Property'; +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; +import { AppError } from '@errors/AppError'; + +@injectable() +class CreatePropertyUseCase { + constructor( + @inject('PropertiesRepository') + private propertiesRepository: IPropertiesRepository, + ) { } + + async execute({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }: ICreatePropertyDTO): Promise { + const propertyAlreadyExist = await this.propertiesRepository.findByTitle(title); + + if (propertyAlreadyExist) { + throw new AppError('Property already exist!'); + } + + const property = await this.propertiesRepository.create({ + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }); + + return property; + } +} + +export { CreatePropertyUseCase } \ No newline at end of file From 85bb6722decad1acbc2415a166ed804a81ccb16a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:35:25 -0300 Subject: [PATCH 029/129] =?UTF-8?q?test:=20:white=5Fcheck=5Fmark:=20criand?= =?UTF-8?q?o=20teste=20de=20integra=C3=A7=C3=A3o=20de=20controlador=20for?= =?UTF-8?q?=20remove=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validating if title already exists --- .../deletePropertyByIdController.spec.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.spec.ts diff --git a/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.spec.ts b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.spec.ts new file mode 100644 index 0000000..dc2f86d --- /dev/null +++ b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.spec.ts @@ -0,0 +1,74 @@ +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { hash } from 'bcrypt'; +import { v4 as uuidV4 } from 'uuid'; + +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; + +let connection: Connection; +let id: string; + +describe('Delete Property By Id Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + + await connection.query(` + INSERT INTO properties(id, title, description, value, area, address, public_place, + house_number, complement, district, cep, city, uf, created_at) + VALUES('${id}', 'Fazenda Martins', 'Fazenda Martins da zona leste', + ${1651}, ${16131}, 'Test', 'Test', ${46163}, 'Test', 'Test', ${656161}, + 'fwefewf', 'rn', 'now()'); + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to remove a property by id', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .delete(`/properties/${id}`) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(200); + }); + + it('should not be able to remove property by id, because id non-exists', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .post(`/properties/non-id`) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(404); + }); +}); \ No newline at end of file From 711c1d3958a4b5f10de2ef52307fef178d4f54a1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:37:40 -0300 Subject: [PATCH 030/129] feat: :sparkles: creating controller for remove property by id validating if id already exists --- .../deletePropertyByIdController.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.ts diff --git a/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.ts b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.ts new file mode 100644 index 0000000..fdd2b04 --- /dev/null +++ b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdController.ts @@ -0,0 +1,17 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { DeletePropertyByIdUseCase } from './deletePropertyByIdUseCase'; + +class DeletePropertyByIdController { + async handle(request: Request, response: Response): Promise { + const { id } = request.params; + + const deletePropertyByIdUseCase = container.resolve(DeletePropertyByIdUseCase); + + const propertyDeleted = await deletePropertyByIdUseCase.execute(id); + + return response.status(200).json(propertyDeleted); + } +} + +export { DeletePropertyByIdController } \ No newline at end of file From 0e736815a830f1e39c80c1e91ca5bc6b57900671 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:38:23 -0300 Subject: [PATCH 031/129] test: :white_check_mark: create use case unit test for remove property --- .../deletePropertyByIdUseCase.spec.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.spec.ts diff --git a/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.spec.ts b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.spec.ts new file mode 100644 index 0000000..f5f94f0 --- /dev/null +++ b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.spec.ts @@ -0,0 +1,40 @@ +import { AppError } from "@shared/errors/AppError"; +import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; +import { DeletePropertyByIdUseCase } from "./deletePropertyByIdUseCase"; + +let fakePropertiesRepository: FakePropertiesRepository; +let deletePropertyByIdUseCase: DeletePropertyByIdUseCase; + +describe('Delete Property By Id', () => { + beforeEach(() => { + fakePropertiesRepository = new FakePropertiesRepository(); + deletePropertyByIdUseCase = new DeletePropertyByIdUseCase(fakePropertiesRepository); + }); + + it('should be able to remove property by id', async () => { + const { id } = await fakePropertiesRepository.create({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + const property = await deletePropertyByIdUseCase.execute(id); + + expect(property).toEqual(true); + }); + + it('should not be able to remove property by id, because id non-exists', async () => { + await expect( + deletePropertyByIdUseCase.execute('non-id') + ).rejects.toEqual(new AppError('Property not exist!', 404)); + }); +}); \ No newline at end of file From c4740f5f3c35dee8c7af69e2ef7a5d6dbddf65ca Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:38:53 -0300 Subject: [PATCH 032/129] feat: :sparkles: creating use case for remove property --- .../deletePropertyByIdUseCase.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.ts diff --git a/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.ts b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.ts new file mode 100644 index 0000000..b0dfe3c --- /dev/null +++ b/backend/src/modules/properties/useCases/deletePropertyById/deletePropertyByIdUseCase.ts @@ -0,0 +1,25 @@ +import { inject, injectable } from 'tsyringe'; +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; +import { AppError } from '@errors/AppError'; + +@injectable() +class DeletePropertyByIdUseCase { + constructor( + @inject('PropertiesRepository') + private propertiesRepository: IPropertiesRepository, + ) { } + + async execute(id: string): Promise { + const propertyExists = await this.propertiesRepository.findById(id); + + if (!propertyExists) { + throw new AppError('Property not exist!', 404); + } + + const propertyDeleted = await this.propertiesRepository.delete(id); + + return propertyDeleted; + } +} + +export { DeletePropertyByIdUseCase } \ No newline at end of file From 346b80057c8044fada4f9ee310b1e35ce17a0542 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:41:58 -0300 Subject: [PATCH 033/129] test: :white_check_mark: creating controller integration test for list properties --- .../listPropertiesController.spec.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts new file mode 100644 index 0000000..87b83a2 --- /dev/null +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts @@ -0,0 +1,55 @@ +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; +import { hash } from 'bcrypt'; +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { v4 as uuidV4 } from 'uuid'; + +let connection: Connection; +let id: string; + +describe('List Properties Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + + await connection.query(` + INSERT INTO properties(id, title, description, value, area, address, public_place, + house_number, complement, district, cep, city, uf, created_at) + VALUES('${id}', 'Fazenda Martins', 'Fazenda Martins da zona leste', + ${1651}, ${16131}, 'Test', 'Test', ${46163}, 'Test', 'Test', ${656161}, + 'fwefewf', 'rn', 'now()'); + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to list the properties', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .get(`/properties`) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(200); + }); +}); \ No newline at end of file From a321240e21f16cc5858a4f2d3939ba4a3bf1a168 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:42:27 -0300 Subject: [PATCH 034/129] feat: :sparkles: creating controller for list of the properties --- .../listProperties/listPropertiesController.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts new file mode 100644 index 0000000..5706424 --- /dev/null +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts @@ -0,0 +1,15 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { ListPropertiesUseCase } from './listPropertiesUseCase'; + +class ListPropertyController { + async handle(request: Request, response: Response): Promise { + const listPropertiesUseCase = container.resolve(ListPropertiesUseCase); + + const properties = await listPropertiesUseCase.execute(); + + return response.status(200).json(properties); + } +} + +export { ListPropertyController } \ No newline at end of file From 19917a13924aaf97585dbd52a52ec3efc35ef3f0 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:43:04 -0300 Subject: [PATCH 035/129] test: :white_check_mark: create use case unit test for list of the properties --- .../listPropertiesUseCase.spec.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts new file mode 100644 index 0000000..57136a5 --- /dev/null +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts @@ -0,0 +1,33 @@ +import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; +import { ListPropertiesUseCase } from "./listPropertiesUseCase"; + +let fakePropertiesRepository: FakePropertiesRepository; +let listPropertiesUseCase: ListPropertiesUseCase; + +describe('List Properties', () => { + beforeEach(() => { + fakePropertiesRepository = new FakePropertiesRepository(); + listPropertiesUseCase = new ListPropertiesUseCase(fakePropertiesRepository); + }); + + it('should be able to list properties', async () => { + const property = await fakePropertiesRepository.create({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + const properties = await listPropertiesUseCase.execute(); + + expect(properties).toEqual([property]); + }); +}); \ No newline at end of file From ed887befaf8b9414b26a11135c35efc1eb291df1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:43:37 -0300 Subject: [PATCH 036/129] feat: :sparkles: creating use case for list of the properties --- .../listProperties/listPropertiesUseCase.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts new file mode 100644 index 0000000..a13c411 --- /dev/null +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts @@ -0,0 +1,19 @@ +import { inject, injectable } from 'tsyringe'; +import { Property } from '@modules/properties/infra/typeorm/entities/Property'; +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; + +@injectable() +class ListPropertiesUseCase { + constructor( + @inject('PropertiesRepository') + private propertiesRepository: IPropertiesRepository, + ) { } + + async execute(): Promise { + const properties = await this.propertiesRepository.find(); + + return properties; + } +} + +export { ListPropertiesUseCase } \ No newline at end of file From fdc24789ddd216efce71b12226133e94fd39fd1a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:45:50 -0300 Subject: [PATCH 037/129] test: :white_check_mark: creating controller integration test for show property by id getting the id of the routes parameters --- .../showPropertyByIdController.spec.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.spec.ts diff --git a/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.spec.ts b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.spec.ts new file mode 100644 index 0000000..7fec4f9 --- /dev/null +++ b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.spec.ts @@ -0,0 +1,54 @@ +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; +import { hash } from 'bcrypt'; +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { v4 as uuidV4 } from 'uuid'; + +let connection: Connection; +let id: string; + +describe('Show Property By Id Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + await connection.query(` + INSERT INTO properties(id, title, description, value, area, address, public_place, + house_number, complement, district, cep, city, uf, created_at) + VALUES('${id}', 'Fazenda Martins', 'Fazenda Martins da zona leste', + ${1651}, ${16131}, 'Test', 'Test', ${46163}, 'Test', 'Test', ${656161}, + 'fwefewf', 'rn', 'now()'); + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to show the property', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .get(`/properties/${id}`) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(200); + }); +}); \ No newline at end of file From 037b551eca802766c804e4071a9a336e7e22dc36 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:46:36 -0300 Subject: [PATCH 038/129] feat: :sparkles: creating controller for show property by id getting the id of the routes parameters --- .../showPropertyByIdController.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.ts diff --git a/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.ts b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.ts new file mode 100644 index 0000000..d0c01fe --- /dev/null +++ b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdController.ts @@ -0,0 +1,17 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { ShowPropertyByIdUseCase } from './showPropertyByIdUseCase'; + +class ShowPropertyByIdController { + async handle(request: Request, response: Response): Promise { + const { id } = request.params; + + const showPropertyByIdUseCase = container.resolve(ShowPropertyByIdUseCase); + + const property = await showPropertyByIdUseCase.execute(id); + + return response.status(200).json(property); + } +} + +export { ShowPropertyByIdController } \ No newline at end of file From f0856a3a5dda1607d414a869f4618214ada54977 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:47:15 -0300 Subject: [PATCH 039/129] test: :white_check_mark: create use case unit test for show property by id --- .../showPropertyByIdUseCase.spec.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.spec.ts diff --git a/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.spec.ts b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.spec.ts new file mode 100644 index 0000000..bd10274 --- /dev/null +++ b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.spec.ts @@ -0,0 +1,34 @@ +import { AppError } from "@shared/errors/AppError"; +import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; +import { ShowPropertyByIdUseCase } from "./showPropertyByIdUseCase"; + +let fakePropertiesRepository: FakePropertiesRepository; +let showPropertyByIdUseCase: ShowPropertyByIdUseCase; + +describe('Show Property By Id', () => { + beforeEach(() => { + fakePropertiesRepository = new FakePropertiesRepository(); + showPropertyByIdUseCase = new ShowPropertyByIdUseCase(fakePropertiesRepository); + }); + + it('should be able to show property by id', async () => { + const propertyCreated = await fakePropertiesRepository.create({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + const property = await showPropertyByIdUseCase.execute(propertyCreated.id); + + expect(property).toEqual(propertyCreated); + }); +}); \ No newline at end of file From 4804b00158e46ea930348e23ba41c984e6b3ad0d Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 21:47:51 -0300 Subject: [PATCH 040/129] feat: :sparkles: creating use case for show property by id --- .../showPropertyByIdUseCase.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.ts diff --git a/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.ts b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.ts new file mode 100644 index 0000000..45b1c2c --- /dev/null +++ b/backend/src/modules/properties/useCases/showPropertyById/showPropertyByIdUseCase.ts @@ -0,0 +1,19 @@ +import { inject, injectable } from 'tsyringe'; +import { Property } from '@modules/properties/infra/typeorm/entities/Property'; +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; + +@injectable() +class ShowPropertyByIdUseCase { + constructor( + @inject('PropertiesRepository') + private propertiesRepository: IPropertiesRepository, + ) { } + + async execute(id: string): Promise { + const property = await this.propertiesRepository.findById(id); + + return property; + } +} + +export { ShowPropertyByIdUseCase } \ No newline at end of file From 26ad16e016bd10f82be43228121d141f1254b272 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:01:02 -0300 Subject: [PATCH 041/129] test: :white_check_mark: creating controller integration test for edit property getting body data of routes --- .../updatePropertyController.spec.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 backend/src/modules/properties/useCases/updateProperty/updatePropertyController.spec.ts diff --git a/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.spec.ts b/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.spec.ts new file mode 100644 index 0000000..f14cd61 --- /dev/null +++ b/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.spec.ts @@ -0,0 +1,87 @@ +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; +import { hash } from 'bcrypt'; +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { v4 as uuidV4 } from 'uuid'; + +let connection: Connection; +let id: string; + +describe('Update Property Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + + await connection.query(` + INSERT INTO properties(id, title, description, value, area, address, public_place, + house_number, complement, district, cep, city, uf, created_at) + VALUES('${id}', 'Fazenda Martins', 'Fazenda Martins da zona leste', + ${1651}, ${16131}, 'Test', 'Test', ${46163}, 'Test', 'Test', ${656161}, + 'fwefewf', 'rn', 'now()'); + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to update a property', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .put(`/properties/${id}`) + .send({ + title: 'Title Change Supertest', + description: 'Description Change Supertest', + value: 16156, + area: 3464, + address: 'Address Supertest', + public_place: 'Public Place Supertest', + house_number: 6161, + complement: 'Fazenda', + district: 'Supertest', + cep: 8461036, + city: 'Supertest', + uf: 'SU' + }) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(200); + }); + + it('should not be able to update property by id, because id non-exists', async () => { + const responseToken = await request(app).post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }); + + const { token } = responseToken.body; + + const response = await request(app) + .post(`/properties/non-id`) + .set({ + Authorization: `Bearer ${token}`, + }); + + expect(response.status).toBe(404); + }); +}); \ No newline at end of file From ee95e3af1b0dbdd0825a3898c7e72db6df4b8d3a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:01:47 -0300 Subject: [PATCH 042/129] feat: :sparkles: creating controller for edit property getting body data of routes --- .../updatePropertyController.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 backend/src/modules/properties/useCases/updateProperty/updatePropertyController.ts diff --git a/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.ts b/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.ts new file mode 100644 index 0000000..712c5d4 --- /dev/null +++ b/backend/src/modules/properties/useCases/updateProperty/updatePropertyController.ts @@ -0,0 +1,43 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { UpdatePropertyUseCase } from './updatePropertyUseCase'; + +class UpdatePropertyController { + async handle(request: Request, response: Response): Promise { + const { id } = request.params; + const { title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf } = request.body; + + const updatePropertyUseCase = container.resolve(UpdatePropertyUseCase); + + const property = await updatePropertyUseCase.execute({ + id, + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf + }); + + return response.status(200).json(property); + } +} + +export { UpdatePropertyController } \ No newline at end of file From 18aba8cd0a41f9f6d8c25ab42f95aa9de7890926 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:03:07 -0300 Subject: [PATCH 043/129] test: :white_check_mark: create use case unit test for edit property --- .../updatePropertyUseCase.spec.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.spec.ts diff --git a/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.spec.ts b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.spec.ts new file mode 100644 index 0000000..2a156e1 --- /dev/null +++ b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.spec.ts @@ -0,0 +1,66 @@ +import { AppError } from "@shared/errors/AppError"; +import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; +import { UpdatePropertyUseCase } from "./updatePropertyUseCase"; + +let fakePropertiesRepository: FakePropertiesRepository; +let updatePropertyUseCase: UpdatePropertyUseCase; + +describe('Update Property', () => { + beforeEach(() => { + fakePropertiesRepository = new FakePropertiesRepository(); + updatePropertyUseCase = new UpdatePropertyUseCase(fakePropertiesRepository); + }); + + it('should be able to edit property by id', async () => { + const { id } = await fakePropertiesRepository.create({ + title: 'Fazenda Martins', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + const property = await updatePropertyUseCase.execute({ + id, + title: 'Fazenda Martins Junior', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + }); + + expect(property); + }); + + it('should not be able to edit property, because id non-exists', async () => { + await expect(updatePropertyUseCase.execute({ + id: 'id', + title: 'Fazenda Martins Junior', + description: 'Fazenda Martins da zona leste', + value: 1200, + area: 600, + address: 'Rua Fazenda Martins', + public_place: 'Rua Martins Fazenda', + house_number: 9463, + complement: 'Fazenda', + district: 'Zona Leste', + cep: 84313630, + city: 'Fazenda City', + uf: 'FM', + })).rejects.toEqual(new AppError('Property not exist!', 404)); + }); +}); \ No newline at end of file From a7ec8794c77e45fc8b79ccf45229f41c3bfb242e Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:03:34 -0300 Subject: [PATCH 044/129] feat: :sparkles: creating use case for edit property --- .../updateProperty/updatePropertyUseCase.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts diff --git a/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts new file mode 100644 index 0000000..e66a512 --- /dev/null +++ b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts @@ -0,0 +1,56 @@ +import { inject, injectable } from 'tsyringe'; +import { ICreatePropertyDTO } from '@modules/properties/dtos/ICreatePropertyDTO'; +import { Property } from '@modules/properties/infra/typeorm/entities/Property'; +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; +import { AppError } from '@errors/AppError'; + +@injectable() +class UpdatePropertyUseCase { + constructor( + @inject('PropertiesRepository') + private propertiesRepository: IPropertiesRepository, + ) { } + + async execute({ + id, + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + }: ICreatePropertyDTO): Promise { + const propertyExists = await this.propertiesRepository.findById(id); + + if (!propertyExists) { + throw new AppError('Property not exist!', 404); + } + + const property = await this.propertiesRepository.update({ + id, + title, + description, + value, + area, + address, + public_place, + house_number, + complement, + district, + cep, + city, + uf, + created_at: propertyExists.created_at, + }); + + return property; + } +} + +export { UpdatePropertyUseCase } \ No newline at end of file From 56729e5165f96b080ceef3eb524b503671959575 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:10:15 -0300 Subject: [PATCH 045/129] feat: :sparkles: defining access routes routes of properties --- .../infra/http/routes/properties.routes.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 backend/src/modules/properties/infra/http/routes/properties.routes.ts diff --git a/backend/src/modules/properties/infra/http/routes/properties.routes.ts b/backend/src/modules/properties/infra/http/routes/properties.routes.ts new file mode 100644 index 0000000..2b2f58a --- /dev/null +++ b/backend/src/modules/properties/infra/http/routes/properties.routes.ts @@ -0,0 +1,23 @@ +import { Router } from 'express'; +import { ensureAuthenticated } from '@shared/infra/http/middlewares/ensureAuthenticated'; +import { CreatePropertyController } from '@modules/properties/useCases/createProperty/createPropertyController'; +import { ListPropertyController } from '@modules/properties/useCases/listProperties/listPropertiesController'; +import { ShowPropertyByIdController } from '@modules/properties/useCases/showPropertyById/showPropertyByIdController'; +import { UpdatePropertyController } from '@modules/properties/useCases/updateProperty/updatePropertyController'; +import { DeletePropertyByIdController } from '@modules/properties/useCases/deletePropertyById/deletePropertyByIdController'; + +const propertiesRoutes = Router(); + +const createPropertyController = new CreatePropertyController(); +const listPropertyController = new ListPropertyController(); +const showPropertyByIdController = new ShowPropertyByIdController(); +const updatePropertyController = new UpdatePropertyController(); +const deletePropertyByIdController = new DeletePropertyByIdController(); + +propertiesRoutes.post('/', ensureAuthenticated, createPropertyController.handle); +propertiesRoutes.get('/', ensureAuthenticated, listPropertyController.handle); +propertiesRoutes.get('/:id', ensureAuthenticated, showPropertyByIdController.handle); +propertiesRoutes.put('/:id', ensureAuthenticated, updatePropertyController.handle); +propertiesRoutes.delete('/:id', ensureAuthenticated, deletePropertyByIdController.handle); + +export { propertiesRoutes } \ No newline at end of file From c16f8292e374942e394b332e163b00abe1282e84 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:14:18 -0300 Subject: [PATCH 046/129] feat: :sparkles: Creating dto for data transfer validating fields for user registration --- backend/src/modules/accounts/dtos/ICreateUserDTO.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 backend/src/modules/accounts/dtos/ICreateUserDTO.ts diff --git a/backend/src/modules/accounts/dtos/ICreateUserDTO.ts b/backend/src/modules/accounts/dtos/ICreateUserDTO.ts new file mode 100644 index 0000000..0f23123 --- /dev/null +++ b/backend/src/modules/accounts/dtos/ICreateUserDTO.ts @@ -0,0 +1,8 @@ +interface ICreateUserDTO { + name: string, + password: string, + email: string, + id?: string +} + +export { ICreateUserDTO } \ No newline at end of file From b01fa35bb048a2e9e61e8bd5102cd4002c5f8373 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:15:42 -0300 Subject: [PATCH 047/129] feat: :sparkles: create contract to force to have the necessary methods creating a contract to define which methods the repository will have to be mandatory, following a SOLID principle which is O open for implementations and closed for modifications --- .../modules/accounts/repositories/IUsersRepository.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 backend/src/modules/accounts/repositories/IUsersRepository.ts diff --git a/backend/src/modules/accounts/repositories/IUsersRepository.ts b/backend/src/modules/accounts/repositories/IUsersRepository.ts new file mode 100644 index 0000000..21d304b --- /dev/null +++ b/backend/src/modules/accounts/repositories/IUsersRepository.ts @@ -0,0 +1,10 @@ +import { ICreateUserDTO } from '../dtos/ICreateUserDTO'; +import { User } from '../infra/typeorm/entities/User'; + +interface IUsersRepository { + create(data: ICreateUserDTO): Promise; + findByEmail(email: string): Promise; + findByID(id: string): Promise; +} + +export { IUsersRepository } \ No newline at end of file From df6af92fe618e79eda5af2d54dba6fd78a3f2758 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:17:25 -0300 Subject: [PATCH 048/129] feat: :sparkles: creating fake database creating fake database to store unit test data --- .../repositories/fakes/FakeUsersRepository.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 backend/src/modules/accounts/repositories/fakes/FakeUsersRepository.ts diff --git a/backend/src/modules/accounts/repositories/fakes/FakeUsersRepository.ts b/backend/src/modules/accounts/repositories/fakes/FakeUsersRepository.ts new file mode 100644 index 0000000..b28743d --- /dev/null +++ b/backend/src/modules/accounts/repositories/fakes/FakeUsersRepository.ts @@ -0,0 +1,29 @@ +import { ICreateUserDTO } from "@modules/accounts/dtos/ICreateUserDTO"; +import { User } from "@modules/accounts/infra/typeorm/entities/User"; +import { IUsersRepository } from "../IUsersRepository"; + +class FakeUsersRepository implements IUsersRepository { + users: User[] = []; + + async create({ email, name, password }: ICreateUserDTO): Promise { + const user = new User(); + + Object.assign(user, { + email, name, password + }); + + this.users.push(user); + + return user; + } + + async findByEmail(email: string): Promise { + return this.users.find(user => user.email === email); + } + + async findByID(id: string): Promise { + return this.users.find(user => user.id === id); + } +} + +export { FakeUsersRepository } \ No newline at end of file From 13069203f0aa3d03d1d25564baefad980c069da1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:18:42 -0300 Subject: [PATCH 049/129] feat: :sparkles: creating implementation repository with typeorm --- .../typeorm/repositories/UsersRepository.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 backend/src/modules/accounts/infra/typeorm/repositories/UsersRepository.ts diff --git a/backend/src/modules/accounts/infra/typeorm/repositories/UsersRepository.ts b/backend/src/modules/accounts/infra/typeorm/repositories/UsersRepository.ts new file mode 100644 index 0000000..f15533a --- /dev/null +++ b/backend/src/modules/accounts/infra/typeorm/repositories/UsersRepository.ts @@ -0,0 +1,44 @@ +import { getRepository, Repository } from 'typeorm'; +import { ICreateUserDTO } from '@modules/accounts/dtos/ICreateUserDTO'; +import { User } from '../entities/User'; +import { IUsersRepository } from '@modules/accounts/repositories/IUsersRepository'; + +class UsersRepository implements IUsersRepository { + private repository: Repository + + constructor() { + this.repository = getRepository(User); + } + + async create({ + name, + password, + email, + id, + }: ICreateUserDTO): Promise { + const user = this.repository.create({ + name, + password, + email, + id, + }) + + const userCreate = await this.repository.save(user); + + return userCreate; + } + + async findByEmail(email: string): Promise { + const user = await this.repository.findOne({ email }); + + return user; + } + + async findByID(id: string): Promise { + const user = await this.repository.findOne(id); + + return user; + } +} + +export { UsersRepository } From ed94a811d53abdc0ed2b4ba869fbc4a0b1a47ca3 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:20:11 -0300 Subject: [PATCH 050/129] feat: :sparkles: creating a contract for password encryption --- .../accounts/providers/HashProvider/models/IHashProvider.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/src/modules/accounts/providers/HashProvider/models/IHashProvider.ts diff --git a/backend/src/modules/accounts/providers/HashProvider/models/IHashProvider.ts b/backend/src/modules/accounts/providers/HashProvider/models/IHashProvider.ts new file mode 100644 index 0000000..5f6bb9f --- /dev/null +++ b/backend/src/modules/accounts/providers/HashProvider/models/IHashProvider.ts @@ -0,0 +1,6 @@ +interface IHashProvider { + generateHash(payload: string): Promise; + compareHash(payload: string, hashed: string): Promise; +} + +export { IHashProvider } \ No newline at end of file From f4b9e4d24bc0e83655a80a9f46e714ab754d95e9 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:21:03 -0300 Subject: [PATCH 051/129] feat: :sparkles: creating a fake encryption for the password --- .../HashProvider/fakes/FakeHashProvider.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 backend/src/modules/accounts/providers/HashProvider/fakes/FakeHashProvider.ts diff --git a/backend/src/modules/accounts/providers/HashProvider/fakes/FakeHashProvider.ts b/backend/src/modules/accounts/providers/HashProvider/fakes/FakeHashProvider.ts new file mode 100644 index 0000000..0ecbae7 --- /dev/null +++ b/backend/src/modules/accounts/providers/HashProvider/fakes/FakeHashProvider.ts @@ -0,0 +1,13 @@ +import { IHashProvider } from '../models/IHashProvider'; + +class FakeHashProvider implements IHashProvider { + public async generateHash(payload: string): Promise { + return payload; + } + + public async compareHash(payload: string, hashed: string): Promise { + return payload === hashed; + } +} + +export { FakeHashProvider } \ No newline at end of file From 42e7e24c56931c6855d7e6dcdd730c138520eb0f Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:21:53 -0300 Subject: [PATCH 052/129] feat: :sparkles: implementing encryption with bcrypt for the password --- .../implementations/BCryptHashProvider.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/src/modules/accounts/providers/HashProvider/implementations/BCryptHashProvider.ts diff --git a/backend/src/modules/accounts/providers/HashProvider/implementations/BCryptHashProvider.ts b/backend/src/modules/accounts/providers/HashProvider/implementations/BCryptHashProvider.ts new file mode 100644 index 0000000..f81a539 --- /dev/null +++ b/backend/src/modules/accounts/providers/HashProvider/implementations/BCryptHashProvider.ts @@ -0,0 +1,14 @@ +import { compare, hash } from 'bcrypt'; +import { IHashProvider } from '../models/IHashProvider'; + +class BCryptHashProvider implements IHashProvider { + public async generateHash(payload: string): Promise { + return await hash(payload, 8); + } + + public async compareHash(payload: string, hashed: string): Promise { + return await compare(payload, hashed); + } +} + +export { BCryptHashProvider } \ No newline at end of file From a287d2f2bbb3199e725aa1fff7d647d52d3090e6 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:23:01 -0300 Subject: [PATCH 053/129] feat: :sparkles: creating injection to inject bcrypt into use cases --- backend/src/modules/accounts/providers/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/src/modules/accounts/providers/index.ts diff --git a/backend/src/modules/accounts/providers/index.ts b/backend/src/modules/accounts/providers/index.ts new file mode 100644 index 0000000..d5f8fec --- /dev/null +++ b/backend/src/modules/accounts/providers/index.ts @@ -0,0 +1,6 @@ +import { container } from 'tsyringe'; + +import { IHashProvider } from './HashProvider/models/IHashProvider'; +import { BCryptHashProvider } from './HashProvider/implementations/BCryptHashProvider'; + +container.registerSingleton('HashProvider', BCryptHashProvider); \ No newline at end of file From c937f97f41cb1e08c3c519d08141a7a2b6e5a831 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:29:43 -0300 Subject: [PATCH 054/129] feat: :sparkles: creating controller for authentication --- .../authenticateUser/AuthenticateUserController.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.ts diff --git a/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.ts b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.ts new file mode 100644 index 0000000..6a68961 --- /dev/null +++ b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.ts @@ -0,0 +1,14 @@ +import { Request, Response } from "express"; +import { container } from 'tsyringe'; +import { AuthenticateUserUseCase } from "./AuthenticateUserUseCase"; + +class AuthenticateUserController { + public async handle(request: Request, response: Response): Promise { + const { email, password } = request.body; + const authenticateUserUseCase = container.resolve(AuthenticateUserUseCase); + const { token, user } = await authenticateUserUseCase.execute({ email, password }); + return response.status(200).json({ token, user }); + } +} + +export { AuthenticateUserController } \ No newline at end of file From f3b04c5c47c97c3af662590538ef74a0d9166284 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:30:22 -0300 Subject: [PATCH 055/129] test: :white_check_mark: create use case unit test for authentication generate of token --- .../AuthenticateUserUseCase.spec.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.spec.ts diff --git a/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.spec.ts b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.spec.ts new file mode 100644 index 0000000..d8c591d --- /dev/null +++ b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.spec.ts @@ -0,0 +1,53 @@ +import { AppError } from "@errors/AppError"; +import { ICreateUserDTO } from "@modules/accounts/dtos/ICreateUserDTO"; +import { FakeHashProvider } from "@modules/accounts/providers/HashProvider/fakes/FakeHashProvider"; +import { FakeUsersRepository } from "@modules/accounts/repositories/fakes/FakeUsersRepository"; +import { CreateUserUseCase } from "../createUser/CreateUseUseCase"; +import { AuthenticateUserUseCase } from "./AuthenticateUserUseCase"; + +let authenticateUserUseCase: AuthenticateUserUseCase; +let fakeUsersRepository: FakeUsersRepository; +let fakeHashProvider: FakeHashProvider; +let createUserUseCase: CreateUserUseCase; + +describe('Authenticate User', () => { + + beforeEach(() => { + fakeUsersRepository = new FakeUsersRepository(); + fakeHashProvider = new FakeHashProvider(); + authenticateUserUseCase = new AuthenticateUserUseCase(fakeUsersRepository, fakeHashProvider); + createUserUseCase = new CreateUserUseCase(fakeUsersRepository, fakeHashProvider); + }); + + it('should be able to authenticate an user', async () => { + const user: ICreateUserDTO = { + name: 'Usuario test', + password: '123456', + email: 'test@test.com', + } + await createUserUseCase.execute(user); + + const results = await authenticateUserUseCase.execute({ email: user.email, password: user.password }); + + expect(results).toHaveProperty('token'); + }); + + it('should not be able to authenticate an nonexistent user', async () => { + expect(async () => { + await authenticateUserUseCase.execute({ email: 'false@test.com', password: 'false' }); + }).rejects.toBeInstanceOf(AppError); + }); + + it('should not be able to authenticate with incorrect password', async () => { + expect(async () => { + const user: ICreateUserDTO = { + name: 'Usuario test 2', + password: '654321', + email: 'test2@test.com', + } + await createUserUseCase.execute(user); + + await authenticateUserUseCase.execute({ email: user.email, password: 'incorrectPassword' }); + }).rejects.toBeInstanceOf(AppError); + }); +}); \ No newline at end of file From 2699a1a8526c95b9475c30f3adbe8b7e9068d0f0 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:31:28 -0300 Subject: [PATCH 056/129] feat: :sparkles: creating use case for authentication generation of token --- .../AuthenticateUserUseCase.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.ts diff --git a/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.ts b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.ts new file mode 100644 index 0000000..b2b0a00 --- /dev/null +++ b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserUseCase.ts @@ -0,0 +1,41 @@ +import { sign } from "jsonwebtoken"; +import { injectable, inject } from "tsyringe"; +import { AppError } from '@errors/AppError'; +import { User } from "@modules/accounts/infra/typeorm/entities/User"; +import { IHashProvider } from "@modules/accounts/providers/HashProvider/models/IHashProvider"; +import { IUsersRepository } from "@modules/accounts/repositories/IUsersRepository"; + +interface IRequest { + email: string; + password: string; +} + +@injectable() +class AuthenticateUserUseCase { + constructor( + @inject('UsersRepository') + private usersRepository: IUsersRepository, + + @inject('HashProvider') + private hashProvider: IHashProvider + ) { } + + public async execute({ email, password }: IRequest): Promise<{ token: string, user: User }> { + const user = await this.usersRepository.findByEmail(email); + if (!user) { + throw new AppError('Email or password incorrect'); + } + const passwordMatch = await this.hashProvider.compareHash(password, user.password); + if (!passwordMatch) { + throw new AppError('Email or password incorrect'); + } + const token = sign({}, process.env.APP_JWT_SECRET || 'jwt_secret', { + subject: user.id, + expiresIn: '1d' + }) + + return { token, user } + } +} + +export { AuthenticateUserUseCase } \ No newline at end of file From e596ea1ca88b571da24a90c0fe0cef1964fef753 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:33:30 -0300 Subject: [PATCH 057/129] feat: :sparkles: creating controller for creating of the user screen of register --- .../useCases/createUser/CreateUserController.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/createUser/CreateUserController.ts diff --git a/backend/src/modules/accounts/useCases/createUser/CreateUserController.ts b/backend/src/modules/accounts/useCases/createUser/CreateUserController.ts new file mode 100644 index 0000000..1aaaff1 --- /dev/null +++ b/backend/src/modules/accounts/useCases/createUser/CreateUserController.ts @@ -0,0 +1,17 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; +import { CreateUserUseCase } from './CreateUseUseCase'; + +class CreateUserController { + async handle(request: Request, response: Response): Promise { + const { name, password, email } = request.body; + + const createUserUseCase = container.resolve(CreateUserUseCase); + + const user = await createUserUseCase.execute({ name, password, email }); + + return response.status(201).json(user); + } +} + +export { CreateUserController } \ No newline at end of file From 518a51e652745bd59f732164ffba04d87b8fec50 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:34:32 -0300 Subject: [PATCH 058/129] test: :white_check_mark: create use case unit test for create new user --- .../createUser/createUserUseCase.spec.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/createUser/createUserUseCase.spec.ts diff --git a/backend/src/modules/accounts/useCases/createUser/createUserUseCase.spec.ts b/backend/src/modules/accounts/useCases/createUser/createUserUseCase.spec.ts new file mode 100644 index 0000000..5904af4 --- /dev/null +++ b/backend/src/modules/accounts/useCases/createUser/createUserUseCase.spec.ts @@ -0,0 +1,40 @@ +import { AppError } from "@shared/errors/AppError"; +import { FakeHashProvider } from "../../providers/HashProvider/fakes/FakeHashProvider"; +import { FakeUsersRepository } from "../../repositories/fakes/FakeUsersRepository"; +import { CreateUserUseCase } from "./CreateUseUseCase"; + +let fakeUsersRepository: FakeUsersRepository; +let hashProvider: FakeHashProvider; +let createUserUseCase: CreateUserUseCase; + +describe('Create User', () => { + beforeEach(() => { + fakeUsersRepository = new FakeUsersRepository(); + hashProvider = new FakeHashProvider(); + createUserUseCase = new CreateUserUseCase(fakeUsersRepository, hashProvider); + }); + + it('should be able to create new user', async () => { + const user = await createUserUseCase.execute({ + name: 'John Joe', + email: 'john.joe@example.com', + password: '123456' + }); + + expect(user).toHaveProperty('id'); + }); + + it('should not be able to create new user, because email already exists', async () => { + await fakeUsersRepository.create({ + name: 'John Joe', + email: 'john.joe@example.com', + password: '123456' + }); + + await expect(createUserUseCase.execute({ + name: 'John Joe', + email: 'john.joe@example.com', + password: '123456' + })).rejects.toBeInstanceOf(AppError); + }); +}); \ No newline at end of file From b0ed95dc7757237101a719f5255791e1a3c2d1d8 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:34:55 -0300 Subject: [PATCH 059/129] feat: :sparkles: creating use case for create new user --- .../useCases/createUser/CreateUseUseCase.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/createUser/CreateUseUseCase.ts diff --git a/backend/src/modules/accounts/useCases/createUser/CreateUseUseCase.ts b/backend/src/modules/accounts/useCases/createUser/CreateUseUseCase.ts new file mode 100644 index 0000000..9122304 --- /dev/null +++ b/backend/src/modules/accounts/useCases/createUser/CreateUseUseCase.ts @@ -0,0 +1,41 @@ +import { inject, injectable } from 'tsyringe'; +import { ICreateUserDTO } from '@modules/accounts/dtos/ICreateUserDTO'; +import { User } from '@modules/accounts/infra/typeorm/entities/User'; +import { IUsersRepository } from '@modules/accounts/repositories/IUsersRepository'; +import { AppError } from '@errors/AppError'; +import { IHashProvider } from '@modules/accounts/providers/HashProvider/models/IHashProvider'; + +@injectable() +class CreateUserUseCase { + constructor( + @inject('UsersRepository') + private usersRepository: IUsersRepository, + + @inject('HashProvider') + private hashProvider: IHashProvider + ) { } + + async execute({ + name, + password, + email + }: ICreateUserDTO): Promise { + const userAlreadyExist = await this.usersRepository.findByEmail(email); + + if (userAlreadyExist) { + throw new AppError('User already exist!'); + } + + const passwordHash = await this.hashProvider.generateHash(password); + + const user = await this.usersRepository.create({ + name, + password: passwordHash, + email + }); + + return user; + } +} + +export { CreateUserUseCase } \ No newline at end of file From a5899f6b21c4f346ed8b96504d906d5432f09ca7 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:37:28 -0300 Subject: [PATCH 060/129] feat: :sparkles: adding routes to the session --- .../accounts/infra/http/routes/authenticate.routes.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 backend/src/modules/accounts/infra/http/routes/authenticate.routes.ts diff --git a/backend/src/modules/accounts/infra/http/routes/authenticate.routes.ts b/backend/src/modules/accounts/infra/http/routes/authenticate.routes.ts new file mode 100644 index 0000000..60be58f --- /dev/null +++ b/backend/src/modules/accounts/infra/http/routes/authenticate.routes.ts @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import { AuthenticateUserController } from '@modules/accounts/useCases/authenticateUser/AuthenticateUserController'; + +const authenticateRouter = Router(); +const authenticateUserController = new AuthenticateUserController(); + +authenticateRouter.post('/', authenticateUserController.handle); + +export { authenticateRouter } \ No newline at end of file From 6a6ff8bb9dad8d9c84532d6a8424cddd7bf0558d Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:38:01 -0300 Subject: [PATCH 061/129] feat: :sparkles: adding routes to the user --- .../accounts/infra/http/routes/users.routes.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 backend/src/modules/accounts/infra/http/routes/users.routes.ts diff --git a/backend/src/modules/accounts/infra/http/routes/users.routes.ts b/backend/src/modules/accounts/infra/http/routes/users.routes.ts new file mode 100644 index 0000000..851f2ce --- /dev/null +++ b/backend/src/modules/accounts/infra/http/routes/users.routes.ts @@ -0,0 +1,11 @@ +import { Router } from 'express'; + +import { CreateUserController } from '@modules/accounts/useCases/createUser/CreateUserController'; + +const usersRoutes = Router(); + +const createUserController = new CreateUserController(); + +usersRoutes.post('/', createUserController.handle); + +export { usersRoutes } \ No newline at end of file From e7a3142670a1c557ac195cf874e60abd3b761484 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Fri, 6 Aug 2021 22:42:49 -0300 Subject: [PATCH 062/129] feat: :sparkles: adding repository injections to the application --- backend/src/shared/container/index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/src/shared/container/index.ts diff --git a/backend/src/shared/container/index.ts b/backend/src/shared/container/index.ts new file mode 100644 index 0000000..122a5b6 --- /dev/null +++ b/backend/src/shared/container/index.ts @@ -0,0 +1,14 @@ +import { container } from 'tsyringe'; + +import '@modules/accounts/providers'; + +import { IUsersRepository } from '@modules/accounts/repositories/IUsersRepository'; +import { UsersRepository } from '@modules/accounts/infra/typeorm/repositories/UsersRepository'; + +import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; +import { PropertiesRepository } from '@modules/properties/infra/typeorm/repositories/PropertiesRepository'; + + +container.registerSingleton('UsersRepository', UsersRepository); + +container.registerSingleton('PropertiesRepository', PropertiesRepository); \ No newline at end of file From 7edc1e85e70bef7530094cd3389cdf24c9c8bd03 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:29:46 -0300 Subject: [PATCH 063/129] feat: :sparkles: Adicionando o axios e o cors --- backend/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 9a0d513..36a35c4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,7 +10,9 @@ "seed:admin": "ts-node-dev src/shared/infra/typeorm/seed/admin.ts" }, "dependencies": { + "axios": "^0.21.1", "bcrypt": "^5.0.1", + "cors": "^2.8.5", "csv-parse": "^4.15.3", "dayjs": "^1.10.4", "express": "^4.17.1", @@ -20,14 +22,15 @@ "multer": "^1.4.2", "pg": "^8.5.1", "reflect-metadata": "^0.1.13", + "supertest": "^6.1.3", "swagger-ui-express": "^4.1.6", "tsyringe": "^4.5.0", "typeorm": "^0.2.32", - "supertest": "^6.1.3", "uuid": "^8.3.2" }, "devDependencies": { "@types/bcrypt": "^3.0.0", + "@types/cors": "^2.8.12", "@types/express": "^4.17.11", "@types/jest": "^26.0.22", "@types/jsonwebtoken": "^8.5.1", From 7de5ab12a46c33a411e3efe1604f625c32a8c195 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:30:25 -0300 Subject: [PATCH 064/129] =?UTF-8?q?feat:=20:sparkles:=20Adicionando=20as?= =?UTF-8?q?=20novas=20rotas=20na=20documental=C3=A7=C3=A3o=20da=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/swagger.json | 199 ++++++++++++++++++++++++++++++++++----- 1 file changed, 177 insertions(+), 22 deletions(-) diff --git a/backend/src/swagger.json b/backend/src/swagger.json index ee8a7f7..287b4a4 100644 --- a/backend/src/swagger.json +++ b/backend/src/swagger.json @@ -10,11 +10,85 @@ } }, "paths": { + "/users": { + "post": { + "tags": ["Users"], + "summary": "Create a new user", + "description": "Create a new user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "number" + } + } + }, + "example": { + "name": "Admin", + "email": "admin@certimoveis.com.br", + "password": "admin_test" + } + } + } + }, + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "User already exist!" + } + } + } + }, + "/sessions": { + "post": { + "tags": ["Sessions"], + "summary": "Authenticate a user", + "description": "Authenticate a user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "number" + } + } + }, + "example": { + "email": "admin@certimoveis.com.br", + "password": "admin_test" + } + } + } + }, + "responses": { + "200": { + "description": "Authenticated" + }, + "400": { + "description": "Email or password incorrect!" + } + } + } + }, "/properties": { "post": { - "tags": [ - "Properties" - ], + "tags": ["Properties"], "summary": "Create a property", "description": "Create new a property", "requestBody": { @@ -38,26 +112,11 @@ "address": { "type": "string" }, - "public_place": { - "type": "string" - }, "house_number": { "type": "number" }, - "complement": { - "type": "string" - }, - "district": { - "type": "string" - }, "cep": { "type": "number" - }, - "city": { - "type": "string" - }, - "uf": { - "type": "string" } } }, @@ -88,9 +147,7 @@ } }, "get": { - "tags": [ - "Properties" - ], + "tags": ["Properties"], "summary": "List all properties", "description": "List all properties", "responses": { @@ -166,6 +223,104 @@ } } } + }, + "put": { + "tags": ["Properties"], + "summary": "Update the property", + "description": "Update a property by Id", + "parameters": [ + { + "name": "id", + "required": true, + "description": "Id of property" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + "type": "number" + }, + "area": { + "type": "number" + }, + "address": { + "type": "string" + }, + "public_place": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "house_number": { + "type": "number" + }, + "cep": { + "type": "number" + }, + "city": { + "type": "string" + }, + "uf": { + "type": "string" + } + } + }, + "example": { + "title": "Im贸vel Martins", + "description": "Fazenda Martins", + "value": "1500", + "area": "600", + "address": "Rua fazenda Martins", + "public_place": "Rua Fazenda Martins", + "house_number": "5122", + "complement": "Fazenda", + "district": "Zona Leste", + "cep": "9999999", + "city": "Fazenda", + "uf": "FM" + } + } + } + }, + "responses": { + "200": { + "description": "Updated with success!" + }, + "404": { + "description": "Property not exist!" + } + } + }, + "delete": { + "tags": ["Properties"], + "summary": "Delete the property", + "description": "Delete a property by Id", + "parameters": [ + { + "name": "id", + "required": true, + "description": "Id of property" + } + ], + "responses": { + "200": { + "description": "Deleted with success!" + }, + "404": { + "description": "Property not exist!" + } + } } } }, @@ -182,4 +337,4 @@ } } } -} \ No newline at end of file +} From f789a902a36f066064d311678c59371519af9b09 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:31:13 -0300 Subject: [PATCH 065/129] =?UTF-8?q?refactor:=20:recycle:=20Adicionando=20o?= =?UTF-8?q?=20cors=20para=20as=20permiss=C3=B5es=20de=20outros=20dominio?= =?UTF-8?q?=20nas=20rotas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/shared/infra/http/app.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/shared/infra/http/app.ts b/backend/src/shared/infra/http/app.ts index 99dfca7..5c2c72a 100644 --- a/backend/src/shared/infra/http/app.ts +++ b/backend/src/shared/infra/http/app.ts @@ -1,5 +1,6 @@ import 'reflect-metadata'; import 'dotenv/config'; +import cors from 'cors'; import express, { NextFunction, Request, Response } from 'express'; import 'express-async-errors'; import swaggerUi from 'swagger-ui-express'; @@ -13,6 +14,8 @@ createConnection(); const app = express(); +app.use(cors()); + app.use(express.json()); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)); From da707ffb0aec0821f3f805ad991e8747c91d8213 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:32:01 -0300 Subject: [PATCH 066/129] refactor: :recycle: Alterando o nome do DB --- backend/src/shared/infra/typeorm/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/shared/infra/typeorm/index.ts b/backend/src/shared/infra/typeorm/index.ts index 9b633d1..71bce3d 100644 --- a/backend/src/shared/infra/typeorm/index.ts +++ b/backend/src/shared/infra/typeorm/index.ts @@ -6,7 +6,7 @@ export default async (host = 'database'): Promise => { return createConnection( Object.assign(defaultOptions, { host: process.env.NODE_ENV === 'test' ? 'localhost' : host, - database: process.env.NODE_ENV === 'test' ? 'cert_imoveis_test' : defaultOptions.database, + database: process.env.NODE_ENV === 'test' ? 'cert_imoveis' : defaultOptions.database, }) ); } \ No newline at end of file From 00d97b10fb48bb840b6a78b79840f8c9166f40f1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:32:37 -0300 Subject: [PATCH 067/129] =?UTF-8?q?feat:=20:sparkles:=20Adicionando=20a=20?= =?UTF-8?q?nova=20inje=C3=A7=C3=A3o=20no=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/shared/container/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/shared/container/index.ts b/backend/src/shared/container/index.ts index 122a5b6..e38a5c4 100644 --- a/backend/src/shared/container/index.ts +++ b/backend/src/shared/container/index.ts @@ -2,12 +2,16 @@ import { container } from 'tsyringe'; import '@modules/accounts/providers'; +import { ICEPProvider } from './CEPProvider/models/ICEPProvider'; +import { ViaCEPProvider } from './CEPProvider/implementations/ViaCEPProvider'; + import { IUsersRepository } from '@modules/accounts/repositories/IUsersRepository'; import { UsersRepository } from '@modules/accounts/infra/typeorm/repositories/UsersRepository'; import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; import { PropertiesRepository } from '@modules/properties/infra/typeorm/repositories/PropertiesRepository'; +container.registerSingleton('CEPProvider', ViaCEPProvider); container.registerSingleton('UsersRepository', UsersRepository); From 313758508f8feda0e959bfb93665a8dcad202b2e Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:35:08 -0300 Subject: [PATCH 068/129] feat: :sparkles: Criando a interface do provider de CEP --- .../container/CEPProvider/models/ICEPProvider.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 backend/src/shared/container/CEPProvider/models/ICEPProvider.ts diff --git a/backend/src/shared/container/CEPProvider/models/ICEPProvider.ts b/backend/src/shared/container/CEPProvider/models/ICEPProvider.ts new file mode 100644 index 0000000..5da5739 --- /dev/null +++ b/backend/src/shared/container/CEPProvider/models/ICEPProvider.ts @@ -0,0 +1,13 @@ +export interface CEPResponse { + public_place: string, + complement: string, + district: string, + city: string, + uf: string +} + +interface ICEPProvider { + recoverAddress(cep: number): Promise; +} + +export { ICEPProvider } \ No newline at end of file From de464c46ee2128320d5630fa8042a8a4791bc9d3 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:35:39 -0300 Subject: [PATCH 069/129] =?UTF-8?q?feat:=20:sparkles:=20Criando=20implemen?= =?UTF-8?q?ta=C3=A7=C3=A3o=20do=20provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usando o Via CEP --- .../implementations/ViaCEPProvider.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/src/shared/container/CEPProvider/implementations/ViaCEPProvider.ts diff --git a/backend/src/shared/container/CEPProvider/implementations/ViaCEPProvider.ts b/backend/src/shared/container/CEPProvider/implementations/ViaCEPProvider.ts new file mode 100644 index 0000000..b45de51 --- /dev/null +++ b/backend/src/shared/container/CEPProvider/implementations/ViaCEPProvider.ts @@ -0,0 +1,31 @@ +import { AppError } from '@shared/errors/AppError'; +import axios from 'axios'; +import { CEPResponse, ICEPProvider } from "../models/ICEPProvider"; + +class ViaCEPProvider implements ICEPProvider { + private client: any; + constructor() { + this.client = axios.create({ + baseURL: "https://viacep.com.br/ws", + timeout: 60000, + }); + } + + public async recoverAddress(cep: number): Promise { + try { + const { data } = await this.client.get(`/${cep}/json`); + + return { + public_place: data.logradouro || 'N茫o encontrado', + complement: data.complemento || 'N茫o encontrado', + district: data.bairro || 'N茫o encontrado', + city: data.localidade || 'N茫o encontrado', + uf: data.uf || 'N茫o encontrado' + } + } catch (error) { + new AppError("Erro na api do Via CEP.", 500) + } + } +} + +export {ViaCEPProvider} \ No newline at end of file From 7d7e3033e5440072a2e6007bcdfffb4f29a99bab Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:36:01 -0300 Subject: [PATCH 070/129] =?UTF-8?q?feat:=20:sparkles:=20Criando=20implemen?= =?UTF-8?q?ta=C3=A7=C3=A3o=20fake=20para=20os=20testes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CEPProvider/fakes/FakeCEPProvider.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/src/shared/container/CEPProvider/fakes/FakeCEPProvider.ts diff --git a/backend/src/shared/container/CEPProvider/fakes/FakeCEPProvider.ts b/backend/src/shared/container/CEPProvider/fakes/FakeCEPProvider.ts new file mode 100644 index 0000000..24b8fcf --- /dev/null +++ b/backend/src/shared/container/CEPProvider/fakes/FakeCEPProvider.ts @@ -0,0 +1,15 @@ +import { ICEPProvider, CEPResponse } from '../models/ICEPProvider'; + +class FakeCEPProvider implements ICEPProvider { + public async recoverAddress(cep: number): Promise { + return { + public_place: "public_place", + complement: "complement", + district: "district", + city: "city", + uf: "uf" + } + } +} + +export { FakeCEPProvider } \ No newline at end of file From 575eba90f5bca7004fe72fe794e4349903eb8ed1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:39:12 -0300 Subject: [PATCH 071/129] =?UTF-8?q?feat:=20:sparkles:=20Implementando=20te?= =?UTF-8?q?ste=20de=20integra=C3=A7=C3=A3o=20para=20autentica=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthenticateUserController.spec.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.spec.ts diff --git a/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.spec.ts b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.spec.ts new file mode 100644 index 0000000..939186c --- /dev/null +++ b/backend/src/modules/accounts/useCases/authenticateUser/AuthenticateUserController.spec.ts @@ -0,0 +1,62 @@ +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { hash } from 'bcrypt'; +import { v4 as uuidV4 } from 'uuid'; + +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; + +let connection: Connection; + +describe('Authenticate User Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + const id = uuidV4(); + const password = await hash('admin_test', 8); + await connection.query(` + INSERT INTO users(id, name, email, password, created_at) + VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to authenticate user', async () => { + const response = await request(app) + .post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }) + + expect(response.status).toBe(200); + }); + + it('should not be able to authenticate an nonexistent user', async () => { + const response = await request(app) + .post('/sessions') + .send({ + email: 'admin@com.br', + password: 'admin_test' + }) + + expect(response.status).toBe(400); + }); + + it('should not be able to authenticate with incorrect password', async () => { + const response = await request(app) + .post('/sessions') + .send({ + email: 'admin@certimoveis.com.br', + password: 'admin_test1' + }) + + expect(response.status).toBe(400); + }); +}); \ No newline at end of file From 78bb2abd7e2ab25b1a11ee6e5e74260b04968e4c Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:39:45 -0300 Subject: [PATCH 072/129] =?UTF-8?q?feat:=20:sparkles:=20Implementando=20te?= =?UTF-8?q?ste=20de=20integra=C3=A7=C3=A3o=20para=20criar=20o=20usuario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createUser/CreateUserController.spec.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 backend/src/modules/accounts/useCases/createUser/CreateUserController.spec.ts diff --git a/backend/src/modules/accounts/useCases/createUser/CreateUserController.spec.ts b/backend/src/modules/accounts/useCases/createUser/CreateUserController.spec.ts new file mode 100644 index 0000000..d43f148 --- /dev/null +++ b/backend/src/modules/accounts/useCases/createUser/CreateUserController.spec.ts @@ -0,0 +1,53 @@ +import request from 'supertest'; +import { Connection } from 'typeorm'; +import { hash } from 'bcrypt'; +import { v4 as uuidV4 } from 'uuid'; + +import { app } from '@shared/infra/http/app'; +import createConnection from '@shared/infra/typeorm'; + +let connection: Connection; + +describe('Create User Controller', () => { + + beforeAll(async () => { + connection = await createConnection(); + await connection.runMigrations(); + + // const id = uuidV4(); + // const password = await hash('admin_test', 8); + // await connection.query(` + // INSERT INTO users(id, name, email, password, created_at) + // VALUES('${id}', 'admin', 'admin@certimoveis.com.br', '${password}', 'now()') + // `); + }); + + afterAll(async () => { + await connection.dropDatabase(); + await connection.close(); + }); + + it('should be able to create new user', async () => { + const response = await request(app) + .post('/users') + .send({ + name: "Admin", + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }) + + expect(response.status).toBe(201); + }); + + it('should not be able to create new user, because email already exists', async () => { + const response = await request(app) + .post('/users') + .send({ + name: "Admin 1", + email: 'admin@certimoveis.com.br', + password: 'admin_test' + }) + + expect(response.status).toBe(400); + }); +}); \ No newline at end of file From 772e123761c570f6d0d65fc068a727f75bb584a7 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:40:45 -0300 Subject: [PATCH 073/129] refactor: :recycle: Refatorando o controller para diminuir os dados do body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit por causa da implementa莽茫o do viacep, os dados ser茫o pegos por ele --- .../createProperty/createPropertyController.spec.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts index 18892b6..4faa531 100644 --- a/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyController.spec.ts @@ -32,7 +32,7 @@ describe('Create Property Controller', () => { .send({ email: 'admin@certimoveis.com.br', password: 'admin_test' - }); + }); const { token } = responseToken.body; @@ -44,16 +44,11 @@ describe('Create Property Controller', () => { value: 16156, area: 3464, address: 'Address Supertest', - public_place: 'Public Place Supertest', house_number: 6161, - complement: 'Fazenda', - district: 'Supertest', - cep: 8461036, - city: 'Supertest', - uf: 'SU' + cep: 59930000, }).set({ Authorization: `Bearer ${token}`, - }); + }); expect(response.status).toBe(201); }); @@ -79,7 +74,7 @@ describe('Create Property Controller', () => { house_number: 6161, complement: 'Fazenda', district: 'Supertest', - cep: 8461036, + cep: 59930000, city: 'Supertest', uf: 'SU' }).set({ From 2fd0040076b5553a56288fc570835283b52a10fc Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:41:23 -0300 Subject: [PATCH 074/129] =?UTF-8?q?refactor:=20:recycle:=20retirando=20os?= =?UTF-8?q?=20dados=20de=20localiza=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit por causa da implementa莽茫o do viacep, os dados ser茫o pegos por ele --- .../createProperty/createPropertyController.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts index bb157fb..6175a3c 100644 --- a/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyController.ts @@ -3,35 +3,25 @@ import { container } from 'tsyringe'; import { CreatePropertyUseCase } from './createPropertyUseCase'; class CreatePropertyController { - async handle(request: Request, response: Response): Promise { + async handle(request: Request, response: Response): Promise { const { title, description, value, area, address, - public_place, house_number, - complement, - district, - cep, - city, - uf } = request.body; + cep } = request.body; const createPropertyUseCase = container.resolve(CreatePropertyUseCase); - + const property = await createPropertyUseCase.execute({ title, description, value, area, address, - public_place, house_number, - complement, - district, cep, - city, - uf }); return response.status(201).json(property); From 5a8f19ac62c8a9b9adbb8ca0ca9925c245657688 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:41:55 -0300 Subject: [PATCH 075/129] =?UTF-8?q?refactor:=20:recycle:=20Retirando=20os?= =?UTF-8?q?=20dados=20de=20localiza=C3=A7=C3=A3o=20da=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit por causa da implementa莽茫o do viacep, os dados ser茫o pegos por ele --- .../createPropertyUseCase.spec.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts index 99db7a5..98d7be8 100644 --- a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.spec.ts @@ -1,14 +1,20 @@ +import { FakeCEPProvider } from "@shared/container/CEPProvider/fakes/FakeCEPProvider"; import { AppError } from "@shared/errors/AppError"; import { FakePropertiesRepository } from "../../repositories/fakes/FakesPropertiesRepository"; import { CreatePropertyUseCase } from "./createPropertyUseCase"; let fakePropertiesRepository: FakePropertiesRepository; +let fakeCEPProvider: FakeCEPProvider; let createPropertyUseCase: CreatePropertyUseCase; -describe('Create Propert', () => { +describe('Create Property', () => { beforeEach(() => { fakePropertiesRepository = new FakePropertiesRepository(); - createPropertyUseCase = new CreatePropertyUseCase(fakePropertiesRepository); + fakeCEPProvider = new FakeCEPProvider(); + createPropertyUseCase = new CreatePropertyUseCase( + fakePropertiesRepository, + fakeCEPProvider + ); }); it('should be able to create new user', async () => { @@ -18,13 +24,8 @@ describe('Create Propert', () => { value: 1200, area: 600, address: 'Rua Fazenda Martins', - public_place: 'Rua Martins Fazenda', house_number: 9463, - complement: 'Fazenda', - district: 'Zona Leste', cep: 84313630, - city: 'Fazenda City', - uf: 'FM', }); expect(property).toHaveProperty('id'); @@ -52,13 +53,8 @@ describe('Create Propert', () => { value: 1200, area: 600, address: 'Rua Fazenda Martins', - public_place: 'Rua Martins Fazenda', house_number: 9463, - complement: 'Fazenda', - district: 'Zona Leste', - cep: 84313630, - city: 'Fazenda City', - uf: 'FM', + cep: 84313630 })).rejects.toEqual(new AppError('Property already exist!')); }); }); \ No newline at end of file From 760077e4992fd871312a7a8bf06a9a2a4e254e1c Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:42:19 -0300 Subject: [PATCH 076/129] =?UTF-8?q?refactor:=20:recycle:=20Retirando=20os?= =?UTF-8?q?=20dados=20de=20localiza=C3=A7=C3=A3o=20da=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit por causa da implementa莽茫o do viacep, os dados ser茫o pegos por ele --- .../createProperty/createPropertyUseCase.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts index 1226d7f..4274f16 100644 --- a/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts +++ b/backend/src/modules/properties/useCases/createProperty/createPropertyUseCase.ts @@ -1,14 +1,19 @@ import { inject, injectable } from 'tsyringe'; +import { IRequestPropertyDTO } from '@modules/properties/dtos/IRequestPropertyDTO'; import { ICreatePropertyDTO } from '@modules/properties/dtos/ICreatePropertyDTO'; import { Property } from '@modules/properties/infra/typeorm/entities/Property'; import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; import { AppError } from '@errors/AppError'; +import { ICEPProvider } from '@shared/container/CEPProvider/models/ICEPProvider'; @injectable() class CreatePropertyUseCase { constructor( @inject('PropertiesRepository') private propertiesRepository: IPropertiesRepository, + + @inject('CEPProvider') + private cepRepository: ICEPProvider, ) { } async execute({ @@ -17,20 +22,23 @@ class CreatePropertyUseCase { value, area, address, - public_place, house_number, - complement, - district, cep, - city, - uf, - }: ICreatePropertyDTO): Promise { + }: IRequestPropertyDTO): Promise { const propertyAlreadyExist = await this.propertiesRepository.findByTitle(title); if (propertyAlreadyExist) { throw new AppError('Property already exist!'); } + const { + public_place, + district, + complement, + city, + uf + } = await this.cepRepository.recoverAddress(cep); + const property = await this.propertiesRepository.create({ title, description, From 8951c1afc7b17529fc47bd664f1accd920e95c8e Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:43:03 -0300 Subject: [PATCH 077/129] feat: :sparkles: Adicionando um novo DTO para o useCase de criar a propriedade --- .../modules/properties/dtos/IRequestPropertyDTO.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 backend/src/modules/properties/dtos/IRequestPropertyDTO.ts diff --git a/backend/src/modules/properties/dtos/IRequestPropertyDTO.ts b/backend/src/modules/properties/dtos/IRequestPropertyDTO.ts new file mode 100644 index 0000000..acde5ce --- /dev/null +++ b/backend/src/modules/properties/dtos/IRequestPropertyDTO.ts @@ -0,0 +1,12 @@ +interface IRequestPropertyDTO { + id?: string; + title: string; + description: string; + value: number; + area: number; + address: string; + house_number: number; + cep: number; +} + +export { IRequestPropertyDTO } \ No newline at end of file From ddb86fae6a9482597e5d24252f1d71b1b8ccd485 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:44:33 -0300 Subject: [PATCH 078/129] feat: :sparkles: Adicionando um dto para os parametros de query --- .../modules/properties/dtos/IFindParamsDTO.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/src/modules/properties/dtos/IFindParamsDTO.ts diff --git a/backend/src/modules/properties/dtos/IFindParamsDTO.ts b/backend/src/modules/properties/dtos/IFindParamsDTO.ts new file mode 100644 index 0000000..15af302 --- /dev/null +++ b/backend/src/modules/properties/dtos/IFindParamsDTO.ts @@ -0,0 +1,18 @@ +interface IFindParamsDTO { + title?: string; + description?: string; + value?: number; + area?: number; + address?: string; + public_place?: string; + house_number?: number; + complement?: string; + district?: string; + cep?: number; + city?: string; + uf?: string; + page: number; + limit: number; +} + +export { IFindParamsDTO } \ No newline at end of file From 944f16cf0a8f79df0e285d8f43d5c5ba0927fc81 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:45:02 -0300 Subject: [PATCH 079/129] =?UTF-8?q?refactor:=20:recycle:=20Retirando=20as?= =?UTF-8?q?=20propriedades=20de=20autentica=C3=A7=C3=A3o=20das=20rotas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../properties/infra/http/routes/properties.routes.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/modules/properties/infra/http/routes/properties.routes.ts b/backend/src/modules/properties/infra/http/routes/properties.routes.ts index 2b2f58a..5c56a03 100644 --- a/backend/src/modules/properties/infra/http/routes/properties.routes.ts +++ b/backend/src/modules/properties/infra/http/routes/properties.routes.ts @@ -14,10 +14,10 @@ const showPropertyByIdController = new ShowPropertyByIdController(); const updatePropertyController = new UpdatePropertyController(); const deletePropertyByIdController = new DeletePropertyByIdController(); -propertiesRoutes.post('/', ensureAuthenticated, createPropertyController.handle); -propertiesRoutes.get('/', ensureAuthenticated, listPropertyController.handle); -propertiesRoutes.get('/:id', ensureAuthenticated, showPropertyByIdController.handle); -propertiesRoutes.put('/:id', ensureAuthenticated, updatePropertyController.handle); -propertiesRoutes.delete('/:id', ensureAuthenticated, deletePropertyByIdController.handle); +propertiesRoutes.post('/', createPropertyController.handle); +propertiesRoutes.get('/', listPropertyController.handle); +propertiesRoutes.get('/:id', showPropertyByIdController.handle); +propertiesRoutes.put('/:id', updatePropertyController.handle); +propertiesRoutes.delete('/:id', deletePropertyByIdController.handle); export { propertiesRoutes } \ No newline at end of file From 868b9314375d1b770ff2de6c9eda4a49cf826ea6 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:45:30 -0300 Subject: [PATCH 080/129] =?UTF-8?q?feat:=20:sparkles:=20Adicionando=20pagi?= =?UTF-8?q?na=C3=A7=C3=A3o=20na=20listagem=20dos=20imoveis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../typeorm/repositories/PropertiesRepository.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts b/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts index c898784..ff74b45 100644 --- a/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts +++ b/backend/src/modules/properties/infra/typeorm/repositories/PropertiesRepository.ts @@ -1,4 +1,5 @@ import { ICreatePropertyDTO } from "@modules/properties/dtos/ICreatePropertyDTO"; +import { IFindParamsDTO } from "@modules/properties/dtos/IFindParamsDTO"; import { IPropertiesRepository } from "@modules/properties/repositories/IPropertiesRepository"; import { getRepository, Repository } from "typeorm"; import { Property } from "../entities/Property"; @@ -57,8 +58,15 @@ class PropertiesRepository implements IPropertiesRepository { return property; } - public async find(): Promise { - return this.ormRepository.find(); + public async find({ page, limit }: IFindParamsDTO): Promise<[properties: Property[], totalCount: number]> { + return await this.ormRepository.findAndCount({ + order: { + created_at: 'DESC', + title: 'ASC' + }, + take: limit, + skip: (page - 1) * limit + }); } public async delete(id: string): Promise { const deleted = await this.ormRepository.delete(id); From e1365eafc87ea6812d28e0038d8a37afccdc3724 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:46:00 -0300 Subject: [PATCH 081/129] refactor: :recycle: REfatorando a interface para receber os filtros --- .../modules/properties/repositories/IPropertiesRepository.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/modules/properties/repositories/IPropertiesRepository.ts b/backend/src/modules/properties/repositories/IPropertiesRepository.ts index 10e5dae..27c9cdb 100644 --- a/backend/src/modules/properties/repositories/IPropertiesRepository.ts +++ b/backend/src/modules/properties/repositories/IPropertiesRepository.ts @@ -1,11 +1,12 @@ import { ICreatePropertyDTO } from "../dtos/ICreatePropertyDTO"; +import { IFindParamsDTO } from "../dtos/IFindParamsDTO"; import { Property } from "../infra/typeorm/entities/Property"; interface IPropertiesRepository { create(data: ICreatePropertyDTO): Promise; findById(id: string): Promise; findByTitle(title: string): Promise; - find(): Promise; + find({ page, limit }: IFindParamsDTO): Promise<[properties: Property[], totalCount: number ]>; update(property: Property): Promise; delete(id: string): Promise; } From e88da834af0f3f6746caf2823e5662f6e4c95ada Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:46:27 -0300 Subject: [PATCH 082/129] =?UTF-8?q?feat:=20:sparkles:=20Colocando=20pagina?= =?UTF-8?q?=C3=A7=C3=A3o=20no=20repositorio=20fake?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositories/fakes/FakesPropertiesRepository.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts b/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts index 9ceccf5..5550e90 100644 --- a/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts +++ b/backend/src/modules/properties/repositories/fakes/FakesPropertiesRepository.ts @@ -1,3 +1,4 @@ +import { IFindParamsDTO } from "@modules/properties/dtos/IFindParamsDTO"; import { ICreatePropertyDTO } from "../../dtos/ICreatePropertyDTO"; import { Property } from "../../infra/typeorm/entities/Property"; import { IPropertiesRepository } from '../IPropertiesRepository'; @@ -51,7 +52,7 @@ class FakePropertiesRepository implements IPropertiesRepository { } public async update( - data: Property + data: Property ): Promise { const property = new Property(); const propertyIndex = this.properties.findIndex( @@ -66,8 +67,14 @@ class FakePropertiesRepository implements IPropertiesRepository { return property; } - public async find(): Promise { - return this.properties; + public async find({ page, limit }: IFindParamsDTO): Promise<[properties: Property[], totalCount: number ]> { + const pageStart = (Number(page) - 1) * Number(limit); + const pageEnd = pageStart + Number(limit); + const properties = this.properties.slice(pageStart, pageEnd); + return [ + properties, + this.properties.length + ] } public async delete(id: string): Promise { From 3da0f0adfff755cc2759616f817f58edca340d78 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:46:56 -0300 Subject: [PATCH 083/129] test: :white_check_mark: Alterando testes da listagem para retornar o total --- .../useCases/listProperties/listPropertiesController.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts index 87b83a2..05d5879 100644 --- a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.spec.ts @@ -46,6 +46,10 @@ describe('List Properties Controller', () => { const response = await request(app) .get(`/properties`) + .query({ + page: 1, + limit: 8 + }) .set({ Authorization: `Bearer ${token}`, }); From 3215fa4512e89bd9b6f136c0e5511dfd2090e019 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:47:15 -0300 Subject: [PATCH 084/129] feat: :sparkles: Alterando controller da listagem para retornar o total --- .../listProperties/listPropertiesController.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts index 5706424..2aac0a1 100644 --- a/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesController.ts @@ -4,11 +4,21 @@ import { ListPropertiesUseCase } from './listPropertiesUseCase'; class ListPropertyController { async handle(request: Request, response: Response): Promise { + const { page, limit } = request.query; const listPropertiesUseCase = container.resolve(ListPropertiesUseCase); - const properties = await listPropertiesUseCase.execute(); + const { + "0": properties, + "1": totalCount + } = await listPropertiesUseCase.execute({ + page: Number(page), + limit: Number(limit) + }); - return response.status(200).json(properties); + response.setHeader('x-total-count', totalCount); + return response.status(200).json({ + properties: properties + }); } } From 72a43b7c78146a861b96fd9585a4200115c0c4a1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:47:28 -0300 Subject: [PATCH 085/129] test: :white_check_mark: Alterando testes da listagem para retornar o total --- .../useCases/listProperties/listPropertiesUseCase.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts index 57136a5..aa5ec23 100644 --- a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.spec.ts @@ -26,8 +26,8 @@ describe('List Properties', () => { uf: 'FM', }); - const properties = await listPropertiesUseCase.execute(); - - expect(properties).toEqual([property]); + const properties = await listPropertiesUseCase.execute({ page: 1, limit: 8 }); + + expect(properties).toEqual([[property], 1]); }); }); \ No newline at end of file From 1b2e7ca7e01194ec8c596aeddcaeb086c1454202 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:47:43 -0300 Subject: [PATCH 086/129] feat: :sparkles: Alterando useCase da listagem para retornar o total --- .../useCases/listProperties/listPropertiesUseCase.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts index a13c411..f521aaa 100644 --- a/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts +++ b/backend/src/modules/properties/useCases/listProperties/listPropertiesUseCase.ts @@ -1,6 +1,7 @@ import { inject, injectable } from 'tsyringe'; import { Property } from '@modules/properties/infra/typeorm/entities/Property'; import { IPropertiesRepository } from '@modules/properties/repositories/IPropertiesRepository'; +import { IFindParamsDTO } from '@modules/properties/dtos/IFindParamsDTO'; @injectable() class ListPropertiesUseCase { @@ -9,8 +10,10 @@ class ListPropertiesUseCase { private propertiesRepository: IPropertiesRepository, ) { } - async execute(): Promise { - const properties = await this.propertiesRepository.find(); + async execute( + { page, limit }: IFindParamsDTO + ): Promise<[properties: Property[], totalCount: number ]> { + const properties = await this.propertiesRepository.find({ page, limit }); return properties; } From 58884604e7f722d7d28fa395e626ab16d8501e4b Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 15:48:24 -0300 Subject: [PATCH 087/129] feat: :sparkles: Atualizando o useCase de atualizar o imovel --- .../properties/useCases/updateProperty/updatePropertyUseCase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts index e66a512..6e541f5 100644 --- a/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts +++ b/backend/src/modules/properties/useCases/updateProperty/updatePropertyUseCase.ts @@ -31,7 +31,7 @@ class UpdatePropertyUseCase { if (!propertyExists) { throw new AppError('Property not exist!', 404); } - + const property = await this.propertiesRepository.update({ id, title, From 5c4030d3859e56890e3a2156d7f04a571e6a7fc5 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:00:40 -0300 Subject: [PATCH 088/129] feat: :sparkles: Iniciando projeto --- frontend/.env | 1 + frontend/.gitignore | 35 +++++++++++++++++++++++++++++++++++ frontend/next-env.d.ts | 2 ++ frontend/package.json | 38 ++++++++++++++++++++++++++++++++++++++ frontend/tsconfig.json | 29 +++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 frontend/.env create mode 100644 frontend/.gitignore create mode 100644 frontend/next-env.d.ts create mode 100644 frontend/package.json create mode 100644 frontend/tsconfig.json diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..083c815 --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +NODE_ENV=development \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..f52567f --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn.lock* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..4d82acc --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "CertImoveis", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@chakra-ui/core": "^0.8.0", + "@chakra-ui/react": "^1.6.3", + "@emotion/react": "^11.4.0", + "@emotion/styled": "^11.3.0", + "@hookform/resolvers": "^2.6.0", + "apexcharts": "^3.27.1", + "axios": "^0.21.1", + "framer-motion": "^4.1.17", + "jwt-decode": "^3.1.2", + "next": "10.2.3", + "nookies": "^2.5.2", + "react": "17.0.2", + "react-apexcharts": "^1.3.9", + "react-dom": "17.0.2", + "react-hook-form": "^7.9.0", + "react-icons": "^4.2.0", + "react-query": "^3.18.1", + "yup": "^0.32.9" + }, + "devDependencies": { + "@types/faker": "^5.5.6", + "@types/node": "^15.12.2", + "@types/react": "^17.0.11", + "faker": "^5.5.3", + "miragejs": "^0.1.41", + "typescript": "^4.3.2" + } +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..35d51ea --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} From d16d4428619560dff3955c3f0d6a51cd66128d73 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:02:28 -0300 Subject: [PATCH 089/129] feat: :sparkles: Instalando o axios e instanciando basePath --- frontend/src/services/api.ts | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 frontend/src/services/api.ts diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts new file mode 100644 index 0000000..c0d971d --- /dev/null +++ b/frontend/src/services/api.ts @@ -0,0 +1,85 @@ +import axios, { AxiosError } from 'axios'; +import { parseCookies, setCookie } from 'nookies'; +import { signOut } from '../hooks/useAuth'; +import { AuthTokenError } from './errors/AuthTokenError'; + +let isRefreshing = false; +let failedRequestsQueue: any[] = []; + +export function setupAPIClient(context = undefined) { + let cookies = parseCookies(context); + + const api = axios.create({ + baseURL: 'http://localhost:3333', + headers: { + Authorization: `Bearer ${cookies['imoveis.token']}` + } + }); + + // api.interceptors.response.use(response => { + // return response; + // }, (error: AxiosError) => { + // if (error.response?.status === 401) { + // if (error.response.data?.code === 'token.expired') { + // cookies = parseCookies(context); + // const { 'nextauth.refreshToken': refreshToken } = cookies; + // const originalConfig = error.config; + // if (!isRefreshing) { + // isRefreshing = true; + + // api.post('/refresh', { + // refreshToken, + // }).then(response => { + // const { token } = response.data; + + // setCookie(context, 'nextauth.token', token, { + // maxAge: 60 * 60 * 24 * 30, // 30 days + // path: '/' + // }); + // setCookie(context, 'nextauth.refreshToken', response.data.refreshToken, { + // maxAge: 60 * 60 * 24 * 30, // 30 days + // path: '/' + // }); + // api.defaults.headers['Authorization'] = `Bearer ${token}`; + + // failedRequestsQueue.forEach(request => request.onSuccess(token)); + + // failedRequestsQueue = []; + // }).catch(() => { + // failedRequestsQueue.forEach(request => request.onFailure()); + + // failedRequestsQueue = []; + + // if (process.browser) { + // signOut(); + // } + // }).finally(() => { + // isRefreshing = false; + // }); + // } + // return new Promise((resolve, reject) => { + // failedRequestsQueue.push({ + // onSuccess: (token: string) => { + // originalConfig.headers['Authorization'] = `Bearer ${token}`; + + // resolve(api(originalConfig)); + // }, + // onFailure: (error: AxiosError) => { + // reject(error); + // } + // }) + // }) + // } else { + // if (process.browser) { + // signOut(); + // } else { + // return Promise.reject(new AuthTokenError()); + // } + // } + // } + + // return Promise.reject(error); + // }); + + return api; +} \ No newline at end of file From cc1c3094749eb2b49266cb893636fb0e39a7103f Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:03:10 -0300 Subject: [PATCH 090/129] feat: :sparkles: Criando instancia com os controllers --- frontend/src/pages/_app.tsx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 frontend/src/pages/_app.tsx diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx new file mode 100644 index 0000000..2519c12 --- /dev/null +++ b/frontend/src/pages/_app.tsx @@ -0,0 +1,31 @@ +import { ChakraProvider } from "@chakra-ui/react"; +import { AppProps } from "next/app"; +import { QueryClientProvider } from "react-query"; +import { ReactQueryDevtools } from "react-query/devtools"; +import { ModalProvider } from "../hooks/useModal"; +import { AuthProvider } from "../hooks/useAuth"; +import { SidebarDrawerProvider } from "../hooks/useSidebarDrawer"; +import { makeServer } from "../services/mirage"; +import { queryClient } from "../services/queryClient"; +import { theme } from "../styles/theme"; + +// if (process.env.NODE_ENV === 'development') { +// makeServer(); +// } + +function MyApp({ Component, pageProps }: AppProps) { + return ( + + + + + + + + + + + ); +} + +export default MyApp; From d92f7c29e5c61fe367bf9166072779a68db85c66 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:03:39 -0300 Subject: [PATCH 091/129] feat: :sparkles: Instanciando as fonts do google --- frontend/src/pages/_document.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 frontend/src/pages/_document.tsx diff --git a/frontend/src/pages/_document.tsx b/frontend/src/pages/_document.tsx new file mode 100644 index 0000000..f38933b --- /dev/null +++ b/frontend/src/pages/_document.tsx @@ -0,0 +1,20 @@ +import Document, { Html, Head, Main, NextScript } from 'next/document'; + +export default class MyDocument extends Document { + render() { + return ( + + + + + + {/* */} + + +
+ + + + ); + } +} \ No newline at end of file From bfef5dbda4bdc59210c08e9b8e9ab6f033ca39f5 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:04:01 -0300 Subject: [PATCH 092/129] feat: :sparkles: Instanciando o axios para o context --- frontend/src/services/apiClient.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frontend/src/services/apiClient.ts diff --git a/frontend/src/services/apiClient.ts b/frontend/src/services/apiClient.ts new file mode 100644 index 0000000..3463de1 --- /dev/null +++ b/frontend/src/services/apiClient.ts @@ -0,0 +1,3 @@ +import { setupAPIClient } from './api'; + +export const api = setupAPIClient(); \ No newline at end of file From 18e99628a2bf0598b470aa4eb87f88fda4559ea4 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:05:14 -0300 Subject: [PATCH 093/129] feat: :sparkles: Criando o client query para o react query --- frontend/src/services/queryClient.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frontend/src/services/queryClient.ts diff --git a/frontend/src/services/queryClient.ts b/frontend/src/services/queryClient.ts new file mode 100644 index 0000000..1adf624 --- /dev/null +++ b/frontend/src/services/queryClient.ts @@ -0,0 +1,3 @@ +import { QueryClient } from "react-query"; + +export const queryClient = new QueryClient(); \ No newline at end of file From f8279bce285fc71cf9f6b6fbe51752151f4c8f9f Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:06:23 -0300 Subject: [PATCH 094/129] feat: :sparkles: Criando um banco fake com o miragejs --- frontend/src/services/mirage/index.ts | 112 ++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 frontend/src/services/mirage/index.ts diff --git a/frontend/src/services/mirage/index.ts b/frontend/src/services/mirage/index.ts new file mode 100644 index 0000000..acc68ec --- /dev/null +++ b/frontend/src/services/mirage/index.ts @@ -0,0 +1,112 @@ +import { createServer, Factory, Model, Response, ActiveModelSerializer } from 'miragejs'; +import faker from 'faker'; + +type Property = { + id: string; + title: string; + description: string; + value: number; + area: number; + address: string; + public_place: string; + house_number: number; + complement: string; + district: string; + cep: number; + city: string; + uf: string; + created_at: Date; +}; + +export function makeServer() { + const server = createServer({ + serializers: { + application: ActiveModelSerializer, + }, + + models: { + property: Model.extend>({}) + }, + + factories: { + property: Factory.extend({ + id() { + return faker.datatype.uuid(); + }, + title(index: number) { + return `Title ${index}`; + }, + description() { + return faker.lorem.paragraph(); + }, + value(index: number) { + return faker.commerce.price(); + }, + area() { + return faker.finance.amount; + }, + address() { + return faker.address.streetAddress(); + }, + public_place() { + return faker.address.streetAddress(); + }, + house_number() { + return faker.finance.amount(); + }, + complement(index: string) { + return `Case ${index}`; + }, + district() { + return faker.address.secondaryAddress(); + }, + cep() { + return faker.finance.amount(); + }, + city() { + return faker.address.city(); + }, + uf() { + return faker.address.stateAbbr(); + }, + created_at() { + return faker.date.recent(10); + }, + }) + }, + + seeds(server) { + server.createList('property', 200); + }, + + routes() { + this.namespace = 'api'; + this.timing = 750; + + this.get('/properties', function (schema, request) { + const { page = 1, per_page = 10 } = request.queryParams; + + const total = schema.all('property').length; + + const pageStart = (Number(page) - 1) * Number(per_page); + const pageEnd = pageStart + Number(per_page); + + const properties = this.serialize(schema.all('property')) + .properties.slice(pageStart, pageEnd); + + return new Response( + 200, + { 'x-total-count': String(total) }, + { properties } + ); + }); + this.get('/properties/:id'); + this.post('/properties'); + + this.namespace = ''; + this.passthrough(); + } + }); + + return server; +} \ No newline at end of file From e6d4deae84e68e91fe45a149a5ff7adf85f41c8b Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:07:36 -0300 Subject: [PATCH 095/129] style: :lipstick: Criando um arquivo theme para as cores --- frontend/src/styles/theme.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/styles/theme.ts diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts new file mode 100644 index 0000000..04a69e5 --- /dev/null +++ b/frontend/src/styles/theme.ts @@ -0,0 +1,30 @@ +import { extendTheme } from '@chakra-ui/react'; + +export const theme = extendTheme({ + colors: { + gray: { + "900": "#181B23", + "800": "#1F2029", + "700": "#353646", + "600": "#4B4D63", + "500": "#616480", + "400": "#797D9A", + "300": "#9699B0", + "200": "#B3B5C6", + "100": "#D1D2DC", + "50": "#EEEEF2", + } + }, + fonts: { + heading: 'Poppins', + body: 'Poppins' + }, + styles: { + global: { + body: { + bg: 'gray.900', + color: 'gray.50' + } + } + } +}); \ No newline at end of file From 59aae36398237404561b435adf9e773073c53d99 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:07:58 -0300 Subject: [PATCH 096/129] =?UTF-8?q?feat:=20:sparkles:=20Criando=20arquivo?= =?UTF-8?q?=20de=20anima=C3=A7=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/styles/animation.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/styles/animation.ts diff --git a/frontend/src/styles/animation.ts b/frontend/src/styles/animation.ts new file mode 100644 index 0000000..d0f04e5 --- /dev/null +++ b/frontend/src/styles/animation.ts @@ -0,0 +1,30 @@ +import { Flex, FlexProps, Stack, StackProps, GridProps, Grid, Box, BoxProps, TextProps, Text } from "@chakra-ui/react"; +import { motion } from 'framer-motion' +import { Button, ButtonProps } from '../components/Button'; + +export const container = { + hidden: { opacity: 1, scale: 0 }, + visible: { + opacity: 1, + scale: 1, + transition: { + delayChildren: 0.3, + staggerChildren: 0.2 + } + } +}; + +export const item = { + hidden: { y: -30, opacity: 0 }, + visible: { + y: 0, + opacity: 1 + } +}; + +export const MotionFlex = motion(Flex); +export const MotionText = motion(Text); +export const MotionBox = motion(Box); +export const MotionGrid = motion(Grid) +export const MotionStack = motion(Stack); +export const MotionButton = motion(Button); \ No newline at end of file From 2d0e9b8dab87e943757093b823f9f622935dcc5a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:13:09 -0300 Subject: [PATCH 097/129] feat: :sparkles: Criandon o componente de activelink --- frontend/src/components/ActiveLink.tsx | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 frontend/src/components/ActiveLink.tsx diff --git a/frontend/src/components/ActiveLink.tsx b/frontend/src/components/ActiveLink.tsx new file mode 100644 index 0000000..9e9c88c --- /dev/null +++ b/frontend/src/components/ActiveLink.tsx @@ -0,0 +1,77 @@ +import { Box, Flex, Text } from '@chakra-ui/react'; +import Link, { LinkProps } from 'next/link'; +import { useRouter } from 'next/router'; +import { cloneElement, ReactElement } from 'react'; +import { item, MotionBox } from '../styles/animation'; + +interface ActiveLinkProps extends LinkProps { + children: ReactElement; + shouldMatchExactHref?: boolean; +} + +export function ActiveLink({ + children, + shouldMatchExactHref = false, + ...rest +}: ActiveLinkProps) { + const { asPath } = useRouter(); + let isActive = false; + + if (shouldMatchExactHref && (asPath === rest.href || asPath === rest.as)) { + isActive = true; + } + + if (!shouldMatchExactHref && + (asPath.startsWith(String(rest.href)) || + asPath.startsWith(String(rest.as)) + )) { + isActive = true; + } + + if (isActive) { + return ( + + + + + {cloneElement(children, { + _hover: { color: 'white', transition: 'color 0.2s' }, + color: 'white', + fontWeight: 'bold', + })} + + + + + + ); + } + + return ( + + + + + {cloneElement(children, { + _hover: { color: 'white', transition: 'color 0.2s' }, + color: 'white', + fontWeight: 'bold', + })} + + + + + ); +} \ No newline at end of file From 24a582a4c5eccdfc4f6c53ef88282cfd78933f90 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:13:45 -0300 Subject: [PATCH 098/129] feat: :sparkles: Criando o componente de button --- frontend/src/components/Button.tsx | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/src/components/Button.tsx diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx new file mode 100644 index 0000000..35303cd --- /dev/null +++ b/frontend/src/components/Button.tsx @@ -0,0 +1,33 @@ +import { Button as ChakraButton, ButtonProps as ChakraButtonProps, Icon, Text } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { ElementType } from 'react'; + +export interface ButtonProps extends ChakraButtonProps { + title: string; + color: string; + icon: ElementType; + handle?: () => void; +} + +const MotionButton = motion(ChakraButton); + +export function Button({ title, color, icon, handle, ...rest }: ButtonProps) { + return ( + + + {title} + + ); +} \ No newline at end of file From f8f0d5c4b4e94b5b26d9f756fb7d6f3789d4c7e1 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:14:04 -0300 Subject: [PATCH 099/129] feat: :sparkles: Criando o componente de can --- frontend/src/components/Can.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 frontend/src/components/Can.tsx diff --git a/frontend/src/components/Can.tsx b/frontend/src/components/Can.tsx new file mode 100644 index 0000000..9fe6ac7 --- /dev/null +++ b/frontend/src/components/Can.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from "react"; +import { useCan } from "../hooks/useCan"; + +interface CanProps { + children: ReactNode; + permissions?: string[]; + roles?: string[]; +} + +export function Can({ children, permissions, roles }: CanProps) { + const userCanSeeComponent = useCan({ + permissions, roles + }); + + if (!userCanSeeComponent) { + return null; + } + + return ( + <> + {children} + + ); +} \ No newline at end of file From 3fc3a79371c6b07335a7ec60f42b509256464bb6 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:14:29 -0300 Subject: [PATCH 100/129] feat: :sparkles: Criando o componente de create property --- .../src/components/Form/CreateProperty.tsx | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 frontend/src/components/Form/CreateProperty.tsx diff --git a/frontend/src/components/Form/CreateProperty.tsx b/frontend/src/components/Form/CreateProperty.tsx new file mode 100644 index 0000000..12bc414 --- /dev/null +++ b/frontend/src/components/Form/CreateProperty.tsx @@ -0,0 +1,189 @@ +import { Text, useToast } from '@chakra-ui/react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useEffect, useState } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { RiAddLine } from "react-icons/ri"; +import * as yup from 'yup'; +import { useModal } from '../../hooks/useModal'; +import { createProperty } from '../../services/hooks/useProperty'; +import { container, item, MotionBox, MotionFlex, MotionGrid } from "../../styles/animation"; +import { Button } from "../Button"; +import { Modal } from '../Modal'; +import { Input } from "./Input"; + + +type PropertyFormData = { + title: string; + description: string; + value: number; + area: number; + address: string; + house_number: number; + cep: number; +} + +const propertyFormSchema = yup.object().shape({ + title: yup.string().required('O t铆tulo 茅 obrigat贸rio'), + description: yup.string().required('A descri莽茫o 茅 obrigat贸ria'), + value: yup.number().required('O valor 茅 obrigat贸rio'), + area: yup.number().required('A 谩rea 茅 obrigat贸ria'), + address: yup.string().required('O endere莽o 茅 obrigat贸rio'), + house_number: yup.number().required('O n煤mero resid锚ncial 茅 obrigat贸rio'), + cep: yup.number() + .required('O CEP 茅 obrigat贸rio') + .min(8, 'O CEP deve conter 8 d铆gitos'), +}); + +export function FormCreateProperty() { + const [isValid, setIsValid] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [dataForm, setDataForm] = useState(); + const { onClose, onOpen } = useModal(); + const toast = useToast(); + + const { register, handleSubmit, formState } = useForm({ + resolver: yupResolver(propertyFormSchema) + }); + + const handleCreateProperty: SubmitHandler = async (data) => { + if (!isValid) { + setDataForm(data); + onOpen(); + } + } + + function handleSetValid() { + setIsLoading(true); + setIsValid(true); + } + + async function handleUseCreateProperty() { + try { + const { property } = await createProperty(dataForm); + + toast({ + title: "Im贸vel cadastrado! 馃コ", + description: `Cadastro do im贸vel "${property.title}" foi realizado com sucesso! 馃コ `, + position: "top-right", + status: "success", + duration: 5000, + isClosable: true, + }); + window.location.reload() + } catch (error) { + toast({ + title: "Falha ao cadastrar o im贸vel! 馃槩", + description: `${error} 馃槩`, + position: "top-right", + status: "error", + duration: 5000, + isClosable: true, + }); + } + } + + useEffect(() => { + if (isValid) { + handleUseCreateProperty(); + onClose(); + setIsValid(false); + setIsLoading(false); + } + }, [isValid]); + + return ( + <> + + + + + + + + + + + + + + + + + + Todos os campos s茫o obrigat贸rios! + + + ); + } + + return ( + + + + ); +} \ No newline at end of file From df615315e1177b93e359f2738ec3dcc44e22ff37 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:17:52 -0300 Subject: [PATCH 111/129] feat: :sparkles: Criando o componente de sidebar --- frontend/src/components/Sidebar/index.tsx | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 frontend/src/components/Sidebar/index.tsx diff --git a/frontend/src/components/Sidebar/index.tsx b/frontend/src/components/Sidebar/index.tsx new file mode 100644 index 0000000..24763d2 --- /dev/null +++ b/frontend/src/components/Sidebar/index.tsx @@ -0,0 +1,35 @@ +import { Box, Drawer, DrawerCloseButton, DrawerHeader, DrawerContent, DrawerOverlay, useBreakpointValue, DrawerBody } from '@chakra-ui/react'; +import { useSidebarDrawer } from '../../hooks/useSidebarDrawer'; +import { SidebarNav } from './SidebarNav'; + +export function Sidebar() { + const { isOpen, onClose } = useSidebarDrawer(); + + const isDrawerSidebar = useBreakpointValue({ + base: true, + lg: false, + }); + + if (isDrawerSidebar) { + return ( + + + + + Navega莽茫o + + + + + + + + ); + } + + return ( + + + + ); +} \ No newline at end of file From 48a895381c3cc3b8af975e4da97b607f73b5718a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:18:03 -0300 Subject: [PATCH 112/129] feat: :sparkles: Criando o componente de navlink --- frontend/src/components/Sidebar/NavLink.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 frontend/src/components/Sidebar/NavLink.tsx diff --git a/frontend/src/components/Sidebar/NavLink.tsx b/frontend/src/components/Sidebar/NavLink.tsx new file mode 100644 index 0000000..2841a04 --- /dev/null +++ b/frontend/src/components/Sidebar/NavLink.tsx @@ -0,0 +1,21 @@ +import { Link as ChakraLink, Icon, Text, LinkProps as ChakraLinkProps } from "@chakra-ui/react"; +import { ElementType } from "react"; +import { ActiveLink } from '../ActiveLink'; +interface NavLinkProps extends ChakraLinkProps { + title: string; + icon: ElementType; + href: string +} + +export function NavLink({ title, icon, href, ...rest }: NavLinkProps) { + return ( + + + + + {title} + + + + ); +} \ No newline at end of file From a99e12890cc322b3774799d5f3e012e5115cda85 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:18:20 -0300 Subject: [PATCH 113/129] feat: :sparkles: Criando o componente de nav section --- .../src/components/Sidebar/NavSection.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 frontend/src/components/Sidebar/NavSection.tsx diff --git a/frontend/src/components/Sidebar/NavSection.tsx b/frontend/src/components/Sidebar/NavSection.tsx new file mode 100644 index 0000000..fb8e730 --- /dev/null +++ b/frontend/src/components/Sidebar/NavSection.tsx @@ -0,0 +1,21 @@ +import { Box, Text, Stack, Link, Icon } from '@chakra-ui/react'; +import { ReactNode } from 'react'; +import { RiContactsLine, RiDashboardLine } from 'react-icons/ri'; + +interface NavSectionProps { + title: string; + children: ReactNode +} + +export function NavSection({ title, children }: NavSectionProps) { + return ( + + + {title} + + + {children} + + + ); +} \ No newline at end of file From 7e70d7608cf44e728a5eefe6563e3b4b8c5f943a Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:18:31 -0300 Subject: [PATCH 114/129] feat: :sparkles: Criando o componente de sidebarnav --- .../src/components/Sidebar/SidebarNav.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 frontend/src/components/Sidebar/SidebarNav.tsx diff --git a/frontend/src/components/Sidebar/SidebarNav.tsx b/frontend/src/components/Sidebar/SidebarNav.tsx new file mode 100644 index 0000000..a7c4c9f --- /dev/null +++ b/frontend/src/components/Sidebar/SidebarNav.tsx @@ -0,0 +1,23 @@ +import { Stack } from '@chakra-ui/react'; +import { RiContactsLine, RiDashboardLine, RiGitMergeLine, RiInputMethodLine } from 'react-icons/ri'; +import { NavLink } from './NavLink'; +import { NavSection } from './NavSection'; + +export function SidebarNav() { + return ( + + + + + + + + + + + + ); +} \ No newline at end of file From 77b67df31042188d1dfaee8777c2c65f989a249b Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:23:14 -0300 Subject: [PATCH 115/129] feat: :sparkles: Criando o context de auth --- frontend/src/hooks/useAuth.tsx | 144 +++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 frontend/src/hooks/useAuth.tsx diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx new file mode 100644 index 0000000..f3dd705 --- /dev/null +++ b/frontend/src/hooks/useAuth.tsx @@ -0,0 +1,144 @@ +import { useState } from "react"; +import { useToast } from '@chakra-ui/react'; +import { createContext, useContext, ReactNode } from "react"; +import Router from 'next/router'; +import { setCookie, parseCookies, destroyCookie } from 'nookies'; +import { api } from "../services/apiClient"; +import { useEffect } from "react"; + +interface User { + email: string; + permissions: string[]; + roles: string[]; +} + +interface SignInCredentials { + email: string; + password: string; +} + +interface AuthContextData { + signIn: (credentials: SignInCredentials) => Promise; + signOut: () => void; + user: User; + isAuthenticated: boolean; +} + +interface AuthProviderProps { + children: ReactNode; +} + +const AuthContext = createContext({} as AuthContextData); + +let authChannel: BroadcastChannel; + +export function signOut() { + destroyCookie(undefined, 'imoveis.token', { path: '/' }); + // destroyCookie(undefined, 'nextauth.refreshToken', { path: '/' }); + + authChannel.postMessage('signOut'); + + Router.push('/'); +} + +export function AuthProvider({ children }: AuthProviderProps) { + const [user, setUser] = useState(); + const toast = useToast(); + const isAuthenticated = !!user; + + useEffect(() => { + authChannel = new BroadcastChannel('auth'); + + authChannel.onmessage = (message) => { + switch (message.data) { + case 'signOut': + signOut(); + authChannel.close(); + break; + case 'signIn': + Router.push('/home'); + authChannel.close(); + break; + + default: + break; + } + } + }, []); + + // useEffect(() => { + // const { 'imoveis.token': token } = parseCookies() + + // if (token) { + // api.get('/me') + // .then(response => { + // const { email, permissions, roles } = response.data + + // setUser({ email, permissions, roles }) + // }) + // .catch(() => { + // signOut(); + // }) + // } + // }, []) + + async function signIn({ email, password }: SignInCredentials) { + try { + const response = await api.post('/sessions', { + email, + password + }); + + // const { token, refreshToken, permissions, roles } = response.data; + const { token } = response.data; + setCookie(undefined, 'imoveis.token', token, { + maxAge: 60 * 60 * 24 * 30, // 30 days + path: '/' + }); + // setCookie(undefined, 'nextauth.refreshToken', refreshToken, { + // maxAge: 60 * 60 * 24 * 30, // 30 days + // path: '/' + // }); + + // setUser({ + // email, + // // permissions, + // // roles + // }); + + api.defaults.headers['Authorization'] = `Bearer ${token}`; + + toast({ + title: "Login Efetuado! 馃", + description: "Login Efetuado com Sucesso! 馃檰", + status: "success", + position: "top-right", + duration: 5000, + isClosable: true, + }) + + Router.push('/home'); + + authChannel.postMessage('signIn'); + } catch (error) { + toast({ + title: "Erro 馃槩!", + description: "Ocorreu um erro ao efetuar o login, cheque suas credenciais! 馃檹", + status: "error", + position: "top-right", + duration: 5000, + isClosable: true, + }) + } + } + + return ( + + {children} + + ); +} + +export function useAuth() { + return useContext(AuthContext); +} \ No newline at end of file From 20577d5f8fa526bdef4a047678e7b36517494d0f Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:23:25 -0300 Subject: [PATCH 116/129] feat: :sparkles: Criando o context de can --- frontend/src/hooks/useCan.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 frontend/src/hooks/useCan.ts diff --git a/frontend/src/hooks/useCan.ts b/frontend/src/hooks/useCan.ts new file mode 100644 index 0000000..738a31c --- /dev/null +++ b/frontend/src/hooks/useCan.ts @@ -0,0 +1,23 @@ +import { validateUserPermissions } from "../utils/validateUserPermissions"; +import { useAuth } from "./useAuth"; + +interface UseCanParams { + permissions?: string[]; + roles?: string[]; +} + +export function useCan({ permissions, roles }: UseCanParams) { + const { user, isAuthenticated } = useAuth(); + + if (!isAuthenticated) { + return false; + } + + const userHasValidPermissions = validateUserPermissions({ + user, + permissions, + roles + }); + + return userHasValidPermissions; +} \ No newline at end of file From 6c94bc7c7f9118bc960ab82d6227e54131f0efbb Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:23:35 -0300 Subject: [PATCH 117/129] feat: :sparkles: Criando o context de modal --- frontend/src/hooks/useModal.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 frontend/src/hooks/useModal.tsx diff --git a/frontend/src/hooks/useModal.tsx b/frontend/src/hooks/useModal.tsx new file mode 100644 index 0000000..b3f143f --- /dev/null +++ b/frontend/src/hooks/useModal.tsx @@ -0,0 +1,24 @@ +import { useDisclosure, UseDisclosureReturn } from '@chakra-ui/react'; +import { ReactNode, useContext, createContext } from 'react'; + +interface ModalProps { + children: ReactNode; +} + +type ModalContextData = UseDisclosureReturn; + +const ModalContext = createContext({} as ModalContextData); + +export function ModalProvider({ children }: ModalProps) { + const disclosure = useDisclosure(); + + return ( + + {children} + + ); +} + +export function useModal() { + return useContext(ModalContext); +} \ No newline at end of file From 748b233cb79f0746594cb0a795f3c0334529dda3 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:23:49 -0300 Subject: [PATCH 118/129] feat: :sparkles: Criando o context de sidebar --- frontend/src/hooks/useSidebarDrawer.tsx | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/hooks/useSidebarDrawer.tsx diff --git a/frontend/src/hooks/useSidebarDrawer.tsx b/frontend/src/hooks/useSidebarDrawer.tsx new file mode 100644 index 0000000..0cb770e --- /dev/null +++ b/frontend/src/hooks/useSidebarDrawer.tsx @@ -0,0 +1,30 @@ +import { useDisclosure, UseDisclosureReturn } from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { ReactNode, useEffect, useContext, createContext } from "react"; + +interface SidebarDrawerProps { + children: ReactNode; +} + +type SidebarDrawerContextData = UseDisclosureReturn; + +const SidebarDrawerContext = createContext({} as SidebarDrawerContextData); + +export function SidebarDrawerProvider({ children }: SidebarDrawerProps) { + const disclosure = useDisclosure(); + const router = useRouter(); + + useEffect(() => { + disclosure.onClose(); + }, [router.asPath]); + + return ( + + {children} + + ); +} + +export function useSidebarDrawer() { + return useContext(SidebarDrawerContext); +} \ No newline at end of file From f110e02aa8c23e62b6e2ee5cb81016303210efe6 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:24:06 -0300 Subject: [PATCH 119/129] feat: :sparkles: Criando o context de property --- frontend/src/services/hooks/useProperty.ts | 191 +++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 frontend/src/services/hooks/useProperty.ts diff --git a/frontend/src/services/hooks/useProperty.ts b/frontend/src/services/hooks/useProperty.ts new file mode 100644 index 0000000..22cd04e --- /dev/null +++ b/frontend/src/services/hooks/useProperty.ts @@ -0,0 +1,191 @@ +import { useQuery, UseQueryOptions, UseQueryResult } from "react-query"; +import { formatAmountToCurrencyPTBR } from "../../utils/formatAmountToCurrencyPTBR"; +import { api } from "../apiClient"; + +interface Property { + id: string; + title: string; + description: string; + value: number; + area: number; + address: string; + public_place: string; + house_number: number; + complement: string; + district: string; + cep: number; + city: string; + uf: string; + created_at: Date; +} + +interface PropertyRequest { + title: string; + description: string; + value: number; + area: number; + address: string; + house_number: number; + cep: number; +} + +type PropertyUpdateRequest = { + id?: string; + title: string; + description: string; + value: number; + area: number; + address: string; + public_place: string; + house_number: number; + complement: string; + district: string; + cep: number; + city: string; + uf: string; +} + +interface GetPropertiesResponse { + properties: Property[]; + totalCount: number; +} + +interface PostPropertyResponse { + property: Property; +} + +export async function getProperties(page: number): Promise { + const { data, headers } = await api.get('/properties', { + params: { + page, + limit: 8 + } + }); + + const properties = data.properties.map((property: Property) => { + return { + id: property.id, + title: property.title, + description: property.description, + value: formatAmountToCurrencyPTBR(property.value), + area: property.area, + address: property.address, + public_place: property.public_place, + house_number: property.house_number, + complement: property.complement, + district: property.district, + cep: property.cep, + city: property.city, + uf: property.uf, + created_at: new Date(property.created_at).toLocaleDateString('pt-BR', { + day: '2-digit', + month: 'long', + year: 'numeric' + }) + } + }); + + return { + properties, + totalCount: Number(headers['x-total-count']) + }; +} + +export async function getPropertyById(id: string): Promise { + const { data } = await api.get(`/properties/${id}`); + + data.value = formatAmountToCurrencyPTBR(data.value); + data.created_at = new Date(data.created_at).toLocaleDateString('pt-BR', { + day: '2-digit', + month: 'long', + year: 'numeric' + }); + + return data; +} + +export async function deletePropertyById(id: string): Promise { + const { data } = await api.delete(`/properties/${id}`); + + return data; +} + +export async function updatePropertyById({ + id, + title, + description, + area, + address, + cep, + house_number, + value, + city, + complement, + district, + public_place, + uf +}: PropertyUpdateRequest): Promise { + const { data } = await api.put(`/properties/${id}`, { + title, + description, + area, + address, + cep, + house_number, + value, + city, + complement, + district, + public_place, + uf + }); + + data.value = formatAmountToCurrencyPTBR(data.value); + data.created_at = new Date(data.created_at).toLocaleDateString('pt-BR', { + day: '2-digit', + month: 'long', + year: 'numeric' + }); + + return { + property: data + }; +} + +export async function createProperty({ + title, + description, + area, + address, + cep, + house_number, + value +}: PropertyRequest): Promise { + const { data } = await api.post('/properties', { + title, + description, + area, + address, + cep, + house_number, + value + }); + + data.value = formatAmountToCurrencyPTBR(data.value); + data.created_at = new Date(data.created_at).toLocaleDateString('pt-BR', { + day: '2-digit', + month: 'long', + year: 'numeric' + }); + + return { + property: data + }; +} + +export function useProperties(page: number, options: UseQueryOptions) { + return useQuery(['properties', page], () => getProperties(page), { + staleTime: 1000 * 10, // 10 minutos + ...options, + }) as UseQueryResult +} \ No newline at end of file From 25e2408dd7e479ddca2d51a5d3949f240ef86834 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:28:05 -0300 Subject: [PATCH 120/129] feat: :sparkles: Criando instancia de erro --- frontend/src/services/errors/AuthTokenError.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 frontend/src/services/errors/AuthTokenError.ts diff --git a/frontend/src/services/errors/AuthTokenError.ts b/frontend/src/services/errors/AuthTokenError.ts new file mode 100644 index 0000000..27e312a --- /dev/null +++ b/frontend/src/services/errors/AuthTokenError.ts @@ -0,0 +1,5 @@ +export class AuthTokenError extends Error { + constructor() { + super('Error with authentication token.'); + } +} \ No newline at end of file From 9aa1e4063582dbd27ec385e014327941cb031317 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:28:44 -0300 Subject: [PATCH 121/129] feat: :sparkles: Validando ssr para pagtina de visitantes --- frontend/src/utils/withSSRGuest.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 frontend/src/utils/withSSRGuest.ts diff --git a/frontend/src/utils/withSSRGuest.ts b/frontend/src/utils/withSSRGuest.ts new file mode 100644 index 0000000..b7538e8 --- /dev/null +++ b/frontend/src/utils/withSSRGuest.ts @@ -0,0 +1,19 @@ +import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next"; +import { parseCookies } from "nookies"; + +export function withSSRGuest

(fn: GetServerSideProps

): GetServerSideProps { + return async (context: GetServerSidePropsContext): Promise> => { + const cookies = parseCookies(context); + + if (cookies['imoveis.token']) { + return { + redirect: { + destination: '/home', + permanent: false + } + } + } + + return await fn(context); + } +} \ No newline at end of file From f7676c03996b4297f3d3c577ff259cecc2571190 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:29:02 -0300 Subject: [PATCH 122/129] feat: :sparkles: Validando ssr para pagtina de autenticados --- frontend/src/utils/withSSRAuth.ts | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 frontend/src/utils/withSSRAuth.ts diff --git a/frontend/src/utils/withSSRAuth.ts b/frontend/src/utils/withSSRAuth.ts new file mode 100644 index 0000000..51283a3 --- /dev/null +++ b/frontend/src/utils/withSSRAuth.ts @@ -0,0 +1,66 @@ +import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next'; +import { destroyCookie, parseCookies } from 'nookies'; +import decode from 'jwt-decode'; +import { AuthTokenError } from '../services/errors/AuthTokenError'; +import { validateUserPermissions } from './validateUserPermissions'; + +interface WithSSRAuthOptions { + permissions?: string[]; + roles?: string[]; +} + +export function withSSRAuth

( + fn: GetServerSideProps

, + options?: WithSSRAuthOptions +): GetServerSideProps { + return async (context: GetServerSidePropsContext): Promise> => { + const cookies = parseCookies(context); + const token = cookies['imoveis.token']; + + if (!token) { + return { + redirect: { + destination: '/', + permanent: false + } + } + } + + // if (options) { + // const user = decode<{ permissions: string[], roles: string[] }>(token); + + // const { permissions, roles } = options; + + // const userHasValidPermissions = validateUserPermissions({ + // user, + // permissions, + // roles + // }); + + // if(!userHasValidPermissions) { + // return { + // redirect: { + // destination: '/home', + // permanent: false + // } + // } + // } + // } + + // try { + return await fn(context); + // } catch (err) { + // if (err instanceof AuthTokenError) { + // destroyCookie(context, 'imoveis.token', { path: '/' }); + // // destroyCookie(context, 'nextauth.refreshToken', { path: '/' }); + + // return { + // redirect: { + // destination: '/', + // permanent: false + // } + // } + // } + // } + } +} \ No newline at end of file From 7184849a0b5f1acd296df7621f23c36212f00c79 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:29:23 -0300 Subject: [PATCH 123/129] =?UTF-8?q?feat:=20:sparkles:=20Criando=20sistema?= =?UTF-8?q?=20de=20permiss=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/validateUserPermissions.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 frontend/src/utils/validateUserPermissions.ts diff --git a/frontend/src/utils/validateUserPermissions.ts b/frontend/src/utils/validateUserPermissions.ts new file mode 100644 index 0000000..46d6ea5 --- /dev/null +++ b/frontend/src/utils/validateUserPermissions.ts @@ -0,0 +1,40 @@ +interface User { + permissions: string[]; + roles: string[]; +} + +interface ValidateUserPermissionsParams { + user: User; + permissions?: string[]; + roles?: string[]; +} + +export function validateUserPermissions({ + user, + permissions, + roles +}: ValidateUserPermissionsParams) { + if (permissions?.length > 0) { + + const hasAllPermissions = permissions.every(permission => { + return user.permissions.includes(permission); + }); + + if (!hasAllPermissions) { + return false; + } + } + + if (roles?.length > 0) { + + const hasAllRoles = roles.some(role => { + return user.roles.includes(role); + }); + + if (!hasAllRoles) { + return false; + } + } + + return true; +} \ No newline at end of file From 3f4d03a8a3b5c379002bb685cd0be5487bbae0af Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:29:45 -0300 Subject: [PATCH 124/129] =?UTF-8?q?feat:=20:sparkles:=20Criando=20fun?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20formata=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/formatAmountToCurrencyPTBR.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 frontend/src/utils/formatAmountToCurrencyPTBR.ts diff --git a/frontend/src/utils/formatAmountToCurrencyPTBR.ts b/frontend/src/utils/formatAmountToCurrencyPTBR.ts new file mode 100644 index 0000000..0c0a218 --- /dev/null +++ b/frontend/src/utils/formatAmountToCurrencyPTBR.ts @@ -0,0 +1,6 @@ +export const formatAmountToCurrencyPTBR = (amount: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' + }).format(amount); +} \ No newline at end of file From a5fbb9f696dbb12598081d75d99228827bd141d5 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:32:12 -0300 Subject: [PATCH 125/129] feat: :sparkles: Criando tela de login --- frontend/src/pages/index.tsx | 123 +++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 frontend/src/pages/index.tsx diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx new file mode 100644 index 0000000..1083088 --- /dev/null +++ b/frontend/src/pages/index.tsx @@ -0,0 +1,123 @@ +import { Flex, Button, Stack, Text } from "@chakra-ui/react"; +import { useForm } from "react-hook-form"; +import Link from "next/link"; +import * as yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Input } from "../components/Form/Input"; +import { GetServerSidePropsContext } from "next"; +import { useAuth } from "../hooks/useAuth"; +import { withSSRGuest } from "../utils/withSSRGuest"; +import { useCallback } from "react"; +import { container, item, MotionBox, MotionFlex } from "../styles/animation"; + +type SignInFormData = { + email: string; + password: string; +}; + +const signInFormSchema = yup.object().shape({ + email: yup.string().required("E-mail 茅 obrigat贸rio").email("E-mail inv谩lido"), + password: yup + .string() + .required("Senha 茅 obrigat贸rio") + .min(6, "No m铆nimo 6 caracteres"), +}); + +export default function SignIn() { + const { register, handleSubmit, formState } = useForm({ + resolver: yupResolver(signInFormSchema), + }); + + const { signIn } = useAuth(); + + const handleSignIn = useCallback(async (data: SignInFormData) => { + try { + await signIn(data); + } catch (err) { + } + }, []); + + return ( + + + + + Im贸veis + + + + + + + + + + + + + + + + + + ); +} + +export const getServerSideProps = withSSRGuest( + async (context: GetServerSidePropsContext) => { + return { + props: {}, + }; + } +); From a8c826117fb4fbac062f54099fbe08ca2847caed Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:32:27 -0300 Subject: [PATCH 126/129] feat: :sparkles: Criando tela de signout --- frontend/src/pages/register.tsx | 139 ++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 frontend/src/pages/register.tsx diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx new file mode 100644 index 0000000..82af4bb --- /dev/null +++ b/frontend/src/pages/register.tsx @@ -0,0 +1,139 @@ +import { Flex, Button, Stack, Text, useToast } from "@chakra-ui/react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import * as yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Input } from "../components/Form/Input"; +import { GetServerSidePropsContext } from "next"; +import { useAuth } from "../hooks/useAuth"; +import { withSSRGuest } from "../utils/withSSRGuest"; +import { useCallback } from "react"; +import { container, item, MotionBox, MotionFlex } from "../styles/animation"; +import { api } from "../services/apiClient"; +import { useRouter } from "next/router"; + +type SignUpFormData = { + name: string; + email: string; + password: string; +}; + +const signInFormSchema = yup.object().shape({ + name: yup.string().required("Nome 茅 obrigat贸rio"), + email: yup.string().required("E-mail 茅 obrigat贸rio").email("E-mail inv谩lido"), + password: yup + .string() + .required("Senha 茅 obrigat贸rio") + .min(6, "No m铆nimo 6 caracteres"), +}); + +export default function Register() { + const { register, handleSubmit, formState } = useForm({ + resolver: yupResolver(signInFormSchema), + }); + + const toast = useToast(); + const router = useRouter(); + + const handleSignUp = useCallback(async (data: SignUpFormData) => { + try { + await api.post('/users', data) + + toast({ + title: "Cadastro Efetuado! 馃檹", + description: "Cadastro Efetuado com Sucesso! 馃檰", + status: "success", + position: "top-right" , + duration: 5000, + isClosable: true, + }) + + router.push('/') + + } catch (err) { + toast({ + title: "Erro ao cadastrar!", + description: "Erro ao efetuar o cadastro de usu谩rio! 馃槩", + status: "success", + position: "top-right" , + duration: 5000, + isClosable: true, + }) + } + }, []); + + return ( + + + + + Im贸veis + + + + + + + + + + + + + + ); +} + +export const getServerSideProps = withSSRGuest( + async (context: GetServerSidePropsContext) => { + return { + props: {}, + }; + } +); From a354cadad1f4ccfeb281e881443334be89f4a0a4 Mon Sep 17 00:00:00 2001 From: Josimar16 Date: Mon, 9 Aug 2021 17:32:42 -0300 Subject: [PATCH 127/129] feat: :sparkles: Criando tela de detalhes --- frontend/src/pages/home/[property_id].tsx | 259 ++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 frontend/src/pages/home/[property_id].tsx diff --git a/frontend/src/pages/home/[property_id].tsx b/frontend/src/pages/home/[property_id].tsx new file mode 100644 index 0000000..2a7d775 --- /dev/null +++ b/frontend/src/pages/home/[property_id].tsx @@ -0,0 +1,259 @@ +import { Box, Button as ChakraButton, Flex, Grid, Icon, Text, useToast } from "@chakra-ui/react"; +import { GetServerSidePropsContext } from "next"; +import Head from 'next/head'; +import Link from 'next/link'; +import Router from 'next/router'; +import { useState } from "react"; +import { FiTrash2 } from "react-icons/fi"; +import { MdChevronLeft } from 'react-icons/md'; +import { RiEditLine } from "react-icons/ri"; +import { Button } from "../../components/Button"; +import { FormProperty } from "../../components/Form/UpdateProperty"; +import { Header } from "../../components/Header"; +import { Modal } from "../../components/Modal"; +import { useModal } from "../../hooks/useModal"; +import { deletePropertyById, getPropertyById } from "../../services/hooks/useProperty"; +import { container, item, MotionFlex, MotionStack, MotionText } from "../../styles/animation"; +import { withSSRAuth } from "../../utils/withSSRAuth"; + + +type Property = { + id: string; + title: string; + description: string; + value: number; + area: number; + address: string; + public_place: string; + house_number: number; + complement: string; + district: string; + cep: number; + city: string; + uf: string; + created_at: Date; +}; + +interface PropertyProps { + property: Property; +} + +export default function DetailsProperty({ property }: PropertyProps) { + const [isLoading, setIsLoading] = useState(false); + const [isModalEdit, setIsModalEdit] = useState(false); + const { onClose, onOpen } = useModal(); + const toast = useToast(); + + async function handleDeleteById() { + setIsLoading(true); + try { + await deletePropertyById(property.id); + setIsLoading(false); + onClose(); + toast({ + title: "Im贸vel removido! 馃コ", + description: `Remo莽茫o do im贸vel "${property.title}" foi realizado com sucesso! 馃コ `, + position: "top-right", + status: "success", + duration: 5000, + isClosable: true, + }); + Router.push('/home'); + } catch (error) { + + toast({ + title: "Falha ao remover o im贸vel! 馃槩", + description: `${error} 馃槩`, + position: "top-right", + status: "error", + duration: 5000, + isClosable: true, + }); + } + } + + return ( + <> + + CertIm贸veis | {property.title} + + +

+ + + + + + + + + Voltar + + + + + Im贸vel: {property.title} + + + + +