├── .env.example ├── .env.test ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.controller.spec.ts ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── auth │ ├── auth.controller.spec.ts │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── auth.service.spec.ts │ ├── auth.service.ts │ └── dto │ │ ├── auth-forget.dto.ts │ │ ├── auth-login.dto.ts │ │ ├── auth-me.dto.ts │ │ ├── auth-register.dto.ts │ │ └── auth-reset.dto.ts ├── decorators │ ├── param-id.decorator.ts │ ├── roles.decorator.ts │ └── user.decorator.ts ├── enums │ └── role.enum.ts ├── file │ ├── file.module.ts │ ├── file.service.spec.ts │ └── file.service.ts ├── guards │ ├── auth.guard.ts │ └── role.guard.ts ├── interceptors │ └── log.interceptor.ts ├── main.ts ├── middlewares │ └── user-id-check.middleware.ts ├── templates │ └── forget.pug ├── testing │ ├── access-token.mock.ts │ ├── auth-forget-dto.mock.ts │ ├── auth-login-dto.mock.ts │ ├── auth-register-dto.mock.ts │ ├── auth-reset-dto.mock.ts │ ├── auth-service.mock.ts │ ├── create-user-dto.mock.ts │ ├── file-service.mock.ts │ ├── get-file-to-buffer.ts │ ├── get-photo.mock.ts │ ├── guard.mock.ts │ ├── jwt-payload.mock.ts │ ├── jwt-service.mock.ts │ ├── mailer-service.mock.ts │ ├── photo.png │ ├── reset-token.mock.ts │ ├── update-patch-user-dto.mock.ts │ ├── update-put-user-dto.mock.ts │ ├── user-entity-list.mock.ts │ ├── user-repository.mock.ts │ └── user-service.mock.ts ├── user │ ├── dto │ │ ├── create-user.dto.ts │ │ ├── update-patch-user.dto.ts │ │ └── update-put-user.dto.ts │ ├── entity │ │ └── user.entity.ts │ ├── user.controller.spec.ts │ ├── user.controller.ts │ ├── user.module.ts │ ├── user.service.spec.ts │ └── user.service.ts └── utils │ └── somar.ts ├── storage └── photos │ ├── photo-15.png │ ├── photo-6.png │ └── photo-test.png ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── typeorm ├── data-source.ts └── migrations └── 1672191057117-Migrate.ts /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="mysql://root:root@localhost:3306/api" 2 | JWT_SECRET="&O,H$2%U_M9kRu{u&@dxGrG+pwReQse" -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | ENV="test" 2 | 3 | JWT_SECRET="&O,H$2%U_M9kRu{u&@dxGrG+pwReQse" 4 | 5 | DB_USERNAME="root" 6 | DB_PASSWORD="root" 7 | DB_DATABASE="api-test" 8 | DB_HOST="localhost" 9 | DB_PORT="3306" -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "assets": [{ 7 | "include": "templates/**/*", 8 | "outDir": "dist/src" 9 | }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/src/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "pretest:e2e": "cross-env ENV=test npm run clear:db:test && cross-env ENV=test npm run migrate:up", 22 | "test:e2e": "cross-env ENV=test jest --config ./test/jest-e2e.json", 23 | "posttest:e2e": "cross-env ENV=test npm run clear:db:test", 24 | "typeorm:ts": "typeorm-ts-node-esm", 25 | "clear:db:test": "npm run typeorm:ts schema:drop -- -d ./typeorm/data-source.ts", 26 | "migrate:create": "npm run typeorm:ts migration:create -- ./typeorm/migrations/Migrate", 27 | "migrate:up": "npm run typeorm:ts migration:run -- -d ./typeorm/data-source.ts", 28 | "migrate:down": "npm run typeorm:ts migration:revert -- -d ./typeorm/data-source.ts", 29 | "preprod": "npm run format && npm run lint && npm test && npm run test:e2e", 30 | "prod": "npm run build" 31 | }, 32 | "dependencies": { 33 | "@nestjs-modules/mailer": "^1.8.1", 34 | "@nestjs/common": "^9.0.0", 35 | "@nestjs/config": "^2.2.0", 36 | "@nestjs/core": "^9.0.0", 37 | "@nestjs/jwt": "^9.0.0", 38 | "@nestjs/mapped-types": "^1.2.0", 39 | "@nestjs/platform-express": "^9.0.0", 40 | "@nestjs/throttler": "^3.1.0", 41 | "@nestjs/typeorm": "^9.0.1", 42 | "bcrypt": "^5.1.0", 43 | "class-transformer": "^0.5.1", 44 | "class-validator": "^0.13.2", 45 | "mysql2": "^2.3.3", 46 | "nodemailer": "^6.8.0", 47 | "pug": "^3.0.2", 48 | "reflect-metadata": "^0.1.13", 49 | "rimraf": "^3.0.2", 50 | "rxjs": "^7.2.0", 51 | "typeorm": "^0.3.11" 52 | }, 53 | "devDependencies": { 54 | "@nestjs/cli": "^9.0.0", 55 | "@nestjs/schematics": "^9.0.0", 56 | "@nestjs/testing": "^9.0.0", 57 | "@types/bcrypt": "^5.0.0", 58 | "@types/express": "^4.17.13", 59 | "@types/jest": "28.1.8", 60 | "@types/multer": "^1.4.7", 61 | "@types/node": "^16.0.0", 62 | "@types/nodemailer": "^6.4.7", 63 | "@types/supertest": "^2.0.11", 64 | "@typescript-eslint/eslint-plugin": "^5.0.0", 65 | "@typescript-eslint/parser": "^5.0.0", 66 | "cross-env": "^7.0.3", 67 | "dotenv": "^16.0.3", 68 | "eslint": "^8.0.1", 69 | "eslint-config-prettier": "^8.3.0", 70 | "eslint-plugin-prettier": "^4.0.0", 71 | "jest": "28.1.3", 72 | "prettier": "^2.3.2", 73 | "source-map-support": "^0.5.20", 74 | "supertest": "^6.1.3", 75 | "ts-jest": "28.0.8", 76 | "ts-loader": "^9.2.3", 77 | "ts-node": "^10.0.0", 78 | "tsconfig-paths": "4.1.0", 79 | "typescript": "^4.7.4" 80 | }, 81 | "jest": { 82 | "moduleFileExtensions": [ 83 | "js", 84 | "json", 85 | "ts" 86 | ], 87 | "rootDir": "src", 88 | "testRegex": ".*\\.spec\\.ts$", 89 | "transform": { 90 | "^.+\\.(t|j)s$": "ts-jest" 91 | }, 92 | "collectCoverageFrom": [ 93 | "**/*.(t|j)s" 94 | ], 95 | "coverageDirectory": "../coverage", 96 | "testEnvironment": "node" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | 13 | @Post() 14 | setHello(): string { 15 | return 'POST: Hello Hcode!'; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_GUARD } from '@nestjs/core'; 2 | import { Module, forwardRef } from '@nestjs/common'; 3 | import { ThrottlerModule } from '@nestjs/throttler'; 4 | import { AppController } from './app.controller'; 5 | import { AppService } from './app.service'; 6 | import { AuthModule } from './auth/auth.module'; 7 | import { UserModule } from './user/user.module'; 8 | import { ThrottlerGuard } from '@nestjs/throttler/dist/throttler.guard'; 9 | import { ConfigModule } from '@nestjs/config'; 10 | import { MailerModule } from '@nestjs-modules/mailer'; 11 | import { PugAdapter } from '@nestjs-modules/mailer/dist/adapters/pug.adapter'; 12 | import { TypeOrmModule } from '@nestjs/typeorm'; 13 | import { UserEntity } from './user/entity/user.entity'; 14 | 15 | @Module({ 16 | imports: [ 17 | ConfigModule.forRoot({ 18 | envFilePath: process.env.ENV === 'test' ? '.env.test' : '.env', 19 | }), 20 | ThrottlerModule.forRoot({ 21 | ttl: 60, 22 | limit: 100, 23 | }), 24 | forwardRef(() => UserModule), 25 | forwardRef(() => AuthModule), 26 | MailerModule.forRoot({ 27 | transport: { 28 | host: 'smtp.ethereal.email', 29 | port: 587, 30 | auth: { 31 | user: 'marcelo44@ethereal.email', 32 | pass: 'YutK7Qq3QFwDDDahfC', 33 | }, 34 | }, 35 | defaults: { 36 | from: '"Hcode" ', 37 | }, 38 | template: { 39 | dir: __dirname + '/templates', 40 | adapter: new PugAdapter(), 41 | options: { 42 | strict: true, 43 | }, 44 | }, 45 | }), 46 | TypeOrmModule.forRoot({ 47 | type: 'mysql', 48 | host: process.env.DB_HOST, 49 | port: Number(process.env.DB_PORT), 50 | username: process.env.DB_USERNAME, 51 | password: process.env.DB_PASSWORD, 52 | database: process.env.DB_DATABASE, 53 | entities: [UserEntity], 54 | synchronize: process.env.ENV === 'development', 55 | }), 56 | ], 57 | controllers: [AppController], 58 | providers: [ 59 | AppService, 60 | { 61 | provide: APP_GUARD, 62 | useClass: ThrottlerGuard, 63 | }, 64 | ], 65 | exports: [AppService], 66 | }) 67 | export class AppModule {} 68 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthGuard } from '../guards/auth.guard'; 3 | import { accessToken } from '../testing/access-token.mock'; 4 | import { authForgetDTO } from '../testing/auth-forget-dto.mock'; 5 | import { authLoginDTO } from '../testing/auth-login-dto.mock'; 6 | import { authRegisterDTO } from '../testing/auth-register-dto.mock'; 7 | import { authResetDTO } from '../testing/auth-reset-dto.mock'; 8 | import { authServiceMock } from '../testing/auth-service.mock'; 9 | import { fileServiceMock } from '../testing/file-service.mock'; 10 | import { getPhoto } from '../testing/get-photo.mock'; 11 | import { guardMock } from '../testing/guard.mock'; 12 | import { userEntityList } from '../testing/user-entity-list.mock'; 13 | import { AuthController } from './auth.controller'; 14 | 15 | describe('AuthController', () => { 16 | let authController: AuthController; 17 | 18 | beforeEach(async () => { 19 | const module: TestingModule = await Test.createTestingModule({ 20 | controllers: [AuthController], 21 | providers: [authServiceMock, fileServiceMock], 22 | }) 23 | .overrideGuard(AuthGuard) 24 | .useValue(guardMock) 25 | .compile(); 26 | 27 | authController = module.get(AuthController); 28 | }); 29 | 30 | test('Validar a definição', () => { 31 | expect(authController).toBeDefined(); 32 | }); 33 | 34 | describe('Fluxo de autenticação', () => { 35 | test('login method', async () => { 36 | const result = await authController.login(authLoginDTO); 37 | expect(result).toEqual({ accessToken }); 38 | }); 39 | test('register method', async () => { 40 | const result = await authController.register(authRegisterDTO); 41 | expect(result).toEqual({ accessToken }); 42 | }); 43 | test('forget method', async () => { 44 | const result = await authController.forget(authForgetDTO); 45 | expect(result).toEqual({ success: true }); 46 | }); 47 | test('reset method', async () => { 48 | const result = await authController.reset(authResetDTO); 49 | expect(result).toEqual({ accessToken }); 50 | }); 51 | }); 52 | 53 | describe('Rotas autenticadas', () => { 54 | test('me method', async () => { 55 | const result = await authController.me(userEntityList[0]); 56 | expect(result).toEqual(userEntityList[0]); 57 | }); 58 | 59 | test('uploadPhoto method', async () => { 60 | const photo = await getPhoto(); 61 | const result = await authController.uploadPhoto(userEntityList[0], photo); 62 | expect(result).toEqual(photo); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | Body, 5 | UseGuards, 6 | UseInterceptors, 7 | BadRequestException, 8 | ParseFilePipe, 9 | FileTypeValidator, 10 | MaxFileSizeValidator, 11 | } from '@nestjs/common'; 12 | import { UploadedFile, UploadedFiles } from '@nestjs/common/decorators'; 13 | import { 14 | FileInterceptor, 15 | FilesInterceptor, 16 | FileFieldsInterceptor, 17 | } from '@nestjs/platform-express'; 18 | import { AuthService } from './auth.service'; 19 | import { AuthForgetDTO } from './dto/auth-forget.dto'; 20 | import { AuthLoginDTO } from './dto/auth-login.dto'; 21 | import { AuthRegisterDTO } from './dto/auth-register.dto'; 22 | import { AuthResetDTO } from './dto/auth-reset.dto'; 23 | import { FileService } from '../file/file.service'; 24 | import { AuthGuard } from '../guards/auth.guard'; 25 | import { User } from '../decorators/user.decorator'; 26 | import { UserEntity } from '../user/entity/user.entity'; 27 | 28 | @Controller('auth') 29 | export class AuthController { 30 | constructor( 31 | private readonly authService: AuthService, 32 | private readonly fileService: FileService, 33 | ) {} 34 | 35 | @Post('login') 36 | async login(@Body() { email, password }: AuthLoginDTO) { 37 | return this.authService.login(email, password); 38 | } 39 | 40 | @Post('register') 41 | async register(@Body() body: AuthRegisterDTO) { 42 | return this.authService.register(body); 43 | } 44 | 45 | @Post('forget') 46 | async forget(@Body() { email }: AuthForgetDTO) { 47 | return this.authService.forget(email); 48 | } 49 | 50 | @Post('reset') 51 | async reset(@Body() { password, token }: AuthResetDTO) { 52 | return this.authService.reset(password, token); 53 | } 54 | 55 | @UseGuards(AuthGuard) 56 | @Post('me') 57 | async me(@User() user: UserEntity) { 58 | return user; 59 | } 60 | 61 | @UseInterceptors(FileInterceptor('file')) 62 | @UseGuards(AuthGuard) 63 | @Post('photo') 64 | async uploadPhoto( 65 | @User() user: UserEntity, 66 | @UploadedFile( 67 | new ParseFilePipe({ 68 | validators: [ 69 | new FileTypeValidator({ fileType: 'image/png' }), 70 | new MaxFileSizeValidator({ maxSize: 1024 * 50 }), 71 | ], 72 | }), 73 | ) 74 | photo: Express.Multer.File, 75 | ) { 76 | const filename = `photo-${user.id}.png`; 77 | 78 | try { 79 | await this.fileService.upload(photo, filename); 80 | } catch (e) { 81 | throw new BadRequestException(e); 82 | } 83 | 84 | return photo; 85 | } 86 | 87 | @UseInterceptors(FilesInterceptor('files')) 88 | @UseGuards(AuthGuard) 89 | @Post('files') 90 | async uploadFiles( 91 | @User() user, 92 | @UploadedFiles() files: Express.Multer.File[], 93 | ) { 94 | return files; 95 | } 96 | 97 | @UseInterceptors( 98 | FileFieldsInterceptor([ 99 | { 100 | name: 'photo', 101 | maxCount: 1, 102 | }, 103 | { 104 | name: 'documents', 105 | maxCount: 10, 106 | }, 107 | ]), 108 | ) 109 | @UseGuards(AuthGuard) 110 | @Post('files-fields') 111 | async uploadFilesFields( 112 | @User() user, 113 | @UploadedFiles() 114 | files: { photo: Express.Multer.File; documents: Express.Multer.File[] }, 115 | ) { 116 | return files; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, forwardRef } from '@nestjs/common'; 2 | import { JwtModule } from '@nestjs/jwt'; 3 | import { AuthController } from './auth.controller'; 4 | import { AuthService } from './auth.service'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { UserModule } from '../user/user.module'; 7 | import { FileModule } from '../file/file.module'; 8 | import { UserEntity } from '../user/entity/user.entity'; 9 | 10 | @Module({ 11 | imports: [ 12 | JwtModule.register({ 13 | secret: String(process.env.JWT_SECRET), 14 | }), 15 | forwardRef(() => UserModule), 16 | FileModule, 17 | TypeOrmModule.forFeature([UserEntity]), 18 | ], 19 | controllers: [AuthController], 20 | providers: [AuthService], 21 | exports: [AuthService], 22 | }) 23 | export class AuthModule {} 24 | -------------------------------------------------------------------------------- /src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { accessToken } from '../testing/access-token.mock'; 3 | import { authRegisterDTO } from '../testing/auth-register-dto.mock'; 4 | import { jwtPayload } from '../testing/jwt-payload.mock'; 5 | import { jwtServiceMock } from '../testing/jwt-service.mock'; 6 | import { mailerServiceMock } from '../testing/mailer-service.mock'; 7 | import { resetToken } from '../testing/reset-token.mock'; 8 | import { userEntityList } from '../testing/user-entity-list.mock'; 9 | import { userRepositoryMock } from '../testing/user-repository.mock'; 10 | import { userServiceMock } from '../testing/user-service.mock'; 11 | import { AuthService } from './auth.service'; 12 | 13 | describe('AuthService', () => { 14 | let authService: AuthService; 15 | 16 | beforeEach(async () => { 17 | const module: TestingModule = await Test.createTestingModule({ 18 | providers: [ 19 | AuthService, 20 | userRepositoryMock, 21 | jwtServiceMock, 22 | userServiceMock, 23 | mailerServiceMock, 24 | ], 25 | }).compile(); 26 | 27 | authService = module.get(AuthService); 28 | }); 29 | 30 | test('Validar a definição', () => { 31 | expect(authService).toBeDefined(); 32 | }); 33 | 34 | describe('Token', () => { 35 | test('createToken method', () => { 36 | const result = authService.createToken(userEntityList[0]); 37 | 38 | console.log(result); 39 | 40 | expect(result).toEqual({ accessToken }); 41 | }); 42 | 43 | test('checkToken method', () => { 44 | const result = authService.checkToken(accessToken); 45 | 46 | expect(result).toEqual(jwtPayload); 47 | }); 48 | 49 | test('isValidToken method', () => { 50 | const result = authService.isValidToken(accessToken); 51 | 52 | expect(result).toEqual(true); 53 | }); 54 | }); 55 | 56 | describe('Autenticação', () => { 57 | test('login method', async () => { 58 | const result = await authService.login('joao@hcode.com.br', '123456'); 59 | 60 | expect(result).toEqual({ accessToken }); 61 | }); 62 | 63 | test('forget method', async () => { 64 | const result = await authService.forget('joao@hcode.com.br'); 65 | 66 | expect(result).toEqual({ success: true }); 67 | }); 68 | 69 | test('reset method', async () => { 70 | const result = await authService.reset('654321', resetToken); 71 | 72 | expect(result).toEqual({ accessToken }); 73 | }); 74 | 75 | test('register method', async () => { 76 | const result = await authService.register(authRegisterDTO); 77 | 78 | expect(result).toEqual({ accessToken }); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { UnauthorizedException } from '@nestjs/common/exceptions/unauthorized.exception'; 3 | import { JwtService } from '@nestjs/jwt'; 4 | import { AuthRegisterDTO } from './dto/auth-register.dto'; 5 | import * as bcrypt from 'bcrypt'; 6 | import { MailerService } from '@nestjs-modules/mailer/dist'; 7 | import { Repository } from 'typeorm'; 8 | import { InjectRepository } from '@nestjs/typeorm'; 9 | import { UserService } from '../user/user.service'; 10 | import { UserEntity } from '../user/entity/user.entity'; 11 | 12 | @Injectable() 13 | export class AuthService { 14 | private issuer = 'login'; 15 | private audience = 'users'; 16 | 17 | constructor( 18 | private readonly jwtService: JwtService, 19 | private readonly userService: UserService, 20 | private readonly mailer: MailerService, 21 | @InjectRepository(UserEntity) 22 | private usersRepository: Repository, 23 | ) {} 24 | 25 | createToken(user: UserEntity) { 26 | return { 27 | accessToken: this.jwtService.sign( 28 | { 29 | id: user.id, 30 | name: user.name, 31 | email: user.email, 32 | }, 33 | { 34 | expiresIn: '7 days', 35 | subject: String(user.id), 36 | issuer: this.issuer, 37 | audience: this.audience, 38 | }, 39 | ), 40 | }; 41 | } 42 | 43 | checkToken(token: string) { 44 | try { 45 | const data = this.jwtService.verify(token, { 46 | issuer: this.issuer, 47 | audience: this.audience, 48 | }); 49 | 50 | return data; 51 | } catch (e) { 52 | throw new BadRequestException(e); 53 | } 54 | } 55 | 56 | isValidToken(token: string) { 57 | try { 58 | this.checkToken(token); 59 | return true; 60 | } catch (e) { 61 | return false; 62 | } 63 | } 64 | 65 | async login(email: string, password: string) { 66 | const user = await this.usersRepository.findOneBy({ 67 | email, 68 | }); 69 | 70 | if (!user) { 71 | throw new UnauthorizedException('E-mail e/ou senha incorretos.'); 72 | } 73 | 74 | if (!(await bcrypt.compare(password, user.password))) { 75 | throw new UnauthorizedException('E-mail e/ou senha incorretos.'); 76 | } 77 | 78 | return this.createToken(user); 79 | } 80 | 81 | async forget(email: string) { 82 | const user = await this.usersRepository.findOneBy({ 83 | email, 84 | }); 85 | 86 | if (!user) { 87 | throw new UnauthorizedException('E-mail está incorreto.'); 88 | } 89 | 90 | const token = this.jwtService.sign( 91 | { 92 | id: user.id, 93 | }, 94 | { 95 | expiresIn: '30 minutes', 96 | subject: String(user.id), 97 | issuer: 'forget', 98 | audience: 'users', 99 | }, 100 | ); 101 | 102 | await this.mailer.sendMail({ 103 | subject: 'Recuperação de Senha', 104 | to: 'joao@hcode.com.br', 105 | template: 'forget', 106 | context: { 107 | name: user.name, 108 | token, 109 | }, 110 | }); 111 | 112 | return { success: true }; 113 | } 114 | 115 | async reset(password: string, token: string) { 116 | try { 117 | const data: any = this.jwtService.verify(token, { 118 | issuer: 'forget', 119 | audience: 'users', 120 | }); 121 | 122 | if (isNaN(Number(data.id))) { 123 | throw new BadRequestException('Token é inválido.'); 124 | } 125 | 126 | const salt = await bcrypt.genSalt(); 127 | password = await bcrypt.hash(password, salt); 128 | 129 | await this.usersRepository.update(Number(data.id), { 130 | password, 131 | }); 132 | 133 | const user = await this.userService.show(Number(data.id)); 134 | 135 | return this.createToken(user); 136 | } catch (e) { 137 | throw new BadRequestException(e); 138 | } 139 | } 140 | 141 | async register(data: AuthRegisterDTO) { 142 | delete data.role; 143 | 144 | const user = await this.userService.create(data); 145 | 146 | return this.createToken(user); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/auth/dto/auth-forget.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail } from 'class-validator'; 2 | 3 | export class AuthForgetDTO { 4 | @IsEmail() 5 | email: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/dto/auth-login.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, MinLength } from 'class-validator'; 2 | 3 | export class AuthLoginDTO { 4 | @IsEmail() 5 | email: string; 6 | 7 | @IsString() 8 | @MinLength(6) 9 | password: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/auth/dto/auth-me.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsJWT } from 'class-validator'; 2 | 3 | export class AuthMeDTO { 4 | @IsJWT() 5 | token: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/dto/auth-register.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserDTO } from '../../user/dto/create-user.dto'; 2 | 3 | export class AuthRegisterDTO extends CreateUserDTO {} 4 | -------------------------------------------------------------------------------- /src/auth/dto/auth-reset.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsJWT, IsString, MinLength } from 'class-validator'; 2 | 3 | export class AuthResetDTO { 4 | @IsString() 5 | @MinLength(6) 6 | password: string; 7 | 8 | @IsJWT() 9 | token: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/decorators/param-id.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | export const ParamId = createParamDecorator( 4 | (_data: unknown, context: ExecutionContext) => { 5 | return Number(context.switchToHttp().getRequest().params.id); 6 | }, 7 | ); 8 | -------------------------------------------------------------------------------- /src/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { Role } from '../enums/role.enum'; 3 | 4 | export const ROLES_KEY = 'roles'; 5 | export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); 6 | -------------------------------------------------------------------------------- /src/decorators/user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createParamDecorator, 3 | ExecutionContext, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | 7 | export const User = createParamDecorator( 8 | (filter: string, context: ExecutionContext) => { 9 | const request = context.switchToHttp().getRequest(); 10 | 11 | if (request.user) { 12 | if (filter) { 13 | return request.user[filter]; 14 | } else { 15 | return request.user; 16 | } 17 | } else { 18 | throw new NotFoundException( 19 | 'Usuário não encontrado no Request. Use o AuthGuard para obter o usuário.', 20 | ); 21 | } 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /src/enums/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | User = 1, 3 | Admin = 2, 4 | } 5 | -------------------------------------------------------------------------------- /src/file/file.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { FileService } from './file.service'; 3 | 4 | @Module({ 5 | providers: [FileService], 6 | exports: [FileService], 7 | }) 8 | export class FileModule {} 9 | -------------------------------------------------------------------------------- /src/file/file.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { getPhoto } from '../testing/get-photo.mock'; 3 | import { FileService } from './file.service'; 4 | 5 | describe('FileService', () => { 6 | let fileService: FileService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [FileService], 11 | }).compile(); 12 | 13 | fileService = module.get(FileService); 14 | }); 15 | 16 | test('Validar a definição', () => { 17 | expect(fileService).toBeDefined(); 18 | }); 19 | 20 | describe('Teste do File Service', () => { 21 | test('upload method', async () => { 22 | const photo = await getPhoto(); 23 | const filename = 'photo-test.png'; 24 | fileService.upload(photo, filename); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/file/file.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PathLike } from 'fs'; 3 | import { writeFile } from 'fs/promises'; 4 | import { join } from 'path'; 5 | 6 | @Injectable() 7 | export class FileService { 8 | getDestinationPath() { 9 | return join(__dirname, '..', '..', 'storage', 'photos'); 10 | } 11 | 12 | async upload(file: Express.Multer.File, filename: string) { 13 | const path: PathLike = join(this.getDestinationPath(), filename); 14 | await writeFile(path, file.buffer); 15 | return path; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable, ExecutionContext } from '@nestjs/common'; 2 | import { AuthService } from '../auth/auth.service'; 3 | import { UserService } from '../user/user.service'; 4 | 5 | @Injectable() 6 | export class AuthGuard implements CanActivate { 7 | constructor( 8 | private readonly authService: AuthService, 9 | private readonly userService: UserService, 10 | ) {} 11 | 12 | async canActivate(context: ExecutionContext) { 13 | const request = context.switchToHttp().getRequest(); 14 | const { authorization } = request.headers; 15 | try { 16 | const data = this.authService.checkToken( 17 | (authorization ?? '').split(' ')[1], 18 | ); 19 | 20 | request.tokenPayload = data; 21 | 22 | request.user = await this.userService.show(data.id); 23 | 24 | return true; 25 | } catch (e) { 26 | return false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/guards/role.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { ROLES_KEY } from '../decorators/roles.decorator'; 4 | import { Role } from '../enums/role.enum'; 5 | 6 | @Injectable() 7 | export class RoleGuard implements CanActivate { 8 | constructor(private readonly reflector: Reflector) {} 9 | 10 | async canActivate(context: ExecutionContext) { 11 | const requeridRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ 12 | context.getHandler(), 13 | context.getClass(), 14 | ]); 15 | 16 | console.log({ requeridRoles }); 17 | 18 | if (!requeridRoles) { 19 | return true; 20 | } 21 | 22 | const { user } = context.switchToHttp().getRequest(); 23 | 24 | const rolesFilted = requeridRoles.filter((role) => role === user.role); 25 | 26 | return rolesFilted.length > 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/interceptors/log.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { tap } from 'rxjs/operators'; 4 | 5 | export class LogInterceptor implements NestInterceptor { 6 | intercept(context: ExecutionContext, next: CallHandler): Observable { 7 | const dt = Date.now(); 8 | 9 | return next.handle().pipe( 10 | tap(() => { 11 | const request = context.switchToHttp().getRequest(); 12 | 13 | console.log(`URL: ${request.url}`); 14 | console.log(`METHOD: ${request.method}`); 15 | console.log(`Execução levou: ${Date.now() - dt} milisegundos.`); 16 | }), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { ValidationPipe } from '@nestjs/common'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | 8 | app.enableCors(); 9 | 10 | app.useGlobalPipes(new ValidationPipe()); 11 | 12 | await app.listen(3000); 13 | } 14 | 15 | bootstrap(); 16 | -------------------------------------------------------------------------------- /src/middlewares/user-id-check.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NestMiddleware, BadRequestException } from '@nestjs/common'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | 4 | export class UserIdCheckMiddleware implements NestMiddleware { 5 | use(req: Request, res: Response, next: NextFunction) { 6 | console.log('UserIdCheckMiddleware', 'antes'); 7 | 8 | if (isNaN(Number(req.params.id)) || Number(req.params.id) <= 0) { 9 | throw new BadRequestException('ID inválido!'); 10 | } 11 | 12 | console.log('UserIdCheckMiddleware', 'depois'); 13 | 14 | next(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/forget.pug: -------------------------------------------------------------------------------- 1 | p #{name} você solicitou a recuperação de senha, por favor use o token #{token} -------------------------------------------------------------------------------- /src/testing/access-token.mock.ts: -------------------------------------------------------------------------------- 1 | export const accessToken = 2 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsIm5hbWUiOiJHbGF1Y2lvIERhbmllbCIsImVtYWlsIjoiZ2xhdWNpbzJAaGNvZGUuY29tLmJyIiwiaWF0IjoxNjcyMTE3NDI3LCJleHAiOjE2NzI3MjIyMjcsImF1ZCI6InVzZXJzIiwiaXNzIjoibG9naW4iLCJzdWIiOiIxNSJ9.eSHCxi2YwRvz4gSZ4Rs1geebvDu7_FRfeAZX9ErvTGY'; 3 | -------------------------------------------------------------------------------- /src/testing/auth-forget-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { AuthForgetDTO } from '../auth/dto/auth-forget.dto'; 2 | 3 | export const authForgetDTO: AuthForgetDTO = { 4 | email: 'joao@hcode.com.br', 5 | }; 6 | -------------------------------------------------------------------------------- /src/testing/auth-login-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { AuthLoginDTO } from '../auth/dto/auth-login.dto'; 2 | 3 | export const authLoginDTO: AuthLoginDTO = { 4 | email: 'joao@hcode.com.br', 5 | password: '123456', 6 | }; 7 | -------------------------------------------------------------------------------- /src/testing/auth-register-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { AuthRegisterDTO } from '../auth/dto/auth-register.dto'; 2 | 3 | export const authRegisterDTO: AuthRegisterDTO = { 4 | email: 'joao@hcode.com.br', 5 | name: 'João Rangel', 6 | password: '123456', 7 | }; 8 | -------------------------------------------------------------------------------- /src/testing/auth-reset-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { AuthResetDTO } from '../auth/dto/auth-reset.dto'; 2 | import { resetToken } from './reset-token.mock'; 3 | 4 | export const authResetDTO: AuthResetDTO = { 5 | password: '654321', 6 | token: resetToken, 7 | }; 8 | -------------------------------------------------------------------------------- /src/testing/auth-service.mock.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from '../auth/auth.service'; 2 | import { accessToken } from './access-token.mock'; 3 | import { jwtPayload } from './jwt-payload.mock'; 4 | 5 | export const authServiceMock = { 6 | provide: AuthService, 7 | useValue: { 8 | createToken: jest.fn().mockReturnValue({ accessToken }), 9 | checkToken: jest.fn().mockReturnValue(jwtPayload), 10 | isValidToken: jest.fn().mockReturnValue(true), 11 | login: jest.fn().mockResolvedValue({ accessToken }), 12 | forget: jest.fn().mockResolvedValue({ success: true }), 13 | reset: jest.fn().mockResolvedValue({ accessToken }), 14 | register: jest.fn().mockResolvedValue({ accessToken }), 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/testing/create-user-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../enums/role.enum'; 2 | import { CreateUserDTO } from '../user/dto/create-user.dto'; 3 | 4 | export const createUserDTO: CreateUserDTO = { 5 | birthAt: '2000-01-01', 6 | email: 'joao@hcode.com.br', 7 | name: 'Joao Rangel', 8 | password: '123456', 9 | role: Role.User, 10 | }; 11 | -------------------------------------------------------------------------------- /src/testing/file-service.mock.ts: -------------------------------------------------------------------------------- 1 | import { FileService } from '../file/file.service'; 2 | 3 | export const fileServiceMock = { 4 | provide: FileService, 5 | useValue: { 6 | getDestinationPath: jest.fn(), 7 | upload: jest.fn().mockResolvedValue(''), 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/testing/get-file-to-buffer.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream, ReadStream } from 'fs'; 2 | 3 | export const getFileToBuffer = (filename: string) => { 4 | const readStream = createReadStream(filename); 5 | const chunks = []; 6 | 7 | return new Promise<{ buffer: Buffer; stream: ReadStream }>( 8 | (resolve, reject) => { 9 | readStream.on('data', (chunk) => chunks.push(chunk)); 10 | 11 | readStream.on('error', (err) => reject(err)); 12 | 13 | readStream.on('close', () => { 14 | resolve({ 15 | buffer: Buffer.concat(chunks) as Buffer, 16 | stream: readStream, 17 | }); 18 | }); 19 | }, 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/testing/get-photo.mock.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { getFileToBuffer } from './get-file-to-buffer'; 3 | 4 | export const getPhoto = async () => { 5 | const { buffer, stream } = await getFileToBuffer( 6 | join(__dirname, 'photo.png'), 7 | ); 8 | 9 | const photo: Express.Multer.File = { 10 | fieldname: 'file', 11 | originalname: 'photo.png', 12 | encoding: '7bit', 13 | mimetype: 'image/png', 14 | size: 1024 * 50, 15 | stream, 16 | destination: '', 17 | filename: 'file-name', 18 | path: 'file-path', 19 | buffer, 20 | }; 21 | 22 | return photo; 23 | }; 24 | -------------------------------------------------------------------------------- /src/testing/guard.mock.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate } from '@nestjs/common'; 2 | 3 | export const guardMock: CanActivate = { 4 | canActivate: jest.fn(() => true), 5 | }; 6 | -------------------------------------------------------------------------------- /src/testing/jwt-payload.mock.ts: -------------------------------------------------------------------------------- 1 | export const jwtPayload = { 2 | id: 1, 3 | name: 'Glaucio Daniel', 4 | email: 'glaucio@hcode.com.br', 5 | iat: 1672197163, 6 | exp: 1672801963, 7 | aud: 'users', 8 | iss: 'login', 9 | sub: '1', 10 | }; 11 | -------------------------------------------------------------------------------- /src/testing/jwt-service.mock.ts: -------------------------------------------------------------------------------- 1 | import { JwtService } from '@nestjs/jwt'; 2 | import { accessToken } from './access-token.mock'; 3 | import { jwtPayload } from './jwt-payload.mock'; 4 | 5 | export const jwtServiceMock = { 6 | provide: JwtService, 7 | useValue: { 8 | sign: jest.fn().mockReturnValue(accessToken), 9 | verify: jest.fn().mockReturnValue(jwtPayload), 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/testing/mailer-service.mock.ts: -------------------------------------------------------------------------------- 1 | import { MailerService } from '@nestjs-modules/mailer/dist'; 2 | 3 | export const mailerServiceMock = { 4 | provide: MailerService, 5 | useValue: { 6 | sendMail: jest.fn(), 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/testing/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcodebr/nestjs-typeorm-mysql/ba11099c1169b201d8109e4a2d659ea94fe9e1ba/src/testing/photo.png -------------------------------------------------------------------------------- /src/testing/reset-token.mock.ts: -------------------------------------------------------------------------------- 1 | export const resetToken = 2 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjcyMjAwNjUwLCJleHAiOjE2NzIyMDI0NTAsImF1ZCI6InVzZXJzIiwiaXNzIjoiZm9yZ2V0Iiwic3ViIjoiMSJ9.mZZF2D3s_06eskyfFOJOWxb5fZkLe_GN2RRmbviHmJI'; 3 | -------------------------------------------------------------------------------- /src/testing/update-patch-user-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../enums/role.enum'; 2 | import { UpdatePatchUserDTO } from '../user/dto/update-patch-user.dto'; 3 | 4 | export const updatePatchUserDTO: UpdatePatchUserDTO = { 5 | role: Role.Admin, 6 | }; 7 | -------------------------------------------------------------------------------- /src/testing/update-put-user-dto.mock.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../enums/role.enum'; 2 | import { UpdatePutUserDTO } from '../user/dto/update-put-user.dto'; 3 | 4 | export const updatePutUserDTO: UpdatePutUserDTO = { 5 | birthAt: '2000-01-01', 6 | email: 'joao@hcode.com.br', 7 | name: 'Joao Rangel', 8 | password: '123456', 9 | role: Role.User, 10 | }; 11 | -------------------------------------------------------------------------------- /src/testing/user-entity-list.mock.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../enums/role.enum'; 2 | import { UserEntity } from '../user/entity/user.entity'; 3 | 4 | export const userEntityList: UserEntity[] = [ 5 | { 6 | name: 'Joao Rangel', 7 | email: 'joao@hcode.com.br', 8 | birthAt: new Date('2000-01-01'), 9 | id: 1, 10 | password: '$2b$10$KTCMumuAvsZcxgEXCA4.x.sqeqtrWXmB7ptFGkF.f32XW3OE3Awb6', 11 | role: Role.Admin, 12 | createdAt: new Date(), 13 | updatedAt: new Date(), 14 | }, 15 | { 16 | name: 'Glaucio Daniel', 17 | email: 'glaucio@hcode.com.br', 18 | birthAt: new Date('2000-01-01'), 19 | id: 2, 20 | password: '$2b$10$KTCMumuAvsZcxgEXCA4.x.sqeqtrWXmB7ptFGkF.f32XW3OE3Awb6', 21 | role: Role.Admin, 22 | createdAt: new Date(), 23 | updatedAt: new Date(), 24 | }, 25 | { 26 | name: 'Djalma Sindaux', 27 | email: 'djalma@hcode.com.br', 28 | birthAt: new Date('2000-01-01'), 29 | id: 3, 30 | password: '$2b$10$KTCMumuAvsZcxgEXCA4.x.sqeqtrWXmB7ptFGkF.f32XW3OE3Awb6', 31 | role: Role.Admin, 32 | createdAt: new Date(), 33 | updatedAt: new Date(), 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /src/testing/user-repository.mock.ts: -------------------------------------------------------------------------------- 1 | import { getRepositoryToken } from '@nestjs/typeorm'; 2 | import { UserEntity } from '../user/entity/user.entity'; 3 | import { userEntityList } from './user-entity-list.mock'; 4 | 5 | export const userRepositoryMock = { 6 | provide: getRepositoryToken(UserEntity), 7 | useValue: { 8 | exist: jest.fn().mockResolvedValue(true), 9 | create: jest.fn(), 10 | save: jest.fn().mockResolvedValue(userEntityList[0]), 11 | find: jest.fn().mockResolvedValue(userEntityList), 12 | findOneBy: jest.fn().mockResolvedValue(userEntityList[0]), 13 | update: jest.fn(), 14 | delete: jest.fn(), 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/testing/user-service.mock.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from '../user/user.service'; 2 | import { userEntityList } from './user-entity-list.mock'; 3 | 4 | export const userServiceMock = { 5 | provide: UserService, 6 | useValue: { 7 | show: jest.fn().mockResolvedValue(userEntityList[0]), 8 | create: jest.fn().mockResolvedValue(userEntityList[0]), 9 | list: jest.fn().mockResolvedValue(userEntityList), 10 | update: jest.fn().mockResolvedValue(userEntityList[0]), 11 | updatePartial: jest.fn().mockResolvedValue(userEntityList[0]), 12 | delete: jest.fn().mockResolvedValue(true), 13 | exists: jest.fn().mockResolvedValue(true), 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsString, 3 | IsEmail, 4 | MinLength, 5 | IsOptional, 6 | IsDateString, 7 | IsEnum, 8 | } from 'class-validator'; 9 | import { Role } from '../../enums/role.enum'; 10 | 11 | export class CreateUserDTO { 12 | @IsString() 13 | name: string; 14 | 15 | @IsEmail() 16 | email: string; 17 | 18 | @IsString() 19 | @MinLength(6) 20 | password: string; 21 | 22 | @IsOptional() 23 | @IsDateString() 24 | birthAt?: string; 25 | 26 | @IsOptional() 27 | @IsEnum(Role) 28 | role?: number; 29 | } 30 | -------------------------------------------------------------------------------- /src/user/dto/update-patch-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserDTO } from './create-user.dto'; 2 | import { PartialType } from '@nestjs/mapped-types'; 3 | 4 | export class UpdatePatchUserDTO extends PartialType(CreateUserDTO) {} 5 | -------------------------------------------------------------------------------- /src/user/dto/update-put-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserDTO } from './create-user.dto'; 2 | 3 | export class UpdatePutUserDTO extends CreateUserDTO {} 4 | -------------------------------------------------------------------------------- /src/user/entity/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | } from 'typeorm'; 8 | import { Role } from '../../enums/role.enum'; 9 | 10 | @Entity({ 11 | name: 'users', 12 | }) 13 | export class UserEntity { 14 | @PrimaryGeneratedColumn({ 15 | unsigned: true, 16 | }) 17 | id?: number; 18 | 19 | @Column({ 20 | length: 63, 21 | }) 22 | name: string; 23 | 24 | @Column({ 25 | length: 127, 26 | unique: true, 27 | }) 28 | email: string; 29 | 30 | @Column({ 31 | length: 127, 32 | }) 33 | password: string; 34 | 35 | @Column({ 36 | type: 'date', 37 | nullable: true, 38 | }) 39 | birthAt?: Date; 40 | 41 | @CreateDateColumn() 42 | createdAt?: Date; 43 | 44 | @UpdateDateColumn() 45 | updatedAt?: Date; 46 | 47 | @Column({ 48 | default: Role.User, 49 | }) 50 | role: number; 51 | } 52 | -------------------------------------------------------------------------------- /src/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthGuard } from '../guards/auth.guard'; 3 | import { RoleGuard } from '../guards/role.guard'; 4 | import { createUserDTO } from '../testing/create-user-dto.mock'; 5 | import { guardMock } from '../testing/guard.mock'; 6 | import { updatePatchUserDTO } from '../testing/update-patch-user-dto.mock'; 7 | import { updatePutUserDTO } from '../testing/update-put-user-dto.mock'; 8 | import { userEntityList } from '../testing/user-entity-list.mock'; 9 | import { userServiceMock } from '../testing/user-service.mock'; 10 | import { UserController } from './user.controller'; 11 | import { UserService } from './user.service'; 12 | 13 | describe('UserController', () => { 14 | let userController: UserController; 15 | let userService: UserService; 16 | 17 | beforeEach(async () => { 18 | const module: TestingModule = await Test.createTestingModule({ 19 | controllers: [UserController], 20 | providers: [userServiceMock], 21 | }) 22 | .overrideGuard(AuthGuard) 23 | .useValue(guardMock) 24 | .overrideGuard(RoleGuard) 25 | .useValue(guardMock) 26 | .compile(); 27 | 28 | userController = module.get(UserController); 29 | userService = module.get(UserService); 30 | }); 31 | 32 | test('Validar a definição', () => { 33 | expect(userController).toBeDefined(); 34 | expect(userService).toBeDefined(); 35 | }); 36 | 37 | describe('Teste da aplicação dos Guards neste controle', () => { 38 | test('Se os guards estão aplicados', () => { 39 | const guards = Reflect.getMetadata('__guards__', UserController); 40 | 41 | expect(guards.length).toEqual(2); 42 | expect(new guards[0]()).toBeInstanceOf(AuthGuard); 43 | expect(new guards[1]()).toBeInstanceOf(RoleGuard); 44 | }); 45 | }); 46 | 47 | describe('Create', () => { 48 | test('create method', async () => { 49 | const result = await userController.create(createUserDTO); 50 | 51 | expect(result).toEqual(userEntityList[0]); 52 | }); 53 | }); 54 | 55 | describe('Read', () => { 56 | test('list method', async () => { 57 | const result = await userController.list(); 58 | 59 | expect(result).toEqual(userEntityList); 60 | }); 61 | test('show method', async () => { 62 | const result = await userController.show(1); 63 | 64 | expect(result).toEqual(userEntityList[0]); 65 | }); 66 | }); 67 | 68 | describe('Update', () => { 69 | test('update method', async () => { 70 | const result = await userController.update(updatePutUserDTO, 1); 71 | 72 | expect(result).toEqual(userEntityList[0]); 73 | }); 74 | test('updatePartial method', async () => { 75 | const result = await userController.updatePartial(updatePatchUserDTO, 1); 76 | 77 | expect(result).toEqual(userEntityList[0]); 78 | }); 79 | }); 80 | 81 | describe('Delete', () => { 82 | test('delete method', async () => { 83 | const result = await userController.delete(1); 84 | 85 | expect(result).toEqual({ success: true }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | Body, 5 | Get, 6 | Put, 7 | Patch, 8 | Delete, 9 | UseInterceptors, 10 | UseGuards, 11 | } from '@nestjs/common'; 12 | import { ParamId } from '../decorators/param-id.decorator'; 13 | import { Roles } from '../decorators/roles.decorator'; 14 | import { Role } from '../enums/role.enum'; 15 | import { AuthGuard } from '../guards/auth.guard'; 16 | import { RoleGuard } from '../guards/role.guard'; 17 | import { LogInterceptor } from '../interceptors/log.interceptor'; 18 | import { CreateUserDTO } from './dto/create-user.dto'; 19 | import { UpdatePatchUserDTO } from './dto/update-patch-user.dto'; 20 | import { UpdatePutUserDTO } from './dto/update-put-user.dto'; 21 | import { UserService } from './user.service'; 22 | 23 | @Roles(Role.Admin) 24 | @UseGuards(AuthGuard, RoleGuard) 25 | @UseInterceptors(LogInterceptor) 26 | @Controller('users') 27 | export class UserController { 28 | constructor(private readonly userService: UserService) {} 29 | 30 | @Post() 31 | async create(@Body() data: CreateUserDTO) { 32 | return this.userService.create(data); 33 | } 34 | 35 | @Get() 36 | async list() { 37 | return this.userService.list(); 38 | } 39 | 40 | @Get(':id') 41 | async show(@ParamId() id: number) { 42 | console.log({ id }); 43 | return this.userService.show(id); 44 | } 45 | 46 | @Put(':id') 47 | async update(@Body() data: UpdatePutUserDTO, @ParamId() id: number) { 48 | return this.userService.update(id, data); 49 | } 50 | 51 | @Patch(':id') 52 | async updatePartial(@Body() data: UpdatePatchUserDTO, @ParamId() id: number) { 53 | return this.userService.updatePartial(id, data); 54 | } 55 | 56 | @Delete(':id') 57 | async delete(@ParamId() id: number) { 58 | return { 59 | success: await this.userService.delete(id), 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Module, 3 | NestModule, 4 | MiddlewareConsumer, 5 | RequestMethod, 6 | forwardRef, 7 | } from '@nestjs/common'; 8 | import { UserController } from './user.controller'; 9 | import { UserService } from './user.service'; 10 | import { TypeOrmModule } from '@nestjs/typeorm'; 11 | import { UserEntity } from './entity/user.entity'; 12 | import { AuthModule } from '../auth/auth.module'; 13 | import { UserIdCheckMiddleware } from '../middlewares/user-id-check.middleware'; 14 | 15 | @Module({ 16 | imports: [ 17 | forwardRef(() => AuthModule), 18 | TypeOrmModule.forFeature([UserEntity]), 19 | ], 20 | controllers: [UserController], 21 | providers: [UserService], 22 | exports: [UserService], 23 | }) 24 | export class UserModule implements NestModule { 25 | configure(consumer: MiddlewareConsumer) { 26 | consumer.apply(UserIdCheckMiddleware).forRoutes({ 27 | path: 'users/:id', 28 | method: RequestMethod.ALL, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserService } from './user.service'; 3 | import { userRepositoryMock } from '../testing/user-repository.mock'; 4 | import { userEntityList } from '../testing/user-entity-list.mock'; 5 | import { createUserDTO } from '../testing/create-user-dto.mock'; 6 | import { UserEntity } from './entity/user.entity'; 7 | import { Repository } from 'typeorm'; 8 | import { getRepositoryToken } from '@nestjs/typeorm'; 9 | import { updatePutUserDTO } from '../testing/update-put-user-dto.mock'; 10 | import { updatePatchUserDTO } from '../testing/update-patch-user-dto.mock'; 11 | 12 | describe('UserService', () => { 13 | let userService: UserService; 14 | let userRepository: Repository; 15 | 16 | beforeEach(async () => { 17 | const module: TestingModule = await Test.createTestingModule({ 18 | providers: [UserService, userRepositoryMock], 19 | }).compile(); 20 | 21 | userService = module.get(UserService); 22 | userRepository = module.get(getRepositoryToken(UserEntity)); 23 | }); 24 | 25 | test('Validar a definição', () => { 26 | expect(userService).toBeDefined(); 27 | expect(userRepository).toBeDefined(); 28 | }); 29 | 30 | describe('Create', () => { 31 | test('method create', async () => { 32 | jest.spyOn(userRepository, 'exist').mockResolvedValueOnce(false); 33 | 34 | const result = await userService.create(createUserDTO); 35 | 36 | expect(result).toEqual(userEntityList[0]); 37 | }); 38 | }); 39 | describe('Read', () => { 40 | test('method list', async () => { 41 | const result = await userService.list(); 42 | 43 | expect(result).toEqual(userEntityList); 44 | }); 45 | 46 | test('method show', async () => { 47 | const result = await userService.show(1); 48 | 49 | expect(result).toEqual(userEntityList[0]); 50 | }); 51 | }); 52 | describe('Update', () => { 53 | test('method update', async () => { 54 | const result = await userService.update(1, updatePutUserDTO); 55 | 56 | expect(result).toEqual(userEntityList[0]); 57 | }); 58 | test('method updatePartial', async () => { 59 | const result = await userService.updatePartial(1, updatePatchUserDTO); 60 | 61 | expect(result).toEqual(userEntityList[0]); 62 | }); 63 | }); 64 | describe('Delete', () => { 65 | test('method delete', async () => { 66 | const result = await userService.delete(1); 67 | 68 | expect(result).toEqual(true); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotFoundException, 4 | BadRequestException, 5 | } from '@nestjs/common'; 6 | import { CreateUserDTO } from './dto/create-user.dto'; 7 | import { UpdatePatchUserDTO } from './dto/update-patch-user.dto'; 8 | import { UpdatePutUserDTO } from './dto/update-put-user.dto'; 9 | import * as bcrypt from 'bcrypt'; 10 | import { UserEntity } from './entity/user.entity'; 11 | import { Repository } from 'typeorm'; 12 | import { InjectRepository } from '@nestjs/typeorm'; 13 | 14 | @Injectable() 15 | export class UserService { 16 | constructor( 17 | @InjectRepository(UserEntity) 18 | private usersRepository: Repository, 19 | ) {} 20 | 21 | async create(data: CreateUserDTO) { 22 | if ( 23 | await this.usersRepository.exist({ 24 | where: { 25 | email: data.email, 26 | }, 27 | }) 28 | ) { 29 | throw new BadRequestException('Este e-mail já está sendo usado.'); 30 | } 31 | 32 | const salt = await bcrypt.genSalt(); 33 | 34 | data.password = await bcrypt.hash(data.password, salt); 35 | 36 | const user = this.usersRepository.create(data); 37 | 38 | return this.usersRepository.save(user); 39 | } 40 | 41 | async list() { 42 | return this.usersRepository.find(); 43 | } 44 | 45 | async show(id: number) { 46 | await this.exists(id); 47 | 48 | return this.usersRepository.findOneBy({ 49 | id, 50 | }); 51 | } 52 | 53 | async update( 54 | id: number, 55 | { email, name, password, birthAt, role }: UpdatePutUserDTO, 56 | ) { 57 | await this.exists(id); 58 | 59 | const salt = await bcrypt.genSalt(); 60 | 61 | password = await bcrypt.hash(password, salt); 62 | 63 | await this.usersRepository.update(id, { 64 | email, 65 | name, 66 | password, 67 | birthAt: birthAt ? new Date(birthAt) : null, 68 | role, 69 | }); 70 | 71 | return this.show(id); 72 | } 73 | 74 | async updatePartial( 75 | id: number, 76 | { email, name, password, birthAt, role }: UpdatePatchUserDTO, 77 | ) { 78 | await this.exists(id); 79 | 80 | const data: any = {}; 81 | 82 | if (birthAt) { 83 | data.birthAt = new Date(birthAt); 84 | } 85 | 86 | if (email) { 87 | data.email = email; 88 | } 89 | 90 | if (name) { 91 | data.name = name; 92 | } 93 | 94 | if (password) { 95 | const salt = await bcrypt.genSalt(); 96 | data.password = await bcrypt.hash(password, salt); 97 | } 98 | 99 | if (role) { 100 | data.role = role; 101 | } 102 | 103 | await this.usersRepository.update(id, data); 104 | 105 | return this.show(id); 106 | } 107 | 108 | async delete(id: number) { 109 | await this.exists(id); 110 | 111 | await this.usersRepository.delete(id); 112 | 113 | return true; 114 | } 115 | 116 | async exists(id: number) { 117 | if ( 118 | !(await this.usersRepository.exist({ 119 | where: { 120 | id, 121 | }, 122 | })) 123 | ) { 124 | throw new NotFoundException(`O usuário ${id} não existe.`); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/utils/somar.ts: -------------------------------------------------------------------------------- 1 | export const somar = (a: number, b: number) => a + b; 2 | -------------------------------------------------------------------------------- /storage/photos/photo-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcodebr/nestjs-typeorm-mysql/ba11099c1169b201d8109e4a2d659ea94fe9e1ba/storage/photos/photo-15.png -------------------------------------------------------------------------------- /storage/photos/photo-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcodebr/nestjs-typeorm-mysql/ba11099c1169b201d8109e4a2d659ea94fe9e1ba/storage/photos/photo-6.png -------------------------------------------------------------------------------- /storage/photos/photo-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcodebr/nestjs-typeorm-mysql/ba11099c1169b201d8109e4a2d659ea94fe9e1ba/storage/photos/photo-test.png -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | import { authRegisterDTO } from '../src/testing/auth-register-dto.mock'; 6 | import { Role } from '../src/enums/role.enum'; 7 | import dataSource from '../typeorm/data-source'; 8 | 9 | describe('AppController (e2e)', () => { 10 | let app: INestApplication; 11 | let accessToken: string; 12 | let userId: number; 13 | 14 | beforeEach(async () => { 15 | const moduleFixture: TestingModule = await Test.createTestingModule({ 16 | imports: [AppModule], 17 | }).compile(); 18 | 19 | app = moduleFixture.createNestApplication(); 20 | await app.init(); 21 | }); 22 | 23 | afterEach(() => { 24 | app.close(); 25 | }); 26 | 27 | it('/ (GET)', () => { 28 | return request(app.getHttpServer()) 29 | .get('/') 30 | .expect(200) 31 | .expect('Hello World!'); 32 | }); 33 | 34 | it('Registrar um novo usuário', async () => { 35 | const response = await request(app.getHttpServer()) 36 | .post('/auth/register') 37 | .send(authRegisterDTO); 38 | 39 | expect(response.statusCode).toEqual(201); 40 | expect(typeof response.body.accessToken).toEqual('string'); 41 | }); 42 | 43 | it('Tentar fazer login com o novo usuário', async () => { 44 | const response = await request(app.getHttpServer()) 45 | .post('/auth/login') 46 | .send({ 47 | email: authRegisterDTO.email, 48 | password: authRegisterDTO.password, 49 | }); 50 | 51 | expect(response.statusCode).toEqual(201); 52 | expect(typeof response.body.accessToken).toEqual('string'); 53 | 54 | accessToken = response.body.accessToken; 55 | }); 56 | 57 | it('Obter os dados do usuário logado', async () => { 58 | const response = await request(app.getHttpServer()) 59 | .post('/auth/me') 60 | .set('Authorization', `bearer ${accessToken}`) 61 | .send(); 62 | 63 | expect(response.statusCode).toEqual(201); 64 | expect(typeof response.body.id).toEqual('number'); 65 | expect(response.body.role).toEqual(Role.User); 66 | }); 67 | 68 | it('Registrar um novo usuário como administrador', async () => { 69 | const response = await request(app.getHttpServer()) 70 | .post('/auth/register') 71 | .send({ 72 | ...authRegisterDTO, 73 | role: Role.Admin, 74 | email: 'henrique@hcode.com.br', 75 | }); 76 | 77 | expect(response.statusCode).toEqual(201); 78 | expect(typeof response.body.accessToken).toEqual('string'); 79 | 80 | accessToken = response.body.accessToken; 81 | }); 82 | 83 | it('Validar de a função do novo usuário ainda é User', async () => { 84 | const response = await request(app.getHttpServer()) 85 | .post('/auth/me') 86 | .set('Authorization', `bearer ${accessToken}`) 87 | .send(); 88 | 89 | expect(response.statusCode).toEqual(201); 90 | expect(typeof response.body.id).toEqual('number'); 91 | expect(response.body.role).toEqual(Role.User); 92 | 93 | userId = response.body.id; 94 | }); 95 | 96 | it('Tentar ver a lista de todos os usuários', async () => { 97 | const response = await request(app.getHttpServer()) 98 | .get('/users') 99 | .set('Authorization', `bearer ${accessToken}`) 100 | .send(); 101 | 102 | expect(response.statusCode).toEqual(403); 103 | expect(response.body.error).toEqual('Forbidden'); 104 | }); 105 | 106 | it('Alterando manualmente o usuário para a função administrador', async () => { 107 | const ds = await dataSource.initialize(); 108 | 109 | const queryRunner = ds.createQueryRunner(); 110 | 111 | await queryRunner.query(` 112 | UPDATE users SET role = ${Role.Admin} WHERE id = ${userId}; 113 | `); 114 | 115 | const rows = await queryRunner.query(` 116 | SELECT * FROM users WHERE id = ${userId}; 117 | `); 118 | 119 | dataSource.destroy(); 120 | 121 | expect(rows.length).toEqual(1); 122 | expect(rows[0].role).toEqual(Role.Admin); 123 | }); 124 | 125 | it('Tentar ver a lista de todos os usuários, agora com acesso', async () => { 126 | const response = await request(app.getHttpServer()) 127 | .get('/users') 128 | .set('Authorization', `bearer ${accessToken}`) 129 | .send(); 130 | 131 | expect(response.statusCode).toEqual(200); 132 | expect(response.body.length).toEqual(2); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /typeorm/data-source.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv' 2 | import {DataSource} from 'typeorm'; 3 | 4 | dotenv.config({ 5 | path: process.env.ENV === 'test' ? '.env.test' : '.env' 6 | }); 7 | 8 | const dataSource = new DataSource({ 9 | type: 'mysql', 10 | host: process.env.DB_HOST, 11 | port: Number(process.env.DB_PORT), 12 | username: process.env.DB_USERNAME, 13 | password: process.env.DB_PASSWORD, 14 | database: process.env.DB_DATABASE, 15 | migrations: [`${__dirname}/migrations/**/*.ts`] 16 | }); 17 | 18 | export default dataSource; -------------------------------------------------------------------------------- /typeorm/migrations/1672191057117-Migrate.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm" 2 | 3 | export class Migrate1672191057117 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable(new Table({ 7 | name: 'users', 8 | columns: [{ 9 | name: 'id', 10 | type: 'int', 11 | isPrimary: true, 12 | isGenerated: true, 13 | generationStrategy: 'increment', 14 | unsigned: true 15 | },{ 16 | name: 'name', 17 | type: 'varchar', 18 | length: '63' 19 | }, { 20 | name: 'email', 21 | type: 'varchar', 22 | length: '127', 23 | isUnique: true 24 | }, { 25 | name: 'password', 26 | type: 'varchar', 27 | length: '127', 28 | }, { 29 | name: 'birthAt', 30 | type: 'date', 31 | isNullable: true 32 | }, { 33 | name: 'role', 34 | type: 'int', 35 | default: '1' 36 | }, { 37 | name: 'createdAt', 38 | type: 'timestamp', 39 | default: 'CURRENT_TIMESTAMP()' 40 | }, { 41 | name: 'updatedAt', 42 | type: 'timestamp', 43 | default: 'CURRENT_TIMESTAMP()' 44 | }] 45 | })); 46 | } 47 | 48 | public async down(queryRunner: QueryRunner): Promise { 49 | await queryRunner.dropTable('users'); 50 | } 51 | 52 | } 53 | 54 | --------------------------------------------------------------------------------