├── .env.example
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── prisma
├── migrations
│ ├── 20221222183909_add_birth_at
│ │ └── migration.sql
│ ├── 20221222184153_birth_at_is_null
│ │ └── migration.sql
│ ├── 20221225214922_add_column_role
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
├── src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── auth
│ ├── auth.controller.ts
│ ├── auth.module.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.ts
├── guards
│ ├── auth.guard.ts
│ └── role.guard.ts
├── interceptors
│ └── log.interceptor.ts
├── main.ts
├── middlewares
│ └── user-id-check.middleware.ts
├── prisma
│ ├── prisma.module.ts
│ └── prisma.service.ts
├── templates
│ └── forget.pug
└── user
│ ├── dto
│ ├── create-user.dto.ts
│ ├── update-patch-user.dto.ts
│ └── update-put-user.dto.ts
│ ├── user.controller.ts
│ ├── user.module.ts
│ └── user.service.ts
├── storage
└── photos
│ └── photo-6.png
├── test
├── app.e2e-spec.ts
└── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | DATABASE_URL="mysql://root:root@localhost:3306/api"
2 | JWT_SECRET="&O,H$2%U_M9kRu{u&@dxGrG+pwReQse"
--------------------------------------------------------------------------------
/.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"
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/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 | "test:e2e": "jest --config ./test/jest-e2e.json"
22 | },
23 | "dependencies": {
24 | "@nestjs-modules/mailer": "^1.8.1",
25 | "@nestjs/common": "^9.0.0",
26 | "@nestjs/config": "^2.2.0",
27 | "@nestjs/core": "^9.0.0",
28 | "@nestjs/jwt": "^9.0.0",
29 | "@nestjs/mapped-types": "^1.2.0",
30 | "@nestjs/platform-express": "^9.0.0",
31 | "@nestjs/throttler": "^3.1.0",
32 | "@prisma/client": "^4.8.0",
33 | "bcrypt": "^5.1.0",
34 | "class-transformer": "^0.5.1",
35 | "class-validator": "^0.13.2",
36 | "nodemailer": "^6.8.0",
37 | "pug": "^3.0.2",
38 | "reflect-metadata": "^0.1.13",
39 | "rimraf": "^3.0.2",
40 | "rxjs": "^7.2.0"
41 | },
42 | "devDependencies": {
43 | "@nestjs/cli": "^9.0.0",
44 | "@nestjs/schematics": "^9.0.0",
45 | "@nestjs/testing": "^9.0.0",
46 | "@types/bcrypt": "^5.0.0",
47 | "@types/express": "^4.17.13",
48 | "@types/jest": "28.1.8",
49 | "@types/multer": "^1.4.7",
50 | "@types/node": "^16.0.0",
51 | "@types/nodemailer": "^6.4.7",
52 | "@types/supertest": "^2.0.11",
53 | "@typescript-eslint/eslint-plugin": "^5.0.0",
54 | "@typescript-eslint/parser": "^5.0.0",
55 | "eslint": "^8.0.1",
56 | "eslint-config-prettier": "^8.3.0",
57 | "eslint-plugin-prettier": "^4.0.0",
58 | "jest": "28.1.3",
59 | "prettier": "^2.3.2",
60 | "prisma": "^4.8.0",
61 | "source-map-support": "^0.5.20",
62 | "supertest": "^6.1.3",
63 | "ts-jest": "28.0.8",
64 | "ts-loader": "^9.2.3",
65 | "ts-node": "^10.0.0",
66 | "tsconfig-paths": "4.1.0",
67 | "typescript": "^4.7.4"
68 | },
69 | "jest": {
70 | "moduleFileExtensions": [
71 | "js",
72 | "json",
73 | "ts"
74 | ],
75 | "rootDir": "src",
76 | "testRegex": ".*\\.spec\\.ts$",
77 | "transform": {
78 | "^.+\\.(t|j)s$": "ts-jest"
79 | },
80 | "collectCoverageFrom": [
81 | "**/*.(t|j)s"
82 | ],
83 | "coverageDirectory": "../coverage",
84 | "testEnvironment": "node"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/prisma/migrations/20221222183909_add_birth_at/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE `users` (
3 | `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
4 | `name` VARCHAR(63) NOT NULL,
5 | `email` VARCHAR(127) NOT NULL,
6 | `password` VARCHAR(127) NOT NULL,
7 | `birthAt` DATE NOT NULL,
8 | `createdAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
9 | `updatedtAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
10 |
11 | PRIMARY KEY (`id`)
12 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
13 |
--------------------------------------------------------------------------------
/prisma/migrations/20221222184153_birth_at_is_null/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE `users` MODIFY `birthAt` DATE NULL;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20221225214922_add_column_role/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE `users` ADD COLUMN `role` INTEGER NOT NULL DEFAULT 1;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "mysql"
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "mysql"
7 | url = env("DATABASE_URL")
8 | }
9 |
10 | model User {
11 | id Int @id @default(autoincrement()) @db.UnsignedInt
12 | name String @db.VarChar(63)
13 | email String @db.VarChar(127)
14 | password String @db.VarChar(127)
15 | birthAt DateTime? @db.Date
16 | role Int @default(1)
17 | createdAt DateTime @default(now()) @db.Timestamp(0)
18 | updatedtAt DateTime @default(now()) @db.Timestamp(0)
19 |
20 | @@map("users")
21 | }
22 |
--------------------------------------------------------------------------------
/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 |
13 | @Module({
14 | imports: [
15 | ConfigModule.forRoot(),
16 | ThrottlerModule.forRoot({
17 | ttl:60,
18 | limit:100
19 | }),
20 | forwardRef(() => UserModule),
21 | forwardRef(() => AuthModule),
22 | MailerModule.forRoot({
23 | transport: {
24 | host: 'smtp.ethereal.email',
25 | port: 587,
26 | auth: {
27 | user: 'marcelo44@ethereal.email',
28 | pass: 'YutK7Qq3QFwDDDahfC'
29 | }
30 | },
31 | defaults: {
32 | from: '"Hcode" ',
33 | },
34 | template: {
35 | dir: __dirname + '/templates',
36 | adapter: new PugAdapter(),
37 | options: {
38 | strict: true,
39 | },
40 | },
41 | }),
42 | ],
43 | controllers: [AppController],
44 | providers: [AppService, {
45 | provide: APP_GUARD,
46 | useClass: ThrottlerGuard
47 | }],
48 | exports: [AppService]
49 | })
50 | export class AppModule {}
51 |
--------------------------------------------------------------------------------
/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.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Post, Body, UseGuards, UseInterceptors, BadRequestException, ParseFilePipe, FileTypeValidator, MaxFileSizeValidator } from "@nestjs/common";
2 | import { UploadedFile, UploadedFiles } from "@nestjs/common/decorators";
3 | import { FileInterceptor, FilesInterceptor, FileFieldsInterceptor } from "@nestjs/platform-express";
4 | import { User } from "src/decorators/user.decorator";
5 | import { AuthGuard } from "src/guards/auth.guard";
6 | import { UserService } from "src/user/user.service";
7 | import { AuthService } from "./auth.service";
8 | import { AuthForgetDTO } from "./dto/auth-forget.dto";
9 | import { AuthLoginDTO } from "./dto/auth-login.dto";
10 | import { AuthRegisterDTO } from "./dto/auth-register.dto";
11 | import { AuthResetDTO } from "./dto/auth-reset.dto";
12 | import {join} from 'path';
13 | import { FileService } from "src/file/file.service";
14 |
15 | @Controller('auth')
16 | export class AuthController {
17 |
18 | constructor(
19 | private readonly userService: UserService,
20 | private readonly authService: AuthService,
21 | private readonly fileService: FileService
22 | ){}
23 |
24 | @Post('login')
25 | async login(@Body() {email, password}: AuthLoginDTO) {
26 | return this.authService.login(email, password);
27 | }
28 |
29 | @Post('register')
30 | async register(@Body() body: AuthRegisterDTO) {
31 | return this.authService.register(body);
32 | }
33 |
34 | @Post('forget')
35 | async forget(@Body() {email}: AuthForgetDTO) {
36 | return this.authService.forget(email);
37 | }
38 |
39 | @Post('reset')
40 | async reset(@Body() {password, token}: AuthResetDTO) {
41 | return this.authService.reset(password, token);
42 | }
43 |
44 | @UseGuards(AuthGuard)
45 | @Post('me')
46 | async me(@User() user) {
47 | return {user};
48 | }
49 |
50 | @UseInterceptors(FileInterceptor('file'))
51 | @UseGuards(AuthGuard)
52 | @Post('photo')
53 | async uploadPhoto(
54 | @User() user,
55 | @UploadedFile(new ParseFilePipe({
56 | validators: [
57 | new FileTypeValidator({fileType:'image/png'}),
58 | new MaxFileSizeValidator({maxSize: 1024 * 50}),
59 | ]
60 | })) photo: Express.Multer.File
61 | ) {
62 |
63 | const path = join(__dirname, '..', '..', 'storage', 'photos', `photo-${user.id}.png`);
64 |
65 | try {
66 | await this.fileService.upload(photo, path);
67 | } catch (e) {
68 | throw new BadRequestException(e)
69 | }
70 |
71 | return {photo};
72 | }
73 |
74 | @UseInterceptors(FilesInterceptor('files'))
75 | @UseGuards(AuthGuard)
76 | @Post('files')
77 | async uploadFiles(@User() user, @UploadedFiles() files: Express.Multer.File[]) {
78 | return files
79 | }
80 |
81 | @UseInterceptors(FileFieldsInterceptor([{
82 | name: 'photo',
83 | maxCount: 1
84 | }, {
85 | name: 'documents',
86 | maxCount: 10
87 | }]))
88 | @UseGuards(AuthGuard)
89 | @Post('files-fields')
90 | async uploadFilesFields(@User() user, @UploadedFiles() files: {photo: Express.Multer.File, documents: Express.Multer.File[]}) {
91 | return files;
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, forwardRef } from "@nestjs/common";
2 | import { JwtModule } from "@nestjs/jwt";
3 | import { FileModule } from "src/file/file.module";
4 | import { PrismaModule } from "src/prisma/prisma.module";
5 | import { UserModule } from "src/user/user.module";
6 | import { AuthController } from "./auth.controller";
7 | import { AuthService } from "./auth.service";
8 |
9 | @Module({
10 | imports: [
11 | JwtModule.register({
12 | secret: process.env.JWT_SECRET
13 | }),
14 | forwardRef(() => UserModule),
15 | PrismaModule,
16 | FileModule
17 | ],
18 | controllers: [AuthController],
19 | providers: [AuthService],
20 | exports: [AuthService]
21 | })
22 | export class AuthModule {
23 |
24 | }
--------------------------------------------------------------------------------
/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 { User } from '@prisma/client';
5 | import { PrismaService } from 'src/prisma/prisma.service'
6 | import { UserService } from 'src/user/user.service';
7 | import { AuthRegisterDTO } from './dto/auth-register.dto';
8 | import * as bcrypt from 'bcrypt';
9 | import { MailerService } from '@nestjs-modules/mailer/dist';
10 |
11 | @Injectable()
12 | export class AuthService {
13 |
14 | private issuer = 'login';
15 | private audience = 'users';
16 |
17 | constructor(
18 | private readonly jwtService: JwtService,
19 | private readonly prisma: PrismaService,
20 | private readonly userService: UserService,
21 | private readonly mailer: MailerService
22 | ) {}
23 |
24 | createToken(user:User) {
25 | return {
26 | accessToken: this.jwtService.sign({
27 | id: user.id,
28 | name: user.name,
29 | email: user.email
30 | }, {
31 | expiresIn: "7 days",
32 | subject: String(user.id),
33 | issuer: this.issuer,
34 | audience: this.audience,
35 | })
36 | }
37 | }
38 |
39 | checkToken(token: string) {
40 | try {
41 | const data = this.jwtService.verify(token, {
42 | issuer: this.issuer,
43 | audience: this.audience,
44 | });
45 |
46 | return data;
47 | } catch (e) {
48 | throw new BadRequestException(e);
49 | }
50 | }
51 |
52 | isValidToken(token: string) {
53 | try {
54 | this.checkToken(token);
55 | return true;
56 | } catch (e) {
57 | return false;
58 | }
59 | }
60 |
61 | async login(email:string, password:string) {
62 |
63 | const user = await this.prisma.user.findFirst({
64 | where: {
65 | email
66 | }
67 | });
68 |
69 | if (!user) {
70 | throw new UnauthorizedException('E-mail e/ou senha incorretos.');
71 | }
72 |
73 | if (!await bcrypt.compare(password, user.password)) {
74 | throw new UnauthorizedException('E-mail e/ou senha incorretos.');
75 | }
76 |
77 | return this.createToken(user);
78 |
79 | }
80 |
81 | async forget(email: string) {
82 |
83 | const user = await this.prisma.user.findFirst({
84 | where: {
85 | email
86 | }
87 | });
88 |
89 | if (!user) {
90 | throw new UnauthorizedException('E-mail está incorreto.');
91 | }
92 |
93 | const token = this.jwtService.sign({
94 | id: user.id
95 | }, {
96 | expiresIn: "30 minutes",
97 | subject: String(user.id),
98 | issuer: 'forget',
99 | audience: 'users',
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 true;
113 |
114 | }
115 |
116 | async reset(password: string, token: string) {
117 |
118 | try {
119 | const data:any = this.jwtService.verify(token, {
120 | issuer: 'forget',
121 | audience: 'users',
122 | });
123 |
124 | if (isNaN(Number(data.id))) {
125 | throw new BadRequestException("Token é inválido.");
126 | }
127 |
128 | const salt = await bcrypt.genSalt();
129 | password = await bcrypt.hash(password, salt);
130 |
131 | const user = await this.prisma.user.update({
132 | where: {
133 | id: Number(data.id),
134 | },
135 | data: {
136 | password,
137 | },
138 | });
139 |
140 | return this.createToken(user);
141 |
142 | } catch (e) {
143 | throw new BadRequestException(e);
144 | }
145 |
146 | }
147 |
148 | async register(data: AuthRegisterDTO) {
149 |
150 | const user = await this.userService.create(data);
151 |
152 | return this.createToken(user);
153 |
154 | }
155 |
156 | }
--------------------------------------------------------------------------------
/src/auth/dto/auth-forget.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail } from "class-validator";
2 |
3 | export class AuthForgetDTO {
4 |
5 | @IsEmail()
6 | email:string;
7 |
8 | }
--------------------------------------------------------------------------------
/src/auth/dto/auth-login.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsString, MinLength } from "class-validator";
2 |
3 | export class AuthLoginDTO {
4 |
5 | @IsEmail()
6 | email:string;
7 |
8 | @IsString()
9 | @MinLength(6)
10 | password:string;
11 |
12 | }
--------------------------------------------------------------------------------
/src/auth/dto/auth-me.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsJWT } from "class-validator";
2 |
3 | export class AuthMeDTO {
4 |
5 | @IsJWT()
6 | token: string;
7 |
8 | }
--------------------------------------------------------------------------------
/src/auth/dto/auth-register.dto.ts:
--------------------------------------------------------------------------------
1 | import { CreateUserDTO } from "src/user/dto/create-user.dto";
2 |
3 | export class AuthRegisterDTO extends CreateUserDTO {
4 |
5 |
6 |
7 | }
--------------------------------------------------------------------------------
/src/auth/dto/auth-reset.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsJWT, IsString, MinLength } from "class-validator";
2 |
3 | export class AuthResetDTO {
4 |
5 | @IsString()
6 | @MinLength(6)
7 | password: string;
8 |
9 | @IsJWT()
10 | token: string;
11 |
12 | }
--------------------------------------------------------------------------------
/src/decorators/param-id.decorator.ts:
--------------------------------------------------------------------------------
1 | import {createParamDecorator,ExecutionContext} from '@nestjs/common';
2 |
3 | export const ParamId = createParamDecorator((_data: unknown, context: ExecutionContext) => {
4 |
5 | return Number(context.switchToHttp().getRequest().params.id);
6 |
7 | });
--------------------------------------------------------------------------------
/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);
--------------------------------------------------------------------------------
/src/decorators/user.decorator.ts:
--------------------------------------------------------------------------------
1 | import {createParamDecorator,ExecutionContext, NotFoundException} from '@nestjs/common';
2 |
3 | export const User = createParamDecorator((filter: string, context: ExecutionContext) => {
4 |
5 | const request = context.switchToHttp().getRequest();
6 |
7 | if (request.user) {
8 |
9 | if (filter) {
10 | return request.user[filter];
11 | } else {
12 | return request.user;
13 | }
14 |
15 | } else {
16 |
17 | throw new NotFoundException("Usuário não encontrado no Request. Use o AuthGuard para obter o usuário.");
18 |
19 | }
20 |
21 | });
--------------------------------------------------------------------------------
/src/enums/role.enum.ts:
--------------------------------------------------------------------------------
1 | export enum Role {
2 | User = 1,
3 | Admin = 2
4 | }
--------------------------------------------------------------------------------
/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 {}
--------------------------------------------------------------------------------
/src/file/file.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@nestjs/common";
2 | import { writeFile } from "fs/promises";
3 |
4 | @Injectable()
5 | export class FileService {
6 |
7 | async upload(file: Express.Multer.File, path: string) {
8 | return writeFile(path, file.buffer);
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/src/guards/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import {CanActivate,Injectable,ExecutionContext} from '@nestjs/common';
2 | import { AuthService } from 'src/auth/auth.service';
3 | import { UserService } from 'src/user/user.service';
4 |
5 | @Injectable()
6 | export class AuthGuard implements CanActivate {
7 |
8 | constructor(
9 | private readonly authService: AuthService,
10 | private readonly userService: UserService
11 | ) {}
12 |
13 | async canActivate(context: ExecutionContext) {
14 |
15 | const request = context.switchToHttp().getRequest();
16 | const {authorization} = request.headers;
17 | try {
18 | const data = this.authService.checkToken((authorization ?? '').split(' ')[1]);
19 |
20 | request.tokenPayload = data;
21 |
22 | request.user = await this.userService.show(data.id);
23 |
24 | return true;
25 |
26 | } catch (e) {
27 | return false;
28 | }
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/guards/role.guard.ts:
--------------------------------------------------------------------------------
1 | import {CanActivate,Injectable,ExecutionContext} from '@nestjs/common';
2 | import { Reflector } from '@nestjs/core';
3 | import { ROLES_KEY } from 'src/decorators/roles.decorator';
4 | import { Role } from 'src/enums/role.enum';
5 |
6 | @Injectable()
7 | export class RoleGuard implements CanActivate {
8 |
9 | constructor(
10 | private readonly reflector: Reflector
11 | ) {}
12 |
13 | async canActivate(context: ExecutionContext) {
14 |
15 |
16 |
17 | const requeridRoles = this.reflector.getAllAndOverride(ROLES_KEY, [context.getHandler(), context.getClass()])
18 |
19 | console.log({requeridRoles})
20 |
21 | if (!requeridRoles) {
22 | return true;
23 | }
24 |
25 | const {user} = context.switchToHttp().getRequest();
26 |
27 | const rolesFilted = requeridRoles.filter(role => role === user.role);
28 |
29 | return rolesFilted.length > 0;
30 |
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/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 |
7 | intercept(context: ExecutionContext, next: CallHandler): Observable {
8 |
9 | const dt = Date.now();
10 |
11 | return next.handle().pipe(tap(() => {
12 |
13 | const request = context.switchToHttp().getRequest();
14 |
15 | console.log(`URL: ${request.url}`);
16 | console.log(`METHOD: ${request.method}`);
17 | console.log(`Execução levou: ${Date.now() - dt} milisegundos.`);
18 |
19 | }));
20 |
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 | import { ValidationPipe } from '@nestjs/common';
4 | import { LogInterceptor } from './interceptors/log.interceptor';
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(AppModule);
8 |
9 | app.enableCors();
10 |
11 | app.useGlobalPipes(new ValidationPipe());
12 | //app.useGlobalInterceptors(new LogInterceptor());
13 |
14 | await app.listen(3000);
15 | }
16 |
17 | bootstrap();
18 |
--------------------------------------------------------------------------------
/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 |
7 | console.log('UserIdCheckMiddleware', 'antes')
8 |
9 | if (isNaN(Number(req.params.id)) || Number(req.params.id) <= 0) {
10 | throw new BadRequestException('ID inválido!');
11 | }
12 |
13 | console.log('UserIdCheckMiddleware', 'depois')
14 |
15 | next();
16 |
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/src/prisma/prisma.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "@nestjs/common";
2 | import { PrismaService } from "./prisma.service";
3 |
4 | @Module({
5 | providers: [PrismaService],
6 | exports: [PrismaService]
7 | })
8 | export class PrismaModule {}
--------------------------------------------------------------------------------
/src/prisma/prisma.service.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication, Injectable, OnModuleInit } from "@nestjs/common";
2 | import { PrismaClient } from "@prisma/client";
3 |
4 | @Injectable()
5 | export class PrismaService extends PrismaClient implements OnModuleInit {
6 |
7 | async onModuleInit() {
8 | await this.$connect();
9 | }
10 |
11 | async enableShutdownHooks(app: INestApplication) {
12 | this.$on('beforeExit', async () => {
13 | await app.close();
14 | });
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/templates/forget.pug:
--------------------------------------------------------------------------------
1 | p #{name} você solicitou a recuperação de senha, por favor use o token #{token}
--------------------------------------------------------------------------------
/src/user/dto/create-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsString, IsEmail, MinLength, IsOptional, IsDateString, IsEnum } from "class-validator";
2 | import { Role } from "src/enums/role.enum";
3 |
4 | export class CreateUserDTO {
5 |
6 | @IsString()
7 | name: string;
8 |
9 | @IsEmail()
10 | email: string;
11 |
12 | @IsString()
13 | @MinLength(6)
14 | password: string;
15 |
16 | @IsOptional()
17 | @IsDateString()
18 | birthAt: string;
19 |
20 | @IsOptional()
21 | @IsEnum(Role)
22 | role: number;
23 |
24 | }
--------------------------------------------------------------------------------
/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 |
6 |
7 |
8 | }
--------------------------------------------------------------------------------
/src/user/dto/update-put-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { CreateUserDTO } from "./create-user.dto";
2 |
3 | export class UpdatePutUserDTO extends CreateUserDTO {
4 |
5 |
6 |
7 | }
--------------------------------------------------------------------------------
/src/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import {Controller, Post, Body, Get, Put, Patch, Delete, UseInterceptors,UseGuards} from "@nestjs/common"
2 | import { ParamId } from "src/decorators/param-id.decorator";
3 | import { Roles } from "src/decorators/roles.decorator";
4 | import { Role } from "src/enums/role.enum";
5 | import { AuthGuard } from "src/guards/auth.guard";
6 | import { RoleGuard } from "src/guards/role.guard";
7 | import { LogInterceptor } from "src/interceptors/log.interceptor";
8 | import { CreateUserDTO } from "./dto/create-user.dto";
9 | import { UpdatePatchUserDTO } from "./dto/update-patch-user.dto";
10 | import { UpdatePutUserDTO } from "./dto/update-put-user.dto";
11 | import { UserService } from "./user.service";
12 |
13 | @Roles(Role.Admin)
14 | @UseGuards(AuthGuard, RoleGuard)
15 | @UseInterceptors(LogInterceptor)
16 | @Controller('users')
17 | export class UserController {
18 |
19 | constructor(private readonly userService: UserService){}
20 |
21 | @Post()
22 | async create(@Body() data: CreateUserDTO) {
23 | return this.userService.create(data);
24 | }
25 |
26 | @Get()
27 | async list() {
28 | return this.userService.list();
29 | }
30 |
31 | @Get(':id')
32 | async show(@ParamId() id: number) {
33 | console.log({id});
34 | return this.userService.show(id);
35 | }
36 |
37 | @Put(':id')
38 | async update(@Body() data: UpdatePutUserDTO, @ParamId() id: number) {
39 | return this.userService.update(id, data);
40 | }
41 |
42 | @Patch(':id')
43 | async updatePartial(@Body() data: UpdatePatchUserDTO, @ParamId() id: number) {
44 | return this.userService.updatePartial(id, data);
45 | }
46 |
47 | @Delete(':id')
48 | async delete(@ParamId() id: number) {
49 | return this.userService.delete(id);
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/src/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, NestModule, MiddlewareConsumer, RequestMethod, forwardRef } from "@nestjs/common";
2 | import { AuthModule } from "src/auth/auth.module";
3 | import { UserIdCheckMiddleware } from "src/middlewares/user-id-check.middleware";
4 | import { PrismaModule } from "src/prisma/prisma.module";
5 | import { UserController } from "./user.controller";
6 | import { UserService } from "./user.service";
7 |
8 | @Module({
9 | imports: [
10 | PrismaModule,
11 | forwardRef(() => AuthModule)
12 | ],
13 | controllers: [UserController],
14 | providers: [UserService],
15 | exports: [UserService]
16 | })
17 | export class UserModule implements NestModule {
18 |
19 | configure(consumer: MiddlewareConsumer) {
20 | consumer.apply(UserIdCheckMiddleware).forRoutes({
21 | path: 'users/:id',
22 | method: RequestMethod.ALL
23 | });
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/user/user.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NotFoundException } from "@nestjs/common";
2 | import { PrismaService } from "src/prisma/prisma.service";
3 | import { CreateUserDTO } from "./dto/create-user.dto";
4 | import { UpdatePatchUserDTO } from "./dto/update-patch-user.dto";
5 | import { UpdatePutUserDTO } from "./dto/update-put-user.dto";
6 | import * as bcrypt from 'bcrypt';
7 |
8 | @Injectable()
9 | export class UserService {
10 |
11 | constructor(private readonly prisma: PrismaService) {}
12 |
13 | async create(data: CreateUserDTO) {
14 |
15 | const salt = await bcrypt.genSalt();
16 |
17 | data.password = await bcrypt.hash(data.password, salt);
18 |
19 | return this.prisma.user.create({
20 | data,
21 | });
22 |
23 | }
24 |
25 | async list() {
26 |
27 | return this.prisma.user.findMany();
28 |
29 | }
30 |
31 | async show(id: number) {
32 |
33 | await this.exists(id);
34 |
35 | return this.prisma.user.findUnique({
36 | where: {
37 | id,
38 | }
39 | })
40 |
41 | }
42 |
43 | async update(id: number, {email, name, password, birthAt, role}: UpdatePutUserDTO) {
44 |
45 | await this.exists(id);
46 |
47 | const salt = await bcrypt.genSalt();
48 |
49 | password = await bcrypt.hash(password, salt);
50 |
51 | return this.prisma.user.update({
52 | data:{email, name, password, birthAt: birthAt ? new Date(birthAt) : null, role},
53 | where: {
54 | id
55 | }
56 | });
57 | }
58 |
59 | async updatePartial(id: number, {email, name, password, birthAt, role}: UpdatePatchUserDTO) {
60 |
61 | await this.exists(id);
62 |
63 | const data: any = {};
64 |
65 | if (birthAt) {
66 | data.birthAt = new Date(birthAt);
67 | }
68 |
69 | if (email) {
70 | data.email = email;
71 | }
72 |
73 | if (name) {
74 | data.name = name;
75 | }
76 |
77 | if (password) {
78 | const salt = await bcrypt.genSalt();
79 | data.password = await bcrypt.hash(password, salt);
80 | }
81 |
82 | if (role) {
83 | data.role = role;
84 | }
85 |
86 | return this.prisma.user.update({
87 | data,
88 | where: {
89 | id
90 | }
91 | });
92 | }
93 |
94 | async delete(id: number) {
95 |
96 | await this.exists(id);
97 |
98 | return this.prisma.user.delete({
99 | where: {
100 | id
101 | }
102 | });
103 | }
104 |
105 | async exists(id: number) {
106 | if (!(await this.prisma.user.count({
107 | where: {
108 | id
109 | }
110 | }))) {
111 | throw new NotFoundException(`O usuário ${id} não existe.`);
112 | }
113 | }
114 |
115 | }
--------------------------------------------------------------------------------
/storage/photos/photo-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hcodebr/nestjs-prisma-mysql/3a36238e576e965df7e79e1759a455df3397ab9f/storage/photos/photo-6.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 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------