From df15ac373b0d61b009c0a0aef4900646b470ca15 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:39:41 -0300 Subject: [PATCH 01/13] (#129) corrige nome arquivo --- src/app.controler.ts | 12 ------------ src/app.controller.spec.ts | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 src/app.controler.ts diff --git a/src/app.controler.ts b/src/app.controler.ts deleted file mode 100644 index a41a124..0000000 --- a/src/app.controler.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly service: AppService) {} - - @Get('health-check') - heathCheck() { - return this.service.heathCheck(); - } -} diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index 5cd149d..3068c57 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controler'; +import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { From 52a965c7ac63f29b08b2b1f78c1c7d0c13355f3c Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:40:11 -0300 Subject: [PATCH 02/13] (#129) corrige mensagem de sucesso --- src/shared/classes/http-response.spec.ts | 6 +++--- src/shared/classes/http-response.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/classes/http-response.spec.ts b/src/shared/classes/http-response.spec.ts index 06c4e70..f2a0b6c 100644 --- a/src/shared/classes/http-response.spec.ts +++ b/src/shared/classes/http-response.spec.ts @@ -7,10 +7,10 @@ describe('HttpResponse', () => { it('should create message with payload text', () => { const response = new HttpResponse({}); - const created = response.onSuccess('MENSAGENS.SALVO-SUCESSO'); + const created = response.onSuccess('Salvo com sucesso!'); const expected = { - message: 'MENSAGENS.SALVO-SUCESSO', + message: 'Salvo com sucesso!', data: {}, }; @@ -22,7 +22,7 @@ describe('HttpResponse', () => { const created = response.onCreated(); const expected = { - message: 'MENSAGENS.SALVO-SUCESSO', + message: 'Salvo com sucesso!', data: {}, }; diff --git a/src/shared/classes/http-response.ts b/src/shared/classes/http-response.ts index c92bdfe..daa783c 100644 --- a/src/shared/classes/http-response.ts +++ b/src/shared/classes/http-response.ts @@ -15,7 +15,7 @@ export class HttpResponse implements Response { } onCreated(): Response { - this.message = 'MENSAGENS.SALVO-SUCESSO'; + this.message = 'Salvo com sucesso!'; return this; } From 896d1b48cca91dddd0446f50ffbb7d2f858b52ec Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:40:45 -0300 Subject: [PATCH 03/13] (#129) adiciona entidade e dtos usuario --- src/usuario/dto/create-usuario.dto.ts | 36 ++++++++++++++++++++++++++ src/usuario/dto/update-usuario.dto.ts | 4 +++ src/usuario/entities/usuario.entity.ts | 34 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/usuario/dto/create-usuario.dto.ts create mode 100644 src/usuario/dto/update-usuario.dto.ts create mode 100644 src/usuario/entities/usuario.entity.ts diff --git a/src/usuario/dto/create-usuario.dto.ts b/src/usuario/dto/create-usuario.dto.ts new file mode 100644 index 0000000..a66dc1f --- /dev/null +++ b/src/usuario/dto/create-usuario.dto.ts @@ -0,0 +1,36 @@ +import { + IsBoolean, + IsEmail, + IsNotEmpty, + IsOptional, + IsString, + MaxLength, + MinLength, +} from 'class-validator'; + +export class CreateUsuarioDto { + @IsString() + @IsNotEmpty() + @MaxLength(60) + @MinLength(5) + nome!: string; + + @IsOptional() + @IsString() + foto?: string; + + @IsString() + @IsNotEmpty() + @IsEmail() + @MaxLength(100) + email!: string; + + @IsString() + @IsNotEmpty() + @MaxLength(100) + senha!: string; + + @IsOptional() + @IsBoolean() + admin?: boolean; +} diff --git a/src/usuario/dto/update-usuario.dto.ts b/src/usuario/dto/update-usuario.dto.ts new file mode 100644 index 0000000..a2b8fbc --- /dev/null +++ b/src/usuario/dto/update-usuario.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateUsuarioDto } from './create-usuario.dto'; + +export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) {} diff --git a/src/usuario/entities/usuario.entity.ts b/src/usuario/entities/usuario.entity.ts new file mode 100644 index 0000000..631694e --- /dev/null +++ b/src/usuario/entities/usuario.entity.ts @@ -0,0 +1,34 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { CreateUsuarioDto } from '../dto/create-usuario.dto'; +import { UpdateUsuarioDto } from '../dto/update-usuario.dto'; + +@Entity({ name: 'usuario' }) +export class Usuario { + @PrimaryGeneratedColumn() + id!: number; + + @Column('varchar', { length: 60 }) + nome!: string; + + @Column('bytea', { nullable: true }) + foto!: Buffer; + + @Column('varchar', { length: 100, unique: true }) + email!: string; + + @Column('varchar', { + length: 100, + select: false, + }) + senha!: string; + + @Column({ + type: 'boolean', + default: false, + }) + admin!: boolean; + + constructor(createUsuarioDto: CreateUsuarioDto | UpdateUsuarioDto) { + Object.assign(this, createUsuarioDto); + } +} From cca90f5eab88eef6ef533ff57b30e5cc09b1ab42 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:42:53 -0300 Subject: [PATCH 04/13] (#129) corrige nome healthCheck --- src/app.controller.ts | 12 ++++++++++++ src/app.module.ts | 2 +- src/app.service.ts | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/app.controller.ts diff --git a/src/app.controller.ts b/src/app.controller.ts new file mode 100644 index 0000000..cea3966 --- /dev/null +++ b/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly service: AppService) {} + + @Get('health-check') + healthCheck() { + return this.service.healthCheck(); + } +} diff --git a/src/app.module.ts b/src/app.module.ts index ce0a9b0..64e3009 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AppController } from './app.controler'; +import { AppController } from './app.controller'; import { AppService } from './app.service'; import { DbModule } from './config/db/db.module'; import { DbService } from './config/db/db.service'; diff --git a/src/app.service.ts b/src/app.service.ts index c054b21..fb773a7 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - heathCheck() { + healthCheck() { return { message: 'GEROcuidadoApiUsuario health check Ok!', data: {}, From 00a9b8b20d00ea51fe0b29005acd737452bf02c9 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:43:54 -0300 Subject: [PATCH 05/13] (#129) adiciona controller e service usuario com rota create --- src/app.module.ts | 2 ++ src/usuario/usuario.controller.ts | 17 +++++++++++++++++ src/usuario/usuario.module.ts | 14 ++++++++++++++ src/usuario/usuario.service.ts | 20 ++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 src/usuario/usuario.controller.ts create mode 100644 src/usuario/usuario.module.ts create mode 100644 src/usuario/usuario.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 64e3009..194408c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { DbModule } from './config/db/db.module'; import { DbService } from './config/db/db.service'; +import { UsuarioModule } from './usuario/usuario.module'; const ENV = process.env.NODE_ENV; @@ -19,6 +20,7 @@ const ENV = process.env.NODE_ENV; useClass: DbService, }), DbModule, + UsuarioModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts new file mode 100644 index 0000000..511072d --- /dev/null +++ b/src/usuario/usuario.controller.ts @@ -0,0 +1,17 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { HttpResponse } from '../shared/classes/http-response'; +import { Response } from '../shared/interceptors/data-transform.interceptor'; +import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { Usuario } from './entities/usuario.entity'; +import { UsuarioService } from './usuario.service'; + +@Controller() +export class UsuarioController { + constructor(private readonly _service: UsuarioService) {} + + @Post() + async create(@Body() body: CreateUsuarioDto): Promise> { + const created = await this._service.create(body); + return new HttpResponse(created).onCreated(); + } +} diff --git a/src/usuario/usuario.module.ts b/src/usuario/usuario.module.ts new file mode 100644 index 0000000..a2c37b8 --- /dev/null +++ b/src/usuario/usuario.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Usuario } from './entities/usuario.entity'; +import { UsuarioController } from './usuario.controller'; +import { UsuarioService } from './usuario.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Usuario])], + controllers: [UsuarioController], + providers: [UsuarioService, Repository], + exports: [UsuarioService], +}) +export class UsuarioModule {} diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts new file mode 100644 index 0000000..53466a6 --- /dev/null +++ b/src/usuario/usuario.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { Usuario } from './entities/usuario.entity'; + +@Injectable() +export class UsuarioService { + constructor( + @InjectRepository(Usuario) + private readonly _repository: Repository, + ) {} + + async create(body: CreateUsuarioDto): Promise { + const usuario = new Usuario(body); + + // TODO verificar email e criptografar senha + return this._repository.save(usuario); + } +} From ca6163c1822da2b8fb76ba250391e0d2170738eb Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:44:04 -0300 Subject: [PATCH 06/13] (#129) adiciona migration do usuario --- src/migration/1697762741479-CreateTableUsuario.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/migration/1697762741479-CreateTableUsuario.ts diff --git a/src/migration/1697762741479-CreateTableUsuario.ts b/src/migration/1697762741479-CreateTableUsuario.ts new file mode 100644 index 0000000..09625df --- /dev/null +++ b/src/migration/1697762741479-CreateTableUsuario.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateTableUsuario1697762741479 implements MigrationInterface { + name = 'CreateTableUsuario1697762741479'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "usuario" ("id" SERIAL NOT NULL, "nome" character varying(60) NOT NULL, "foto" bytea, "email" character varying(100) NOT NULL, "senha" character varying(100) NOT NULL, "admin" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_2863682842e688ca198eb25c124" UNIQUE ("email"), CONSTRAINT "PK_a56c58e5cabaa04fb2c98d2d7e2" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "usuario"`); + } +} From 05c66417c29549a636593856e2b86075a4bdb047 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:48:48 -0300 Subject: [PATCH 07/13] (#129) adiciona findOne usuario --- src/usuario/usuario.controller.ts | 8 +++++++- src/usuario/usuario.service.ts | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts index 511072d..fcf31fa 100644 --- a/src/usuario/usuario.controller.ts +++ b/src/usuario/usuario.controller.ts @@ -1,6 +1,7 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { HttpResponse } from '../shared/classes/http-response'; import { Response } from '../shared/interceptors/data-transform.interceptor'; +import { IdValidator } from '../shared/validators/id.validator'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { Usuario } from './entities/usuario.entity'; import { UsuarioService } from './usuario.service'; @@ -14,4 +15,9 @@ export class UsuarioController { const created = await this._service.create(body); return new HttpResponse(created).onCreated(); } + + @Get(':id') + async findOne(@Param() param: IdValidator): Promise { + return this._service.findOne(param.id); + } } diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts index 53466a6..833509f 100644 --- a/src/usuario/usuario.service.ts +++ b/src/usuario/usuario.service.ts @@ -17,4 +17,8 @@ export class UsuarioService { // TODO verificar email e criptografar senha return this._repository.save(usuario); } + + async findOne(id: number) { + return this._repository.findOneOrFail({ where: { id } }); + } } From bb3e01b0d49c72fdd525fdb09158e3548590dd87 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:55:08 -0300 Subject: [PATCH 08/13] (#129) corrige mensagens de resposta --- src/shared/classes/http-response.spec.ts | 4 ++-- src/shared/classes/http-response.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/classes/http-response.spec.ts b/src/shared/classes/http-response.spec.ts index f2a0b6c..1456231 100644 --- a/src/shared/classes/http-response.spec.ts +++ b/src/shared/classes/http-response.spec.ts @@ -34,7 +34,7 @@ describe('HttpResponse', () => { const updated = response.onUpdated(); const expected = { - message: 'MENSAGENS.ATUALIZADO-SUCESSO', + message: 'Atualizado com sucesso!', data: {}, }; @@ -46,7 +46,7 @@ describe('HttpResponse', () => { const updated = response.onDeleted(); const expected = { - message: 'MENSAGENS.EXCLUIDO-SUCESSO', + message: 'Excluído com sucesso!', data: {}, }; diff --git a/src/shared/classes/http-response.ts b/src/shared/classes/http-response.ts index daa783c..f5ce99a 100644 --- a/src/shared/classes/http-response.ts +++ b/src/shared/classes/http-response.ts @@ -20,12 +20,12 @@ export class HttpResponse implements Response { } onUpdated(): Response { - this.message = 'MENSAGENS.ATUALIZADO-SUCESSO'; + this.message = 'Atualizado com sucesso!'; return this; } onDeleted(): Response { - this.message = 'MENSAGENS.EXCLUIDO-SUCESSO'; + this.message = 'Excluído com sucesso!'; return this; } } From b6fde55c820d2f241a08d526fbf84f3b15dea55c Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 22:55:30 -0300 Subject: [PATCH 09/13] (#129) adiciona delete de usuario --- src/usuario/usuario.controller.ts | 8 +++++++- src/usuario/usuario.service.ts | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts index fcf31fa..7844b65 100644 --- a/src/usuario/usuario.controller.ts +++ b/src/usuario/usuario.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; import { HttpResponse } from '../shared/classes/http-response'; import { Response } from '../shared/interceptors/data-transform.interceptor'; import { IdValidator } from '../shared/validators/id.validator'; @@ -20,4 +20,10 @@ export class UsuarioController { async findOne(@Param() param: IdValidator): Promise { return this._service.findOne(param.id); } + + @Delete(':id') + async remove(@Param() param: IdValidator): Promise> { + await this._service.remove(param.id); + return new HttpResponse({}).onDeleted(); + } } diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts index 833509f..580c6cb 100644 --- a/src/usuario/usuario.service.ts +++ b/src/usuario/usuario.service.ts @@ -21,4 +21,9 @@ export class UsuarioService { async findOne(id: number) { return this._repository.findOneOrFail({ where: { id } }); } + + async remove(id: number) { + const found = await this.findOne(id); + return this._repository.remove(found); + } } From bd44224486e3636d5b5695961b6af03ab533cd4a Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 23:03:16 -0300 Subject: [PATCH 10/13] (#129) adiciona update usuario --- src/usuario/usuario.controller.ts | 24 +++++++++++++++++++++--- src/usuario/usuario.service.ts | 8 ++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts index 7844b65..cbee0cc 100644 --- a/src/usuario/usuario.controller.ts +++ b/src/usuario/usuario.controller.ts @@ -1,8 +1,17 @@ -import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, +} from '@nestjs/common'; import { HttpResponse } from '../shared/classes/http-response'; import { Response } from '../shared/interceptors/data-transform.interceptor'; import { IdValidator } from '../shared/validators/id.validator'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { UpdateUsuarioDto } from './dto/update-usuario.dto'; import { Usuario } from './entities/usuario.entity'; import { UsuarioService } from './usuario.service'; @@ -21,9 +30,18 @@ export class UsuarioController { return this._service.findOne(param.id); } + @Patch(':id') + async update( + @Param() param: IdValidator, + @Body() body: UpdateUsuarioDto, + ): Promise> { + const updated = await this._service.update(param.id, body); + return new HttpResponse(updated).onUpdated(); + } + @Delete(':id') async remove(@Param() param: IdValidator): Promise> { - await this._service.remove(param.id); - return new HttpResponse({}).onDeleted(); + const deleted = await this._service.remove(param.id); + return new HttpResponse(deleted).onDeleted(); } } diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts index 580c6cb..21974e4 100644 --- a/src/usuario/usuario.service.ts +++ b/src/usuario/usuario.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { UpdateUsuarioDto } from './dto/update-usuario.dto'; import { Usuario } from './entities/usuario.entity'; @Injectable() @@ -22,6 +23,13 @@ export class UsuarioService { return this._repository.findOneOrFail({ where: { id } }); } + async update(id: number, body: UpdateUsuarioDto): Promise { + const found = await this.findOne(id); + const merged = Object.assign(found, body); + // TODO caso a senha seja editada, também criptografar + return this._repository.save(merged); + } + async remove(id: number) { const found = await this.findOne(id); return this._repository.remove(found); From 2060cf1844a2a01bb9a9b72b8371d3567003fb42 Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Thu, 19 Oct 2023 23:23:44 -0300 Subject: [PATCH 11/13] (#129) adiciona usuario findAll --- src/shared/helpers/commons.ts | 1 - .../interfaces/usuario-filter.interface.ts | 5 +++ src/usuario/usuario.controller.ts | 14 ++++++ src/usuario/usuario.service.ts | 44 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/usuario/interfaces/usuario-filter.interface.ts diff --git a/src/shared/helpers/commons.ts b/src/shared/helpers/commons.ts index a3fb000..194d883 100644 --- a/src/shared/helpers/commons.ts +++ b/src/shared/helpers/commons.ts @@ -9,7 +9,6 @@ export const isUndefined = (obj: any): obj is undefined => export const isJsonObject = (str: string) => { try { const parse = JSON.parse(str); - return parse && typeof parse === 'object'; } catch (e) { return false; diff --git a/src/usuario/interfaces/usuario-filter.interface.ts b/src/usuario/interfaces/usuario-filter.interface.ts new file mode 100644 index 0000000..c6cb6c2 --- /dev/null +++ b/src/usuario/interfaces/usuario-filter.interface.ts @@ -0,0 +1,5 @@ +export interface IUsuarioFilter { + id?: number; + nome?: string; + email?: string; +} diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts index cbee0cc..225e19d 100644 --- a/src/usuario/usuario.controller.ts +++ b/src/usuario/usuario.controller.ts @@ -8,11 +8,16 @@ import { Post, } from '@nestjs/common'; import { HttpResponse } from '../shared/classes/http-response'; +import { Filtering, Filtrate } from '../shared/decorators/filtrate.decorator'; +import { Ordenate, Ordering } from '../shared/decorators/ordenate.decorator'; +import { Paginate, Pagination } from '../shared/decorators/paginate.decorator'; import { Response } from '../shared/interceptors/data-transform.interceptor'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; import { IdValidator } from '../shared/validators/id.validator'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; import { Usuario } from './entities/usuario.entity'; +import { IUsuarioFilter } from './interfaces/usuario-filter.interface'; import { UsuarioService } from './usuario.service'; @Controller() @@ -25,6 +30,15 @@ export class UsuarioController { return new HttpResponse(created).onCreated(); } + @Get() + async findAll( + @Filtrate() queryParam: Filtering, + @Paginate() pagination: Pagination, + @Ordenate() ordering: Ordering, + ): Promise> { + return this._service.findAll(queryParam.filter, ordering, pagination); + } + @Get(':id') async findOne(@Param() param: IdValidator): Promise { return this._service.findOne(param.id); diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts index 21974e4..96b1d31 100644 --- a/src/usuario/usuario.service.ts +++ b/src/usuario/usuario.service.ts @@ -1,9 +1,17 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { Ordering } from '../shared/decorators/ordenate.decorator'; +import { Pagination } from '../shared/decorators/paginate.decorator'; +import { + getWhereClauseNumber, + getWhereClauseString, +} from '../shared/helpers/sql-query-helper'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; import { Usuario } from './entities/usuario.entity'; +import { IUsuarioFilter } from './interfaces/usuario-filter.interface'; @Injectable() export class UsuarioService { @@ -34,4 +42,40 @@ export class UsuarioService { const found = await this.findOne(id); return this._repository.remove(found); } + + async findAll( + filter: IUsuarioFilter, + ordering: Ordering, + paging: Pagination, + ): Promise> { + const limit = paging.limit; + const offset = paging.offset; + const sort = ordering.column; + const order = ordering.dir.toUpperCase() as 'ASC' | 'DESC'; + const where = this.buildWhereClause(filter); + + const [result, total] = await this._repository + .createQueryBuilder('usuario') + .where(`${where}`) + .limit(limit) + .offset(offset) + .orderBy(sort, order) + .getManyAndCount(); + + return { + data: result, + count: +total, + pageSize: +total, + }; + } + + private buildWhereClause(filter: IUsuarioFilter): string { + let whereClause = '1 = 1 '; + + whereClause += getWhereClauseString(filter.nome, 'nome'); + whereClause += getWhereClauseString(filter.email, 'email'); + whereClause += getWhereClauseNumber(filter.id, 'id'); + + return whereClause; + } } From d15611e47d0fda8e72654a5234f951134697c8bf Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Fri, 20 Oct 2023 10:38:11 -0300 Subject: [PATCH 12/13] (#129) adiciona testes unitarios --- src/app.controller.spec.ts | 2 +- src/app.service.spec.ts | 2 +- src/usuario/usuario.controller.spec.ts | 129 +++++++++++++++++++++++++ src/usuario/usuario.service.spec.ts | 120 +++++++++++++++++++++++ 4 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 src/usuario/usuario.controller.spec.ts create mode 100644 src/usuario/usuario.service.spec.ts diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index 3068c57..95f22dc 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -19,7 +19,7 @@ describe('AppController', () => { }); it('should make health check', () => { - expect(controller.heathCheck()).toEqual({ + expect(controller.healthCheck()).toEqual({ message: 'GEROcuidadoApiUsuario health check Ok!', data: {}, }); diff --git a/src/app.service.spec.ts b/src/app.service.spec.ts index 02c6b75..9c2005f 100644 --- a/src/app.service.spec.ts +++ b/src/app.service.spec.ts @@ -16,7 +16,7 @@ describe('AppService', () => { }); it('should make health check', () => { - expect(service.heathCheck()).toEqual({ + expect(service.healthCheck()).toEqual({ message: 'GEROcuidadoApiUsuario health check Ok!', data: {}, }); diff --git a/src/usuario/usuario.controller.spec.ts b/src/usuario/usuario.controller.spec.ts new file mode 100644 index 0000000..7cda64b --- /dev/null +++ b/src/usuario/usuario.controller.spec.ts @@ -0,0 +1,129 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Filtering } from '../shared/decorators/filtrate.decorator'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { + Pagination, + PaginationParams, +} from '../shared/decorators/paginate.decorator'; +import { Usuario } from './entities/usuario.entity'; +import { IUsuarioFilter } from './interfaces/usuario-filter.interface'; +import { UsuarioController } from './usuario.controller'; +import { UsuarioService } from './usuario.service'; + +describe('UsuarioController', () => { + let controller: UsuarioController; + let service: UsuarioService; + + const userDto = { + nome: 'Henrique', + email: 'hacmelo@gmail.com', + senha: '123', + foto: '1', + admin: false, + }; + + const user = { + ...userDto, + id: 1, + foto: Buffer.from('1'), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + controllers: [UsuarioController], + providers: [ + { + provide: UsuarioService, + useValue: { + create: jest.fn(), + findOne: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + findAll: jest.fn(), + }, + }, + { + provide: getRepositoryToken(Usuario), + useValue: {}, + }, + ], + }).compile(); + + controller = module.get(UsuarioController); + service = module.get(UsuarioService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should create Usuario', async () => { + jest.spyOn(service, 'create').mockReturnValue(Promise.resolve(user)); + + const response = await controller.create(userDto); + expect(response.data).toEqual(user); + expect(response.message).toEqual('Salvo com sucesso!'); + }); + + it('should find Usuario', async () => { + jest.spyOn(service, 'findOne').mockReturnValue(Promise.resolve(user)); + + const response = await controller.findOne({ id: 1 }); + expect(response).toEqual(user); + }); + + it('should remove Usuario', async () => { + jest.spyOn(service, 'remove').mockReturnValue(Promise.resolve(user)); + + const response = await controller.remove({ id: 1 }); + expect(response.data).toEqual(user); + expect(response.message).toEqual('Excluído com sucesso!'); + }); + + it('should update Usuario', async () => { + jest.spyOn(service, 'update').mockReturnValue(Promise.resolve(user)); + + const response = await controller.update({ id: 1 }, { nome: 'Henrique' }); + expect(response.data).toEqual(user); + expect(response.message).toEqual('Atualizado com sucesso!'); + }); + + describe('findAll', () => { + const filter: IUsuarioFilter = { + nome: 'Henrique', + id: 1, + email: 'email@email.com', + }; + const filtering = new Filtering(JSON.stringify(filter)); + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + const pagination: Pagination = new Pagination(paginate); + + it('should findAll Usuario', async () => { + const expected = { data: [user], count: 1, pageSize: 1 }; + + jest.spyOn(service, 'findAll').mockReturnValue(Promise.resolve(expected)); + + const { data, count, pageSize } = await controller.findAll( + filtering, + pagination, + ordering, + ); + + expect(count).toEqual(1); + expect(pageSize).toEqual(1); + expect(data).toEqual([user]); + }); + }); +}); diff --git a/src/usuario/usuario.service.spec.ts b/src/usuario/usuario.service.spec.ts new file mode 100644 index 0000000..c59c66b --- /dev/null +++ b/src/usuario/usuario.service.spec.ts @@ -0,0 +1,120 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { + Pagination, + PaginationParams, +} from '../shared/decorators/paginate.decorator'; +import { Usuario } from './entities/usuario.entity'; +import { UsuarioService } from './usuario.service'; + +describe('UsuarioService', () => { + let service: UsuarioService; + let repository: Repository; + + const mockRepository = { + save: jest.fn(), + findOneOrFail: jest.fn(), + remove: jest.fn(), + createQueryBuilder: jest.fn(() => ({ + where: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + offset: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn(), + })), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + UsuarioService, + { + provide: getRepositoryToken(Usuario), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(UsuarioService); + repository = module.get>(getRepositoryToken(Usuario)); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should create Usuario', async () => { + const user = { nome: 'Henrique' } as any; + jest.spyOn(repository, 'save').mockReturnValue({ id: 1 } as any); + + const created = await service.create(user); + expect(created.id).toEqual(1); + }); + + it('should find Usuario', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + + const found = await service.findOne(1); + expect(found.id).toEqual(1); + }); + + it('should remove Usuario', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + jest.spyOn(repository, 'remove').mockReturnValue({ id: 1 } as any); + + const removed = await service.remove(1); + expect(removed.id).toEqual(1); + }); + + it('should update Usuario', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + jest + .spyOn(repository, 'save') + .mockReturnValue({ id: 1, nome: 'Henrique' } as any); + + const found = await service.update(1, { nome: 'Henrique' }); + expect(found).toEqual({ id: 1, nome: 'Henrique' }); + }); + + describe('findAll', () => { + const usuario = { + id: 1, + nome: 'Henrique', + email: 'email@email.com', + }; + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + const pagination: Pagination = new Pagination(paginate); + + it('should findAll Usuario', async () => { + jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({ + where: () => ({ + limit: () => ({ + offset: () => ({ + orderBy: () => ({ + getManyAndCount: jest + .fn() + .mockResolvedValueOnce([[usuario], 1]), + }), + }), + }), + }), + } as any); + + const { data, count } = await service.findAll({}, ordering, pagination); + expect(count).toEqual(1); + expect((data as Usuario[])[0]).toEqual(usuario); + }); + }); +}); From 03da9847ca80e11d5a97e21013d3b6a9594125ae Mon Sep 17 00:00:00 2001 From: Henrique Melo Date: Fri, 20 Oct 2023 11:22:34 -0300 Subject: [PATCH 13/13] (#129) adiciona testes e2e usuario --- e2e/app.e2e-spec.ts | 8 +- e2e/usuario.e2e-spec.ts | 182 ++++++++++++++++++++++++++ src/usuario/dto/create-usuario.dto.ts | 2 + 3 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 e2e/usuario.e2e-spec.ts diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts index 9fcac20..f5443ee 100644 --- a/e2e/app.e2e-spec.ts +++ b/e2e/app.e2e-spec.ts @@ -1,10 +1,10 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { AllExceptionsFilter } from './../src/shared/filters/all-exceptions.filter'; -import { ModelNotFoundExceptionFilter } from './../src/shared/filters/model-not-found.exception-filter'; -import { DataTransformInterceptor } from './../src/shared/interceptors/data-transform.interceptor'; +import { AppModule } from '../src/app.module'; +import { AllExceptionsFilter } from '../src/shared/filters/all-exceptions.filter'; +import { ModelNotFoundExceptionFilter } from '../src/shared/filters/model-not-found.exception-filter'; +import { DataTransformInterceptor } from '../src/shared/interceptors/data-transform.interceptor'; describe('App (e2e)', () => { let app: INestApplication; diff --git a/e2e/usuario.e2e-spec.ts b/e2e/usuario.e2e-spec.ts new file mode 100644 index 0000000..4845870 --- /dev/null +++ b/e2e/usuario.e2e-spec.ts @@ -0,0 +1,182 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import request from 'supertest'; +import { Repository } from 'typeorm'; +import { AppModule } from '../src/app.module'; +import { AllExceptionsFilter } from '../src/shared/filters/all-exceptions.filter'; +import { ModelNotFoundExceptionFilter } from '../src/shared/filters/model-not-found.exception-filter'; +import { DataTransformInterceptor } from '../src/shared/interceptors/data-transform.interceptor'; +import { Usuario } from '../src/usuario/entities/usuario.entity'; + +describe('E2E - Usuario', () => { + let app: INestApplication; + let repository: Repository; + + const user: Partial = { + id: undefined, + nome: 'Henrique', + email: 'hacmelo@gmail.com', + senha: '123', + admin: false, + }; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + }), + ); + app.useGlobalInterceptors(new DataTransformInterceptor()); + app.useGlobalFilters( + new AllExceptionsFilter(), + new ModelNotFoundExceptionFilter(), + ); + + await app.startAllMicroservices(); + await app.init(); + + repository = app.get>(getRepositoryToken(Usuario)); + }); + + describe('POST - /api/usuario', () => { + it('should successfully add a new "usuario"', async () => { + const res = await request(app.getHttpServer()) + .post('') + .set('Content-Type', 'application/json') + .send(user); + + expect(res.statusCode).toEqual(201); + expect(res.body.message).toEqual('Salvo com sucesso!'); + expect(res.body.data).toMatchObject({ ...user, id: res.body.data.id }); + + Object.assign(user, res.body.data); + delete user.senha; + }); + + it('should not add a new "usuario" when validations are incorrect', async () => { + const res = await request(app.getHttpServer()) + .post('') + .set('Content-Type', 'application/json') + .send({}); + + expect(res.statusCode).toEqual(400); + expect(res.body.message).toBeInstanceOf(Array); + expect(res.body.message).toEqual([ + 'nome must be longer than or equal to 5 characters', + 'nome must be shorter than or equal to 60 characters', + 'nome should not be empty', + 'nome must be a string', + 'email must be shorter than or equal to 100 characters', + 'email must be an email', + 'email should not be empty', + 'email must be a string', + 'senha must be shorter than or equal to 100 characters', + 'senha should not be empty', + 'senha must be a string', + ]); + expect(res.body.data).toBeNull(); + }); + }); + + describe('GET - /api/usuario/:id', () => { + it('should successfully get "usuario" by id', async () => { + const res = await request(app.getHttpServer()) + .get(`/${user.id}`) + .set('Content-Type', 'application/json') + .send(); + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toBeNull(); + expect(res.body.data).toMatchObject(user); + }); + + it('should return status 400 when id is invalid', async () => { + const wrongId = 'NaN'; + const res = await request(app.getHttpServer()) + .get(`/${wrongId}`) + .set('Content-Type', 'application/json') + .send(); + + expect(res.statusCode).toEqual(400); + expect(res.body.message).toBeInstanceOf(Array); + expect(res.body.message).toEqual(['ID inválido']); + expect(res.body.data).toBeNull(); + }); + + it('should return status 404 when no "usuario" is found', async () => { + const res = await request(app.getHttpServer()) + .get('/9999') + .set('Content-Type', 'application/json') + .send(); + + expect(res.statusCode).toEqual(404); + expect(res.body.message).toEqual('Registro(s) não encontrado(s)!'); + expect(res.body.data).toBeNull(); + }); + }); + + describe('GET - /api/usuario/', () => { + it('should successfully findAll "usuario"', async () => { + const filter = JSON.stringify({ + nome: user.nome, + id: user.id, + email: user.email, + }); + + const res = await request(app.getHttpServer()) + .get('?filter=' + JSON.stringify(filter)) + .set('Content-Type', 'application/json') + .send(); + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toBeNull(); + expect(res.body.data).toEqual([user]); + }); + }); + + describe('PATCH - /api/usuario/:id', () => { + it('should successfully update "usuario" by id', async () => { + const update = { nome: 'Jose da Silva' }; + + const res = await request(app.getHttpServer()) + .patch(`/${user.id}`) + .set('Content-Type', 'application/json') + .send(update); + + user.nome = update.nome; + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toBe('Atualizado com sucesso!'); + expect(res.body.data).toMatchObject(user); + }); + }); + + describe('DELETE - /api/usuario/:id', () => { + it('should successfully delete "usuario" by id', async () => { + const res = await request(app.getHttpServer()) + .delete(`/${user.id}`) + .set('Content-Type', 'application/json') + .send(); + + delete user.id; + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toBe('Excluído com sucesso!'); + expect(res.body.data).toMatchObject(user); + }); + }); + + afterAll(async () => { + await repository.query('TRUNCATE usuario CASCADE'); + await repository.delete({}); + await app.close(); + }); +}); diff --git a/src/usuario/dto/create-usuario.dto.ts b/src/usuario/dto/create-usuario.dto.ts index a66dc1f..99f871d 100644 --- a/src/usuario/dto/create-usuario.dto.ts +++ b/src/usuario/dto/create-usuario.dto.ts @@ -9,6 +9,8 @@ import { } from 'class-validator'; export class CreateUsuarioDto { + // TODO colocar mensagens customizadas "user friendly" em todos os validators + @IsString() @IsNotEmpty() @MaxLength(60)