From 705814599f92fdd383873e97d4208ecc0ff2cdc1 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sat, 11 May 2024 20:48:55 -0300 Subject: [PATCH 01/13] feat: add req body validation & auth for swagger --- src/main.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.ts b/src/main.ts index eaf3c678..a3e28539 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import { import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const fastifyAdapter = new FastifyAdapter(); @@ -14,11 +15,20 @@ async function bootstrap() { fastifyAdapter, ); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + }), + ); + const config = new DocumentBuilder() .setTitle('SOS - Rio Grande do Sul') .setDescription('...') .setVersion('1.0') + .addBearerAuth() .build(); + const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); From 819a6e5ce53634efd0af3cef58960051d17e426b Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sat, 11 May 2024 20:54:55 -0300 Subject: [PATCH 02/13] feat: add req body validation & swagger docs to users routes --- package-lock.json | 35 +++++++++++++++++++++++++++++++++ package.json | 2 ++ src/users/dtos/CreateUserDTO.ts | 19 ++++++++++++++++++ src/users/dtos/UpdateUserDTO.ts | 29 +++++++++++++++++++++++++++ src/users/types.ts | 31 ----------------------------- src/users/users.controller.ts | 31 +++++++++++++++++++++++++---- src/users/users.service.ts | 11 +++++------ 7 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 src/users/dtos/CreateUserDTO.ts create mode 100644 src/users/dtos/UpdateUserDTO.ts delete mode 100644 src/users/types.ts diff --git a/package-lock.json b/package-lock.json index 6c3ae692..2c7efb84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "date-fns": "^3.6.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.0", @@ -2639,6 +2641,11 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/validator": { + "version": "13.11.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz", + "integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3807,6 +3814,21 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -6883,6 +6905,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz", + "integrity": "sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==" + }, "node_modules/light-my-request": { "version": "5.13.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.13.0.tgz", @@ -9652,6 +9679,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 3212f2fd..c413855b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "date-fns": "^3.6.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.0", diff --git a/src/users/dtos/CreateUserDTO.ts b/src/users/dtos/CreateUserDTO.ts new file mode 100644 index 00000000..20d786c2 --- /dev/null +++ b/src/users/dtos/CreateUserDTO.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateUserDTO { + @ApiProperty({ type: 'string', example: 'John' }) + @IsNotEmpty() + @IsString() + readonly name = ''; + + @ApiProperty({ type: 'string', example: 'Doe' }) + @IsNotEmpty() + @IsString() + readonly lastName = ''; + + @ApiProperty({ type: 'string', example: '(55) 99671-6164' }) + @IsNotEmpty() + @IsString() + readonly phone = ''; +} diff --git a/src/users/dtos/UpdateUserDTO.ts b/src/users/dtos/UpdateUserDTO.ts new file mode 100644 index 00000000..f601a049 --- /dev/null +++ b/src/users/dtos/UpdateUserDTO.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateUserDTO { + @ApiProperty({ type: 'string', example: 'John' }) + @IsOptional() + @IsString() + readonly name?: string; + + @ApiProperty({ type: 'string', example: 'Doe' }) + @IsOptional() + @IsString() + readonly lastName?: string; + + @ApiProperty({ type: 'string', example: 'John' }) + @IsOptional() + @IsString() + readonly login?: string; + + @ApiProperty({ type: 'string', example: 'john123' }) + @IsOptional() + @IsString() + readonly password?: string; + + @ApiProperty({ type: 'string', example: '(55) 99671-6164' }) + @IsOptional() + @IsString() + readonly phone?: string; +} diff --git a/src/users/types.ts b/src/users/types.ts deleted file mode 100644 index 1721f4bf..00000000 --- a/src/users/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AccessLevel } from '@prisma/client'; -import z from 'zod'; - -import { removeNotNumbers } from '../utils'; - -const UserSchema = z.object({ - id: z.string().uuid(), - name: z.string(), - lastName: z.string(), - login: z.string().transform((v) => v.toLowerCase()), - password: z.string(), - phone: z.string().transform(removeNotNumbers), - accessLevel: z.nativeEnum(AccessLevel), - createdAt: z.string(), - updatedAt: z.string().nullable().optional(), -}); - -const CreateUserSchema = UserSchema.pick({ - name: true, - lastName: true, - phone: true, -}); - -const UpdateUserSchema = UserSchema.omit({ - id: true, - accessLevel: true, - createdAt: true, - updatedAt: true, -}).partial(); - -export { CreateUserSchema, UpdateUserSchema }; diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 5d4a0dc2..f4b0fb41 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -9,12 +9,21 @@ import { Req, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiCreatedResponse, + ApiInternalServerErrorResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { UserGuard } from '@/guards/user.guard'; import { ServerResponse } from '../utils'; import { UsersService } from './users.service'; import { AdminGuard } from '@/guards/admin.guard'; +import { CreateUserDTO } from './dtos/CreateUserDTO'; +import { UpdateUserDTO } from './dtos/UpdateUserDTO'; @ApiTags('Usuários') @Controller('users') @@ -23,9 +32,13 @@ export class UsersController { constructor(private readonly userServices: UsersService) {} + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiInternalServerErrorResponse() + @ApiCreatedResponse() @Post('') @UseGuards(AdminGuard) - async store(@Body() body) { + async store(@Body() body: CreateUserDTO) { try { await this.userServices.store(body); return new ServerResponse(201, 'Successfully created user'); @@ -35,9 +48,14 @@ export class UsersController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiInternalServerErrorResponse() + @ApiCreatedResponse() @Put(':id') @UseGuards(AdminGuard) - async update(@Body() body, @Param('id') id: string) { + async update(@Body() body: UpdateUserDTO, @Param('id') id: string) { try { await this.userServices.update(id, body); return new ServerResponse(201, 'Successfully updated user'); @@ -47,9 +65,14 @@ export class UsersController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiInternalServerErrorResponse() + @ApiCreatedResponse() @Put('') @UseGuards(UserGuard) - async selfUpdate(@Body() body, @Req() req) { + async selfUpdate(@Body() body: UpdateUserDTO, @Req() req) { try { const { userId } = req.user; await this.userServices.update(userId, body); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 49e5df91..fc2a0186 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,14 +1,14 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateUserSchema, UpdateUserSchema } from './types'; +import { CreateUserDTO } from './dtos/CreateUserDTO'; +import { UpdateUserDTO } from './dtos/UpdateUserDTO'; @Injectable() export class UsersService { constructor(private readonly prismaService: PrismaService) {} - async store(body: any) { - const { name, lastName, phone } = CreateUserSchema.parse(body); + async store({ name, lastName, phone }: CreateUserDTO) { await this.prismaService.user.create({ data: { name, @@ -21,14 +21,13 @@ export class UsersService { }); } - async update(id: string, body: any) { - const payload = UpdateUserSchema.parse(body); + async update(id: string, body: UpdateUserDTO) { await this.prismaService.user.update({ where: { id, }, data: { - ...payload, + ...body, updatedAt: new Date().toISOString(), }, }); From 0d4be0f6b7c6ae425f98256ea475a9733745fdd6 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sat, 11 May 2024 21:41:23 -0300 Subject: [PATCH 03/13] chore: restore zod validation for users --- src/users/types.ts | 31 +++++++++++++++++++++++++++++++ src/users/users.service.ts | 7 +++++-- 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/users/types.ts diff --git a/src/users/types.ts b/src/users/types.ts new file mode 100644 index 00000000..1721f4bf --- /dev/null +++ b/src/users/types.ts @@ -0,0 +1,31 @@ +import { AccessLevel } from '@prisma/client'; +import z from 'zod'; + +import { removeNotNumbers } from '../utils'; + +const UserSchema = z.object({ + id: z.string().uuid(), + name: z.string(), + lastName: z.string(), + login: z.string().transform((v) => v.toLowerCase()), + password: z.string(), + phone: z.string().transform(removeNotNumbers), + accessLevel: z.nativeEnum(AccessLevel), + createdAt: z.string(), + updatedAt: z.string().nullable().optional(), +}); + +const CreateUserSchema = UserSchema.pick({ + name: true, + lastName: true, + phone: true, +}); + +const UpdateUserSchema = UserSchema.omit({ + id: true, + accessLevel: true, + createdAt: true, + updatedAt: true, +}).partial(); + +export { CreateUserSchema, UpdateUserSchema }; diff --git a/src/users/users.service.ts b/src/users/users.service.ts index fc2a0186..48cf95c9 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -3,12 +3,14 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateUserDTO } from './dtos/CreateUserDTO'; import { UpdateUserDTO } from './dtos/UpdateUserDTO'; +import { CreateUserSchema, UpdateUserSchema } from './types'; @Injectable() export class UsersService { constructor(private readonly prismaService: PrismaService) {} - async store({ name, lastName, phone }: CreateUserDTO) { + async store(body: CreateUserDTO) { + const { name, lastName, phone } = CreateUserSchema.parse(body); await this.prismaService.user.create({ data: { name, @@ -22,12 +24,13 @@ export class UsersService { } async update(id: string, body: UpdateUserDTO) { + const payload = UpdateUserSchema.parse(body); await this.prismaService.user.update({ where: { id, }, data: { - ...body, + ...payload, updatedAt: new Date().toISOString(), }, }); From 9ccf1d7ad723cd9ec984dbd4e2ae18faba798ee1 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sat, 11 May 2024 22:41:55 -0300 Subject: [PATCH 04/13] feat: add req body validation & swagger docs to sessions routes --- src/sessions/dtos/LoginSessionDTO.ts | 14 ++++++++++++++ src/sessions/sessions.controller.ts | 25 ++++++++++++++++++++++--- src/sessions/sessions.service.ts | 13 +++++++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/sessions/dtos/LoginSessionDTO.ts diff --git a/src/sessions/dtos/LoginSessionDTO.ts b/src/sessions/dtos/LoginSessionDTO.ts new file mode 100644 index 00000000..2c688972 --- /dev/null +++ b/src/sessions/dtos/LoginSessionDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class LoginSessionDTO { + @ApiProperty({ type: 'string', example: 'John' }) + @IsNotEmpty() + @IsString() + readonly login = ''; + + @ApiProperty({ type: 'string', example: 'john123' }) + @IsNotEmpty() + @IsString() + readonly password = ''; +} diff --git a/src/sessions/sessions.controller.ts b/src/sessions/sessions.controller.ts index a29a3f9e..56c65429 100644 --- a/src/sessions/sessions.controller.ts +++ b/src/sessions/sessions.controller.ts @@ -10,11 +10,19 @@ import { Request, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { UserGuard } from '@/guards/user.guard'; import { ServerResponse } from '../utils'; import { SessionsService } from './sessions.service'; +import { LoginSessionDTO } from './dtos/LoginSessionDTO'; @ApiTags('Sessões') @Controller('sessions') @@ -23,14 +31,17 @@ export class SessionsController { constructor(private readonly sessionService: SessionsService) {} + @ApiBadRequestResponse() + @ApiInternalServerErrorResponse() + @ApiOkResponse() @Post('') async login( - @Body() body, + @Body() body: LoginSessionDTO, @Headers('x-real-ip') ip: string, @Headers('user-agent') userAgent: string, ) { try { - const data = await this.sessionService.login({ ...body, ip, userAgent }); + const data = await this.sessionService.login(body, ip, userAgent); return new ServerResponse(200, 'Successfully logged in', data); } catch (err: any) { this.logger.error(`Failed to login ${err}`); @@ -41,6 +52,10 @@ export class SessionsController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiInternalServerErrorResponse() + @ApiOkResponse() @Get('') @UseGuards(UserGuard) async show(@Request() req) { @@ -54,6 +69,10 @@ export class SessionsController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiInternalServerErrorResponse() + @ApiOkResponse() @Delete('') @UseGuards(UserGuard) async delete(@Request() req) { diff --git a/src/sessions/sessions.service.ts b/src/sessions/sessions.service.ts index 200104b6..a83c9684 100644 --- a/src/sessions/sessions.service.ts +++ b/src/sessions/sessions.service.ts @@ -4,6 +4,7 @@ import * as bcrypt from 'bcrypt'; import { PrismaService } from '../prisma/prisma.service'; import { LoginSchema, TokenPayload } from './types'; +import { LoginSessionDTO } from './dtos/LoginSessionDTO'; @Injectable() export class SessionsService { @@ -12,8 +13,16 @@ export class SessionsService { private readonly jwtService: JwtService, ) {} - async login(body: any) { - const { login, password, ip, userAgent } = LoginSchema.parse(body); + async login( + body: LoginSessionDTO, + ipHeaders: string, + userAgentHeaders: string, + ) { + const { login, password, ip, userAgent } = LoginSchema.parse({ + ...body, + ipHeaders, + userAgentHeaders, + }); const user = await this.prismaService.user.findUnique({ where: { login }, }); From 407b3f821ae3cdfad934c0d158c3c05b7ba7245f Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sun, 12 May 2024 21:42:46 -0300 Subject: [PATCH 05/13] feat: add req body validation & swagger docs to shelters routes --- src/shelter/dtos/CreateShelterDTO.ts | 62 +++++++++++++++++++++++ src/shelter/dtos/ShelterQuerysDTO.ts | 74 ++++++++++++++++++++++++++++ src/shelter/dtos/UpdateShelterDTO.ts | 7 +++ src/shelter/shelter.controller.ts | 37 ++++++++++++-- src/shelter/shelter.service.ts | 18 ++++--- 5 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 src/shelter/dtos/CreateShelterDTO.ts create mode 100644 src/shelter/dtos/ShelterQuerysDTO.ts create mode 100644 src/shelter/dtos/UpdateShelterDTO.ts diff --git a/src/shelter/dtos/CreateShelterDTO.ts b/src/shelter/dtos/CreateShelterDTO.ts new file mode 100644 index 00000000..c6579370 --- /dev/null +++ b/src/shelter/dtos/CreateShelterDTO.ts @@ -0,0 +1,62 @@ +import { + IsOptional, + IsString, + IsBoolean, + IsNumber, + Min, + IsNotEmpty, +} from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateShelterDTO { + @ApiProperty({ type: 'string', example: 'Nome do Abrigo' }) + @IsNotEmpty() + @IsString() + readonly name = ''; + + @ApiProperty({ required: false, type: 'string', example: 'PIX do Abrigo' }) + @IsOptional() + @IsString() + readonly pix?: string; + + @ApiProperty({ type: 'string', example: 'Endereço do Abrigo' }) + @IsNotEmpty() + @IsString() + readonly address = ''; + + @ApiProperty({ required: false, type: 'boolean', example: true }) + @IsOptional() + @IsBoolean() + readonly petFriendly?: boolean; + + @ApiProperty({ required: false, type: 'number', example: 10 }) + @IsOptional() + @IsNumber() + @Min(0) + readonly shelteredPeople?: number; + + @ApiProperty({ required: false, type: 'number', example: 123.456 }) + @IsOptional() + @IsNumber() + readonly latitude?: number; + + @ApiProperty({ required: false, type: 'number', example: 654.321 }) + @IsOptional() + @IsNumber() + readonly longitude?: number; + + @ApiProperty({ required: false, type: 'number', example: 50 }) + @IsOptional() + @IsNumber() + @Min(0) + readonly capacity?: number; + + @ApiProperty({ + required: false, + type: 'string', + example: 'Contato do Abrigo', + }) + @IsOptional() + @IsString() + readonly contact?: string; +} diff --git a/src/shelter/dtos/ShelterQuerysDTO.ts b/src/shelter/dtos/ShelterQuerysDTO.ts new file mode 100644 index 00000000..e92ddc6c --- /dev/null +++ b/src/shelter/dtos/ShelterQuerysDTO.ts @@ -0,0 +1,74 @@ +import { IsOptional, IsString, IsNumber, IsIn, IsEnum } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { SupplyPriority } from 'src/supply/types'; +import { Transform } from 'class-transformer'; + +export class ShelterQueryDTO { + @ApiProperty({ + required: false, + description: 'Quantidade de resultados por página', + }) + @IsOptional() + @IsNumber() + @Transform((value) => Number(value.value)) + perPage?: number; + + @ApiProperty({ required: false, description: 'Número da página' }) + @IsOptional() + @IsNumber() + @Transform((value) => Number(value.value)) + page?: number; + + @ApiProperty({ required: false, description: 'Termo de busca' }) + @IsOptional() + @IsString() + search?: string; + + @ApiProperty({ + required: false, + description: 'Ordem dos resultados', + enum: ['asc', 'desc'], + }) + @IsOptional() + @IsIn(['asc', 'desc']) + order?: 'asc' | 'desc'; + + @ApiProperty({ + required: false, + description: 'Critério de ordenação dos resultados', + }) + @IsOptional() + @IsString() + orderBy?: string; + + @ApiProperty({ + required: false, + description: 'IDs de categoria de suprimento', + }) + @IsOptional() + @IsString({ each: true }) + supplyCategoryIds?: string[]; + + @ApiProperty({ + required: false, + description: 'Prioridade de suprimento', + enum: SupplyPriority, + }) + @IsOptional() + @IsEnum(SupplyPriority) + @Transform((value) => Number(value.value)) + priority?: SupplyPriority; + + @ApiProperty({ required: false, description: 'IDs de suprimento' }) + @IsOptional() + @IsString({ each: true }) + supplyIds?: string[]; + + @ApiProperty({ + required: false, + description: 'Status do abrigo', + }) + @IsOptional() + @IsIn(['available', 'unavailable', 'waiting'], { each: true }) + shelterStatus?: ('available' | 'unavailable' | 'waiting')[]; // MUDAR !!!! +} diff --git a/src/shelter/dtos/UpdateShelterDTO.ts b/src/shelter/dtos/UpdateShelterDTO.ts new file mode 100644 index 00000000..2a7ba016 --- /dev/null +++ b/src/shelter/dtos/UpdateShelterDTO.ts @@ -0,0 +1,7 @@ +import { PickType } from '@nestjs/swagger'; +import { CreateShelterDTO } from './CreateShelterDTO'; + +export class UpdateShelterDTO extends PickType(CreateShelterDTO, [ + 'petFriendly', + 'shelteredPeople', +]) {} diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 24603857..8026a784 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -10,23 +10,36 @@ import { Query, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { ShelterService } from './shelter.service'; import { ServerResponse } from '../utils'; import { StaffGuard } from '@/guards/staff.guard'; import { ApplyUser } from '@/guards/apply-user.guard'; import { UserDecorator } from '@/decorators/UserDecorator/user.decorator'; +import { ShelterQueryDTO } from './dtos/ShelterQuerysDTO'; +import { CreateShelterDTO } from './dtos/CreateShelterDTO'; +import { UpdateShelterDTO } from './dtos/UpdateShelterDTO'; @ApiTags('Abrigos') +@ApiInternalServerErrorResponse() @Controller('shelters') export class ShelterController { private logger = new Logger(ShelterController.name); constructor(private readonly shelterService: ShelterService) {} + @ApiBadRequestResponse() + @ApiOkResponse() @Get('') - async index(@Query() query) { + async index(@Query() query: ShelterQueryDTO) { try { const data = await this.shelterService.index(query); return new ServerResponse(200, 'Successfully get shelters', data); @@ -36,6 +49,8 @@ export class ShelterController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Get('cities') async cities() { try { @@ -47,6 +62,8 @@ export class ShelterController { } } + @ApiBearerAuth() + @ApiOkResponse() @Get(':id') @UseGuards(ApplyUser) async show(@UserDecorator() user: any, @Param('id') id: string) { @@ -61,9 +78,13 @@ export class ShelterController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Post('') @UseGuards(StaffGuard) - async store(@Body() body) { + async store(@Body() body: CreateShelterDTO) { try { const data = await this.shelterService.store(body); return new ServerResponse(200, 'Successfully created shelter', data); @@ -73,8 +94,10 @@ export class ShelterController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':id') - async update(@Param('id') id: string, @Body() body) { + async update(@Param('id') id: string, @Body() body: UpdateShelterDTO) { try { const data = await this.shelterService.update(id, body); return new ServerResponse(200, 'Successfully updated shelter', data); @@ -84,9 +107,13 @@ export class ShelterController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':id/admin') @UseGuards(StaffGuard) - async fullUpdate(@Param('id') id: string, @Body() body) { + async fullUpdate(@Param('id') id: string, @Body() body: UpdateShelterDTO) { try { const data = await this.shelterService.fullUpdate(id, body); return new ServerResponse(200, 'Successfully updated shelter', data); diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index 74d03860..3ed03693 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -5,16 +5,19 @@ import * as qs from 'qs'; import { z } from 'zod'; import { PrismaService } from '../prisma/prisma.service'; -import { SupplyPriority } from '../supply/types'; -import { SearchSchema } from '../types'; -import { ShelterSearch, parseTagResponse } from './ShelterSearch'; -import { ShelterSearchPropsSchema } from './types/search.types'; import { CreateShelterSchema, FullUpdateShelterSchema, UpdateShelterSchema, } from './types/types'; import { subDays } from 'date-fns'; +import { SupplyPriority } from 'src/supply/types'; +import { SearchSchema } from 'src/types'; +import { ShelterSearch, parseTagResponse } from './ShelterSearch'; +import { CreateShelterDTO } from './dtos/CreateShelterDTO'; +import { ShelterQueryDTO } from './dtos/ShelterQuerysDTO'; +import { UpdateShelterDTO } from './dtos/UpdateShelterDTO'; +import { ShelterSearchPropsSchema } from './types/search.types'; @Injectable() export class ShelterService { @@ -24,7 +27,7 @@ export class ShelterService { this.loadVoluntaryIds(); } - async store(body: z.infer) { + async store(body: CreateShelterDTO) { const payload = CreateShelterSchema.parse(body); await this.prismaService.shelter.create({ @@ -36,7 +39,7 @@ export class ShelterService { }); } - async update(id: string, body: z.infer) { + async update(id: string, body: UpdateShelterDTO) { const payload = UpdateShelterSchema.parse(body); await this.prismaService.shelter.update({ where: { @@ -114,7 +117,8 @@ export class ShelterService { return data; } - async index(query: any) { + // ARRUMAR o queryData + async index(query: ShelterQueryDTO) { const { order, orderBy, From 63437e890a6d931839ab78f8c340fd4c27f38edd Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sun, 12 May 2024 22:01:15 -0300 Subject: [PATCH 06/13] feat: add req body validation & swagger docs to shelters routes --- src/shelter/dtos/FullUpdateShelterDTO.ts | 70 ++++++++++++++++++++++++ src/shelter/shelter.controller.ts | 6 +- src/shelter/shelter.service.ts | 4 +- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/shelter/dtos/FullUpdateShelterDTO.ts diff --git a/src/shelter/dtos/FullUpdateShelterDTO.ts b/src/shelter/dtos/FullUpdateShelterDTO.ts new file mode 100644 index 00000000..757a98ec --- /dev/null +++ b/src/shelter/dtos/FullUpdateShelterDTO.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsOptional, + IsString, + IsBoolean, + IsNumber, + Min, +} from 'class-validator'; + +export class FullUpdateShelterDTO { + @ApiProperty({ required: false, type: 'string', example: 'Nome do Abrigo' }) + @IsOptional() + @IsString() + readonly name?: string; + + @ApiProperty({ required: false, type: 'string', example: 'PIX do Abrigo' }) + @IsOptional() + @IsString() + readonly pix?: string; + + @ApiProperty({ + required: false, + type: 'string', + example: 'Endereço do Abrigo', + }) + @IsOptional() + @IsString() + readonly address?: string; + + @ApiProperty({ required: false, type: 'boolean', example: true }) + @IsOptional() + @IsBoolean() + readonly petFriendly?: boolean; + + @ApiProperty({ required: false, type: 'number', example: 10 }) + @IsOptional() + @IsNumber() + @Min(0) + readonly shelteredPeople?: number; + + @ApiProperty({ required: false, type: 'number', example: 123.456 }) + @IsOptional() + @IsNumber() + readonly latitude?: number; + + @ApiProperty({ required: false, type: 'number', example: 654.321 }) + @IsOptional() + @IsNumber() + readonly longitude?: number; + + @ApiProperty({ required: false, type: 'number', example: 50 }) + @IsOptional() + @IsNumber() + @Min(0) + readonly capacity?: number; + + @ApiProperty({ + required: false, + type: 'string', + example: 'Contato do Abrigo', + }) + @IsOptional() + @IsString() + readonly contact?: string; + + @ApiProperty({ required: false, type: 'boolean' }) + @IsOptional() + @IsBoolean() + readonly verified?: boolean; +} diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 8026a784..3bf3d751 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -27,6 +27,7 @@ import { UserDecorator } from '@/decorators/UserDecorator/user.decorator'; import { ShelterQueryDTO } from './dtos/ShelterQuerysDTO'; import { CreateShelterDTO } from './dtos/CreateShelterDTO'; import { UpdateShelterDTO } from './dtos/UpdateShelterDTO'; +import { FullUpdateShelterDTO } from './dtos/FullUpdateShelterDTO'; @ApiTags('Abrigos') @ApiInternalServerErrorResponse() @@ -113,7 +114,10 @@ export class ShelterController { @ApiOkResponse() @Put(':id/admin') @UseGuards(StaffGuard) - async fullUpdate(@Param('id') id: string, @Body() body: UpdateShelterDTO) { + async fullUpdate( + @Param('id') id: string, + @Body() body: FullUpdateShelterDTO, + ) { try { const data = await this.shelterService.fullUpdate(id, body); return new ServerResponse(200, 'Successfully updated shelter', data); diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index 3ed03693..3ae90560 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { DefaultArgs } from '@prisma/client/runtime/library'; import * as qs from 'qs'; -import { z } from 'zod'; import { PrismaService } from '../prisma/prisma.service'; import { @@ -18,6 +17,7 @@ import { CreateShelterDTO } from './dtos/CreateShelterDTO'; import { ShelterQueryDTO } from './dtos/ShelterQuerysDTO'; import { UpdateShelterDTO } from './dtos/UpdateShelterDTO'; import { ShelterSearchPropsSchema } from './types/search.types'; +import { FullUpdateShelterDTO } from './dtos/FullUpdateShelterDTO'; @Injectable() export class ShelterService { @@ -52,7 +52,7 @@ export class ShelterService { }); } - async fullUpdate(id: string, body: z.infer) { + async fullUpdate(id: string, body: FullUpdateShelterDTO) { const payload = FullUpdateShelterSchema.parse(body); await this.prismaService.shelter.update({ where: { From 3239cc72258c43f79d2e68e961726783d0491848 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Sun, 12 May 2024 22:45:20 -0300 Subject: [PATCH 07/13] feat: add req body validation & swagger docs to supplies routes --- src/supply/dtos/CreateSupplyDTO.ts | 14 ++++++++++++++ src/supply/dtos/UpdateSupplyDTO.ts | 22 ++++++++++++++++++++++ src/supply/supply.controller.ts | 19 ++++++++++++++++--- src/supply/supply.service.ts | 7 ++++--- 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 src/supply/dtos/CreateSupplyDTO.ts create mode 100644 src/supply/dtos/UpdateSupplyDTO.ts diff --git a/src/supply/dtos/CreateSupplyDTO.ts b/src/supply/dtos/CreateSupplyDTO.ts new file mode 100644 index 00000000..27e4fba1 --- /dev/null +++ b/src/supply/dtos/CreateSupplyDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateSupplyDTO { + @ApiProperty({ type: 'string', example: 'ID da Categoria do Suprimento' }) + @IsNotEmpty() + @IsString() + readonly supplyCategoryId = ''; + + @ApiProperty({ type: 'string', example: 'Nome do Suprimento' }) + @IsNotEmpty() + @IsString() + readonly name = ''; +} diff --git a/src/supply/dtos/UpdateSupplyDTO.ts b/src/supply/dtos/UpdateSupplyDTO.ts new file mode 100644 index 00000000..75c442ed --- /dev/null +++ b/src/supply/dtos/UpdateSupplyDTO.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateSupplyDTO { + @ApiProperty({ + required: false, + type: 'string', + example: 'ID da Categoria do Suprimento', + }) + @IsOptional() + @IsString() + readonly supplyCategoryId?: string; + + @ApiProperty({ + required: false, + type: 'string', + example: 'Nome do Suprimento', + }) + @IsOptional() + @IsString() + readonly name?: string; +} diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 55133484..3e0a77b6 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -8,18 +8,27 @@ import { Post, Put, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, +} from '@nestjs/swagger'; import { ServerResponse } from '../utils'; import { SupplyService } from './supply.service'; +import { CreateSupplyDTO } from './dtos/CreateSupplyDTO'; +import { UpdateSupplyDTO } from './dtos/UpdateSupplyDTO'; @ApiTags('Suprimentos') +@ApiInternalServerErrorResponse() @Controller('supplies') export class SupplyController { private logger = new Logger(SupplyController.name); constructor(private readonly supplyServices: SupplyService) {} + @ApiOkResponse() @Get('') async index() { try { @@ -31,8 +40,10 @@ export class SupplyController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Post('') - async store(@Body() body) { + async store(@Body() body: CreateSupplyDTO) { try { const data = await this.supplyServices.store(body); return new ServerResponse(200, 'Successfully created supply', data); @@ -42,8 +53,10 @@ export class SupplyController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':id') - async update(@Param('id') id: string, @Body() body) { + async update(@Param('id') id: string, @Body() body: UpdateSupplyDTO) { try { const data = await this.supplyServices.update(id, body); return new ServerResponse(200, 'Successfully updated supply', data); diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index a3caf57e..081c12b6 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -1,14 +1,15 @@ -import z from 'zod'; import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateSupplySchema, UpdateSupplySchema } from './types'; +import { CreateSupplyDTO } from './dtos/CreateSupplyDTO'; +import { UpdateSupplyDTO } from './dtos/UpdateSupplyDTO'; @Injectable() export class SupplyService { constructor(private readonly prismaService: PrismaService) {} - async store(body: z.infer) { + async store(body: CreateSupplyDTO) { const payload = CreateSupplySchema.parse(body); return await this.prismaService.supply.create({ data: { @@ -18,7 +19,7 @@ export class SupplyService { }); } - async update(id: string, body: z.infer) { + async update(id: string, body: UpdateSupplyDTO) { const payload = UpdateSupplySchema.parse(body); await this.prismaService.supply.update({ where: { From 48a3e36bcf7fc0a506b77274282d22b0a3249dc4 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 11:59:25 -0300 Subject: [PATCH 08/13] feat: add req body validation & swagger docs to supply-categories routes --- .../dtos/CreateSupplyCategoryDTO.ts | 9 +++++++ .../dtos/UpdateSupplyCategoryDTO.ts | 13 ++++++++++ .../supply-categories.controller.ts | 25 ++++++++++++++++--- .../supply-categories.service.ts | 7 +++--- 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 src/supply-categories/dtos/CreateSupplyCategoryDTO.ts create mode 100644 src/supply-categories/dtos/UpdateSupplyCategoryDTO.ts diff --git a/src/supply-categories/dtos/CreateSupplyCategoryDTO.ts b/src/supply-categories/dtos/CreateSupplyCategoryDTO.ts new file mode 100644 index 00000000..96b6a863 --- /dev/null +++ b/src/supply-categories/dtos/CreateSupplyCategoryDTO.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateSupplyCategoryDTO { + @ApiProperty({ type: 'string', example: 'Nome da categoria do suprimento' }) + @IsNotEmpty() + @IsString() + readonly name = ''; +} diff --git a/src/supply-categories/dtos/UpdateSupplyCategoryDTO.ts b/src/supply-categories/dtos/UpdateSupplyCategoryDTO.ts new file mode 100644 index 00000000..b1ccfea1 --- /dev/null +++ b/src/supply-categories/dtos/UpdateSupplyCategoryDTO.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateSupplyCategoryDTO { + @ApiProperty({ + required: false, + type: 'string', + example: 'Nome da categoria do suprimento', + }) + @IsOptional() + @IsString() + readonly name?: string; +} diff --git a/src/supply-categories/supply-categories.controller.ts b/src/supply-categories/supply-categories.controller.ts index 96106050..2bee6772 100644 --- a/src/supply-categories/supply-categories.controller.ts +++ b/src/supply-categories/supply-categories.controller.ts @@ -9,13 +9,23 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { SupplyCategoriesService } from './supply-categories.service'; import { ServerResponse } from '../utils'; import { AdminGuard } from '@/guards/admin.guard'; +import { CreateSupplyCategoryDTO } from './dtos/CreateSupplyCategoryDTO'; +import { UpdateSupplyCategoryDTO } from './dtos/UpdateSupplyCategoryDTO'; @ApiTags('Categoria de Suprimentos') +@ApiInternalServerErrorResponse() @Controller('supply-categories') export class SupplyCategoriesController { private logger = new Logger(SupplyCategoriesController.name); @@ -24,6 +34,7 @@ export class SupplyCategoriesController { private readonly supplyCategoryServices: SupplyCategoriesService, ) {} + @ApiOkResponse() @Get('') async index() { try { @@ -39,9 +50,13 @@ export class SupplyCategoriesController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Post('') @UseGuards(AdminGuard) - async store(@Body() body) { + async store(@Body() body: CreateSupplyCategoryDTO) { try { const data = await this.supplyCategoryServices.store(body); return new ServerResponse( @@ -55,9 +70,13 @@ export class SupplyCategoriesController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':id') @UseGuards(AdminGuard) - async update(@Param('id') id: string, @Body() body) { + async update(@Param('id') id: string, @Body() body: UpdateSupplyCategoryDTO) { try { const data = await this.supplyCategoryServices.update(id, body); return new ServerResponse( diff --git a/src/supply-categories/supply-categories.service.ts b/src/supply-categories/supply-categories.service.ts index 4ebd796a..2ef0b85d 100644 --- a/src/supply-categories/supply-categories.service.ts +++ b/src/supply-categories/supply-categories.service.ts @@ -1,17 +1,18 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { z } from 'zod'; import { CreateSupplyCategorySchema, UpdateSupplyCategorySchema, } from './types'; +import { CreateSupplyCategoryDTO } from './dtos/CreateSupplyCategoryDTO'; +import { UpdateSupplyCategoryDTO } from './dtos/UpdateSupplyCategoryDTO'; @Injectable() export class SupplyCategoriesService { constructor(private readonly prismaService: PrismaService) {} - async store(body: z.infer) { + async store(body: CreateSupplyCategoryDTO) { const payload = CreateSupplyCategorySchema.parse(body); await this.prismaService.supplyCategory.create({ data: { @@ -21,7 +22,7 @@ export class SupplyCategoriesService { }); } - async update(id: string, body: z.infer) { + async update(id: string, body: UpdateSupplyCategoryDTO) { const payload = UpdateSupplyCategorySchema.parse(body); await this.prismaService.supplyCategory.update({ where: { From cefe2a44f1ab343462b02878a706db627c4e4ae4 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 12:14:21 -0300 Subject: [PATCH 09/13] feat: add req body validation & swagger docs to shelter managers routes --- .../dtos/CreateShelterManagerDTO.ts | 14 ++++++++++++++ .../shelter-managers.controller.ts | 18 ++++++++++++++++-- .../shelter-managers.service.ts | 4 ++-- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/shelter-managers/dtos/CreateShelterManagerDTO.ts diff --git a/src/shelter-managers/dtos/CreateShelterManagerDTO.ts b/src/shelter-managers/dtos/CreateShelterManagerDTO.ts new file mode 100644 index 00000000..d2f90a03 --- /dev/null +++ b/src/shelter-managers/dtos/CreateShelterManagerDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateShelterManagerDTO { + @ApiProperty({ type: 'string', example: 'ID do Abrigo' }) + @IsNotEmpty() + @IsString() + readonly shelterId = ''; + + @ApiProperty({ type: 'string', example: 'ID do Usuário' }) + @IsNotEmpty() + @IsString() + readonly userId = ''; +} diff --git a/src/shelter-managers/shelter-managers.controller.ts b/src/shelter-managers/shelter-managers.controller.ts index adea36c2..b16c397e 100644 --- a/src/shelter-managers/shelter-managers.controller.ts +++ b/src/shelter-managers/shelter-managers.controller.ts @@ -9,13 +9,22 @@ import { Query, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { ShelterManagersService } from './shelter-managers.service'; import { ServerResponse } from '../utils'; import { AdminGuard } from '@/guards/admin.guard'; +import { CreateShelterManagerDTO } from './dtos/CreateShelterManagerDTO'; @ApiTags('Admin de Abrigo') +@ApiInternalServerErrorResponse() @Controller('shelter/managers') export class ShelterManagersController { private logger = new Logger(ShelterManagersController.name); @@ -24,9 +33,13 @@ export class ShelterManagersController { private readonly shelterManagerServices: ShelterManagersService, ) {} + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Post('') @UseGuards(AdminGuard) - async store(@Body() body) { + async store(@Body() body: CreateShelterManagerDTO) { try { await this.shelterManagerServices.store(body); return new ServerResponse(200, 'Successfully added manager to shelter'); @@ -36,6 +49,7 @@ export class ShelterManagersController { } } + @ApiOkResponse() @Get(':shelterId') async index( @Param('shelterId') shelterId: string, diff --git a/src/shelter-managers/shelter-managers.service.ts b/src/shelter-managers/shelter-managers.service.ts index 648e9719..753dcb52 100644 --- a/src/shelter-managers/shelter-managers.service.ts +++ b/src/shelter-managers/shelter-managers.service.ts @@ -1,14 +1,14 @@ -import { z } from 'zod'; import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateShelterManagerSchema } from './types'; +import { CreateShelterManagerDTO } from './dtos/CreateShelterManagerDTO'; @Injectable() export class ShelterManagersService { constructor(private readonly prismaService: PrismaService) {} - async store(body: z.infer) { + async store(body: CreateShelterManagerDTO) { const { shelterId, userId } = CreateShelterManagerSchema.parse(body); await this.prismaService.shelterManagers.create({ data: { From 20572c6591b686ad8e964e75b9fa2607805f25c2 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 16:45:40 -0300 Subject: [PATCH 10/13] feat: add req body validation & swagger docs to shelter supplies routes --- .../dtos/CreateShelterSupplyDTO.ts | 47 +++++++++++++++++++ .../dtos/UpdateManyShelterSupplyDTO.ts | 14 ++++++ .../dtos/UpdateShelterSupplyDTO.ts | 27 +++++++++++ .../shelter-supply.controller.ts | 45 +++++++++++++----- src/shelter-supply/shelter-supply.service.ts | 45 ++++++++++++------ src/shelter-supply/types.ts | 29 +++++------- 6 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 src/shelter-supply/dtos/CreateShelterSupplyDTO.ts create mode 100644 src/shelter-supply/dtos/UpdateManyShelterSupplyDTO.ts create mode 100644 src/shelter-supply/dtos/UpdateShelterSupplyDTO.ts diff --git a/src/shelter-supply/dtos/CreateShelterSupplyDTO.ts b/src/shelter-supply/dtos/CreateShelterSupplyDTO.ts new file mode 100644 index 00000000..c06aa276 --- /dev/null +++ b/src/shelter-supply/dtos/CreateShelterSupplyDTO.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, + Min, +} from 'class-validator'; +import { SupplyPriority } from 'src/supply/types'; + +export class CreateShelterSupplyDTO { + constructor() { + this.priority = SupplyPriority.UnderControl; + } + + @ApiProperty({ type: 'string', example: 'ID do Abrigo' }) + @IsNotEmpty() + @IsString() + readonly shelterId = ''; + + @ApiProperty({ type: 'string', example: 'ID do Suprimento' }) + @IsNotEmpty() + @IsString() + readonly supplyId = ''; + + @ApiProperty({ + description: 'Prioridade de suprimento', + enum: SupplyPriority, + }) + @IsNotEmpty() + @IsEnum(SupplyPriority) + @Transform((value) => Number(value.value)) + readonly priority: SupplyPriority; + + @ApiProperty({ + required: false, + type: 'number', + example: 1, + }) + @IsOptional() + @IsNumber() + @Min(1) + @Transform((value) => Number(value.value)) + readonly quantity?: number; +} diff --git a/src/shelter-supply/dtos/UpdateManyShelterSupplyDTO.ts b/src/shelter-supply/dtos/UpdateManyShelterSupplyDTO.ts new file mode 100644 index 00000000..1801219c --- /dev/null +++ b/src/shelter-supply/dtos/UpdateManyShelterSupplyDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ArrayMinSize, IsArray, IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateManyShelterSupplySchemaDTO { + @ApiProperty({ + type: [String], + example: ['ID do Suprimento', 'ID do Suprimento 2'], + }) + @IsArray() + @ArrayMinSize(1) + @IsNotEmpty() + @IsString({ each: true }) + readonly ids!: string; +} diff --git a/src/shelter-supply/dtos/UpdateShelterSupplyDTO.ts b/src/shelter-supply/dtos/UpdateShelterSupplyDTO.ts new file mode 100644 index 00000000..746789a1 --- /dev/null +++ b/src/shelter-supply/dtos/UpdateShelterSupplyDTO.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsEnum, IsOptional, IsNumber, Min } from 'class-validator'; +import { SupplyPriority } from 'src/supply/types'; + +export class UpdateShelterSupplyDTO { + @ApiProperty({ + required: false, + description: 'Prioridade de suprimento', + enum: SupplyPriority, + }) + @IsOptional() + @IsEnum(SupplyPriority) + @Transform((value) => Number(value.value)) + readonly priority?: SupplyPriority; + + @ApiProperty({ + required: false, + type: 'number', + example: 1, + }) + @IsOptional() + @IsNumber() + @Min(1) + @Transform((value) => Number(value.value)) + readonly quantity?: number; +} diff --git a/src/shelter-supply/shelter-supply.controller.ts b/src/shelter-supply/shelter-supply.controller.ts index e2d3df69..f39b4d8f 100644 --- a/src/shelter-supply/shelter-supply.controller.ts +++ b/src/shelter-supply/shelter-supply.controller.ts @@ -9,19 +9,31 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiInternalServerErrorResponse, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { ShelterSupplyService } from './shelter-supply.service'; import { ServerResponse } from '../utils'; import { DistributionCenterGuard } from '@/guards/distribution-center.guard'; +import { CreateShelterSupplyDTO } from './dtos/CreateShelterSupplyDTO'; +import { UpdateShelterSupplyDTO } from './dtos/UpdateShelterSupplyDTO'; +import { UpdateManyShelterSupplySchemaDTO } from './dtos/UpdateManyShelterSupplyDTO'; @ApiTags('Suprimento de abrigos') +@ApiInternalServerErrorResponse() @Controller('shelter/supplies') export class ShelterSupplyController { private logger = new Logger(ShelterSupplyController.name); constructor(private readonly shelterSupplyService: ShelterSupplyService) {} + @ApiOkResponse() @Get(':shelterId') async index(@Param('shelterId') shelterId: string) { try { @@ -33,8 +45,10 @@ export class ShelterSupplyController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Post('') - async store(@Body() body) { + async store(@Body() body: CreateShelterSupplyDTO) { try { const data = await this.shelterSupplyService.store(body); return new ServerResponse( @@ -48,17 +62,20 @@ export class ShelterSupplyController { } } + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':shelterId/:supplyId') async update( - @Body() body, + @Body() body: UpdateShelterSupplyDTO, @Param('shelterId') shelterId: string, @Param('supplyId') supplyId: string, ) { try { - const data = await this.shelterSupplyService.update({ - where: { shelterId, supplyId }, - data: body, - }); + const data = await this.shelterSupplyService.update( + body, + shelterId, + supplyId, + ); return new ServerResponse( 200, 'Successfully updated shelter supply', @@ -70,14 +87,18 @@ export class ShelterSupplyController { } } + @ApiBearerAuth() + @ApiUnauthorizedResponse() + @ApiBadRequestResponse() + @ApiOkResponse() @Put(':shelterId/supplies/many') @UseGuards(DistributionCenterGuard) - async updateMany(@Body() body, @Param('shelterId') shelterId: string) { + async updateMany( + @Body() body: UpdateManyShelterSupplySchemaDTO, + @Param('shelterId') shelterId: string, + ) { try { - const data = await this.shelterSupplyService.updateMany({ - shelterId, - ...body, - }); + const data = await this.shelterSupplyService.updateMany(body, shelterId); return new ServerResponse( 200, 'Successfully updated many shelter supplies', diff --git a/src/shelter-supply/shelter-supply.service.ts b/src/shelter-supply/shelter-supply.service.ts index ef4cf97a..c785e2b1 100644 --- a/src/shelter-supply/shelter-supply.service.ts +++ b/src/shelter-supply/shelter-supply.service.ts @@ -1,4 +1,3 @@ -import { z } from 'zod'; import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; @@ -8,6 +7,9 @@ import { UpdateShelterSupplySchema, } from './types'; import { SupplyPriority } from '../supply/types'; +import { CreateShelterSupplyDTO } from './dtos/CreateShelterSupplyDTO'; +import { UpdateShelterSupplyDTO } from './dtos/UpdateShelterSupplyDTO'; +import { UpdateManyShelterSupplySchemaDTO } from './dtos/UpdateManyShelterSupplyDTO'; @Injectable() export class ShelterSupplyService { @@ -31,7 +33,7 @@ export class ShelterSupplyService { }); } - async store(body: z.infer) { + async store(body: CreateShelterSupplyDTO) { const { shelterId, priority, supplyId, quantity } = CreateShelterSupplySchema.parse(body); await this.handleUpdateShelterSum(shelterId, 0, priority); @@ -46,22 +48,31 @@ export class ShelterSupplyService { }); } - async update(body: z.infer) { - const { data, where } = UpdateShelterSupplySchema.parse(body); - const { priority, quantity } = data; + async update( + body: UpdateShelterSupplyDTO, + shelterId: string, + supplyId: string, + ) { + const { + shelterId: shelterIdParse, + supplyId: supplyIdParse, + priority, + quantity, + } = UpdateShelterSupplySchema.parse({ ...body, shelterId, supplyId }); if (priority !== null && priority !== undefined) { const shelterSupply = await this.prismaService.shelterSupply.findFirst({ where: { - shelterId: where.shelterId, - supplyId: where.supplyId, + shelterId: shelterIdParse, + supplyId: supplyIdParse, }, select: { priority: true, }, }); + if (shelterSupply) await this.handleUpdateShelterSum( - where.shelterId, + shelterIdParse, shelterSupply.priority, priority, ); @@ -69,22 +80,26 @@ export class ShelterSupplyService { await this.prismaService.shelterSupply.update({ where: { - shelterId_supplyId: where, + shelterId_supplyId: { + shelterId: shelterIdParse, + supplyId: supplyIdParse, + }, }, data: { - ...data, + priority: priority ?? undefined, quantity: priority !== SupplyPriority.UnderControl ? quantity : null, updatedAt: new Date().toISOString(), }, }); } - async updateMany(body: z.infer) { - const { ids, shelterId } = UpdateManyShelterSupplySchema.parse(body); + async updateMany(body: UpdateManyShelterSupplySchemaDTO, shelterId: string) { + const { ids, shelterId: shelterIdParsed } = + UpdateManyShelterSupplySchema.parse({ ...body, shelterId }); const supplies = await this.prismaService.shelterSupply.findMany({ where: { - shelterId, + shelterId: shelterIdParsed, supplyId: { in: ids, }, @@ -99,7 +114,7 @@ export class ShelterSupplyService { await this.prismaService.$transaction([ this.prismaService.shelter.update({ where: { - id: shelterId, + id: shelterIdParsed, }, data: { prioritySum: { @@ -110,7 +125,7 @@ export class ShelterSupplyService { }), this.prismaService.shelterSupply.updateMany({ where: { - shelterId, + shelterId: shelterIdParsed, supplyId: { in: ids, }, diff --git a/src/shelter-supply/types.ts b/src/shelter-supply/types.ts index 1ff25f90..9b38d423 100644 --- a/src/shelter-supply/types.ts +++ b/src/shelter-supply/types.ts @@ -25,23 +25,18 @@ const CreateShelterSupplySchema = ShelterSupplySchema.pick({ }); const UpdateShelterSupplySchema = z.object({ - data: z - .object({ - priority: z.union([ - z.literal(SupplyPriority.UnderControl), - z.literal(SupplyPriority.Remaining), - z.literal(SupplyPriority.Needing), - z.literal(SupplyPriority.Urgent), - ]), - quantity: z.number().nullable().optional(), - shelterId: z.string(), - supplyId: z.string(), - }) - .partial(), - where: z.object({ - shelterId: z.string(), - supplyId: z.string(), - }), + shelterId: z.string(), + supplyId: z.string(), + priority: z + .union([ + z.literal(SupplyPriority.UnderControl), + z.literal(SupplyPriority.Remaining), + z.literal(SupplyPriority.Needing), + z.literal(SupplyPriority.Urgent), + ]) + .nullable() + .optional(), + quantity: z.number().nullable().optional(), }); const UpdateManyShelterSupplySchema = z.object({ From 03d986448af6f3d4b76f370eb828b308140a4602 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 20:04:03 -0300 Subject: [PATCH 11/13] chore: add swagger @ApiBearerAuth in POST /users route --- src/users/users.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index f4b0fb41..3e86a7a7 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -32,6 +32,7 @@ export class UsersController { constructor(private readonly userServices: UsersService) {} + @ApiBearerAuth() @ApiUnauthorizedResponse() @ApiBadRequestResponse() @ApiInternalServerErrorResponse() From a5c9f717bc81ae2baf3a914cd7c6f12f419af182 Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 22:05:16 -0300 Subject: [PATCH 12/13] fix: fix ShelterQuerysDTO --- src/shelter/dtos/ShelterQuerysDTO.ts | 19 ++++++++++--------- src/shelter/shelter.service.ts | 5 ++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/shelter/dtos/ShelterQuerysDTO.ts b/src/shelter/dtos/ShelterQuerysDTO.ts index e92ddc6c..9f52c28d 100644 --- a/src/shelter/dtos/ShelterQuerysDTO.ts +++ b/src/shelter/dtos/ShelterQuerysDTO.ts @@ -2,6 +2,7 @@ import { IsOptional, IsString, IsNumber, IsIn, IsEnum } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { SupplyPriority } from 'src/supply/types'; import { Transform } from 'class-transformer'; +import { ShelterStatus } from '../types/search.types'; export class ShelterQueryDTO { @ApiProperty({ @@ -11,18 +12,18 @@ export class ShelterQueryDTO { @IsOptional() @IsNumber() @Transform((value) => Number(value.value)) - perPage?: number; + readonly perPage?: number; @ApiProperty({ required: false, description: 'Número da página' }) @IsOptional() @IsNumber() @Transform((value) => Number(value.value)) - page?: number; + readonly page?: number; @ApiProperty({ required: false, description: 'Termo de busca' }) @IsOptional() @IsString() - search?: string; + readonly search?: string; @ApiProperty({ required: false, @@ -31,7 +32,7 @@ export class ShelterQueryDTO { }) @IsOptional() @IsIn(['asc', 'desc']) - order?: 'asc' | 'desc'; + readonly order?: 'asc' | 'desc'; @ApiProperty({ required: false, @@ -39,7 +40,7 @@ export class ShelterQueryDTO { }) @IsOptional() @IsString() - orderBy?: string; + readonly orderBy?: string; @ApiProperty({ required: false, @@ -47,7 +48,7 @@ export class ShelterQueryDTO { }) @IsOptional() @IsString({ each: true }) - supplyCategoryIds?: string[]; + readonly supplyCategoryIds?: string[]; @ApiProperty({ required: false, @@ -57,12 +58,12 @@ export class ShelterQueryDTO { @IsOptional() @IsEnum(SupplyPriority) @Transform((value) => Number(value.value)) - priority?: SupplyPriority; + readonly priority?: SupplyPriority; @ApiProperty({ required: false, description: 'IDs de suprimento' }) @IsOptional() @IsString({ each: true }) - supplyIds?: string[]; + readonly supplyIds?: string[]; @ApiProperty({ required: false, @@ -70,5 +71,5 @@ export class ShelterQueryDTO { }) @IsOptional() @IsIn(['available', 'unavailable', 'waiting'], { each: true }) - shelterStatus?: ('available' | 'unavailable' | 'waiting')[]; // MUDAR !!!! + readonly shelterStatus?: ShelterStatus[]; } diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index 3ae90560..538bf564 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -10,13 +10,13 @@ import { UpdateShelterSchema, } from './types/types'; import { subDays } from 'date-fns'; -import { SupplyPriority } from 'src/supply/types'; import { SearchSchema } from 'src/types'; import { ShelterSearch, parseTagResponse } from './ShelterSearch'; +import { SupplyPriority } from '../supply/types'; +import { ShelterSearchPropsSchema } from './types/search.types'; import { CreateShelterDTO } from './dtos/CreateShelterDTO'; import { ShelterQueryDTO } from './dtos/ShelterQuerysDTO'; import { UpdateShelterDTO } from './dtos/UpdateShelterDTO'; -import { ShelterSearchPropsSchema } from './types/search.types'; import { FullUpdateShelterDTO } from './dtos/FullUpdateShelterDTO'; @Injectable() @@ -117,7 +117,6 @@ export class ShelterService { return data; } - // ARRUMAR o queryData async index(query: ShelterQueryDTO) { const { order, From 3e68056815934e8a52dacda1283abdc631c8a8ff Mon Sep 17 00:00:00 2001 From: ocsoares Date: Mon, 13 May 2024 22:23:12 -0300 Subject: [PATCH 13/13] chore: add fields to ShelterQuerysDTO --- src/shelter/dtos/ShelterQuerysDTO.ts | 52 +++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/shelter/dtos/ShelterQuerysDTO.ts b/src/shelter/dtos/ShelterQuerysDTO.ts index 9f52c28d..b435dd3f 100644 --- a/src/shelter/dtos/ShelterQuerysDTO.ts +++ b/src/shelter/dtos/ShelterQuerysDTO.ts @@ -1,4 +1,11 @@ -import { IsOptional, IsString, IsNumber, IsIn, IsEnum } from 'class-validator'; +import { + IsOptional, + IsString, + IsNumber, + IsIn, + IsEnum, + ValidateNested, +} from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { SupplyPriority } from 'src/supply/types'; import { Transform } from 'class-transformer'; @@ -72,4 +79,47 @@ export class ShelterQueryDTO { @IsOptional() @IsIn(['available', 'unavailable', 'waiting'], { each: true }) readonly shelterStatus?: ShelterStatus[]; + + @ApiProperty({ + required: false, + description: 'Informação sobre tags do abrigo', + }) + @IsOptional() + @ValidateNested() + readonly tags?: Record< + 'NeedVolunteers' | 'NeedDonations' | 'RemainingSupplies', + number | undefined + >; + + @ApiProperty({ required: false, description: 'Lista de cidades' }) + @IsOptional() + @IsString({ each: true }) + readonly cities?: string[]; + + @ApiProperty({ + required: false, + description: 'Latitude', + }) + @IsOptional() + @IsNumber() + @Transform((value) => Number(value.value)) + readonly latitude?: number; + + @ApiProperty({ + required: false, + description: 'Longitude', + }) + @IsOptional() + @IsNumber() + @Transform((value) => Number(value.value)) + readonly longitude?: number; + + @ApiProperty({ + required: false, + description: 'Raio em metros', + }) + @IsOptional() + @IsNumber() + @Transform((value) => Number(value.value)) + readonly radiusInMeters?: number; }