├── .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 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------