From 70e5f61aa6317a4598a4e7352f393ec16e9d1bce Mon Sep 17 00:00:00 2001 From: Italo Date: Sun, 3 Mar 2024 16:45:35 -0300 Subject: [PATCH] feat(CC-99): update patient endpoint --- apps/core-rest-api/http/patient-client.http | 11 ++ .../adapters/controllers/api/api.module.ts | 40 +++--- .../use-cases/patient/update-patient/docs.ts | 17 +++ .../patient/update-patient/input-dto.ts | 33 +++++ .../nestjs-update-patient.service.ts | 11 ++ .../update-patient.controller.ts | 40 ++++++ .../update-patient/update-patient.e2e-spec.ts | 127 ++++++++++++++++++ 7 files changed, 261 insertions(+), 18 deletions(-) create mode 100644 apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/docs.ts create mode 100644 apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/input-dto.ts create mode 100644 apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/nestjs-update-patient.service.ts create mode 100644 apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.controller.ts create mode 100644 apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.e2e-spec.ts diff --git a/apps/core-rest-api/http/patient-client.http b/apps/core-rest-api/http/patient-client.http index a2d0b5b..afa811a 100644 --- a/apps/core-rest-api/http/patient-client.http +++ b/apps/core-rest-api/http/patient-client.http @@ -23,3 +23,14 @@ Authorization: Bearer {{authToken}} DELETE http://localhost:3333/core/patient/{{$dotenv PATIENT_ID}}/delete HTTP/1.1 Content-Type: application/json Authorization: Bearer {{authToken}} + +### Update a patient +# @name update_patient +PATCH http://localhost:3333/core/patient/{{$dotenv PATIENT_ID}}/update HTTP/1.1 +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "name": "teste", + "email": "abigail.doe@email.com" +} diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/api.module.ts b/apps/core-rest-api/src/app/adapters/controllers/api/api.module.ts index dedf0c2..e4a1114 100644 --- a/apps/core-rest-api/src/app/adapters/controllers/api/api.module.ts +++ b/apps/core-rest-api/src/app/adapters/controllers/api/api.module.ts @@ -11,33 +11,35 @@ import { BcryptHasherService } from '../../../core/shared/cryptography/use-cases import { PostgreSqlPrismaOrmService } from '../../database/infra/prisma/prisma.service'; import { DatabaseRepositoriesModule } from '../../database/repositories/repositories.module'; -import { CreateClinicController } from './use-cases/clinic/create-clinic/create-clinic.controller'; -import { DeleteClinicController } from './use-cases/clinic/delete-clinic/delete-clinic.controller'; -import { UpdateClinicController } from './use-cases/clinic/update-clinic/update-clinic.controller'; -import { CreatePatientController } from './use-cases/patient/create-patient/create-patient.controller'; -import { DeletePatientController } from './use-cases/patient/delete-patient/delete-patient.controller'; -import { AuthenticatePsychologistController } from './use-cases/psychologist/authenticate-psychologist/authenticate-psychologist.controller'; -import { CreatePsychologistController } from './use-cases/psychologist/create-psychologist/create-psychologist.controller'; -import { DeletePsychologistController } from './use-cases/psychologist/delete-psychologist/delete-psychologist.controller'; - -import { CreateAppointmentController } from './use-cases/appointment/create-appointment/create-appointment.controller'; import { NestjsCreateAppointmentService } from './use-cases/appointment/create-appointment/nestjs-create-appointment.service'; import { NestjsCreateClinicService } from './use-cases/clinic/create-clinic/nestjs-create-clinic.service'; import { NestjsDeleteClinicService } from './use-cases/clinic/delete-clinic/nestjs-delete-clinic.service'; import { NestjsUpdateClinicService } from './use-cases/clinic/update-clinic/nestjs-update-clinic.service'; -import { CreatePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/create-patient-appointment-registry/create-patient-appointment-registry.controller'; -import { NestjsCreatePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/create-patient-appointment-registry/nestjs-create-patient-appointment-registry.service'; -import { DeletePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/delete-patient-appointment-registry/delete-patient-appointment-registry.controller'; -import { NestjsDeletePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/delete-patient-appointment-registry/nestjs-delete-patient-appointment-registry.service'; -import { NestjsUpdatePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/update-patient-appointment-registry/nestjs-update-patient-appointment-registry.service'; -import { UpdatePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/update-patient-appointment-registry/update-patient-appointment-registry.controller'; import { NestjsCreatePatientService } from './use-cases/patient/create-patient/nestjs-create-patient.service'; import { NestjsDeletePatientService } from './use-cases/patient/delete-patient/nestjs-delete-patient.service'; +import { NestjsUpdatePatientService } from './use-cases/patient/update-patient/nestjs-update-patient.service'; import { NestjsAuthenticatePsychologistService } from './use-cases/psychologist/authenticate-psychologist/nestjs-authenticate-psychologist.service'; import { NestjsCreatePsychologistService } from './use-cases/psychologist/create-psychologist/nestjs-create-psychologist.service'; import { NestjsDeletePsychologistService } from './use-cases/psychologist/delete-psychologist/nestjs-delete-psychologist.service'; import { NestjsUpdatePsychologistService } from './use-cases/psychologist/update-psychologist/nestjs-update-psychologist.service'; +import { CreateAppointmentController } from './use-cases/appointment/create-appointment/create-appointment.controller'; +import { CreateClinicController } from './use-cases/clinic/create-clinic/create-clinic.controller'; +import { DeleteClinicController } from './use-cases/clinic/delete-clinic/delete-clinic.controller'; +import { UpdateClinicController } from './use-cases/clinic/update-clinic/update-clinic.controller'; +import { CreatePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/create-patient-appointment-registry/create-patient-appointment-registry.controller'; +import { NestjsCreatePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/create-patient-appointment-registry/nestjs-create-patient-appointment-registry.service'; +import { DeletePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/delete-patient-appointment-registry/delete-patient-appointment-registry.controller'; +import { NestjsDeletePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/delete-patient-appointment-registry/nestjs-delete-patient-appointment-registry.service'; +import { NestjsUpdatePatientAppointmentRegistryService } from './use-cases/patient-appointment-registry/update-patient-appointment-registry/nestjs-update-patient-appointment-registry.service'; +import { UpdatePatientAppointmentRegistryController } from './use-cases/patient-appointment-registry/update-patient-appointment-registry/update-patient-appointment-registry.controller'; +import { CreatePatientController } from './use-cases/patient/create-patient/create-patient.controller'; +import { DeletePatientController } from './use-cases/patient/delete-patient/delete-patient.controller'; +import { UpdatePatientController } from './use-cases/patient/update-patient/update-patient.controller'; +import { AuthenticatePsychologistController } from './use-cases/psychologist/authenticate-psychologist/authenticate-psychologist.controller'; +import { CreatePsychologistController } from './use-cases/psychologist/create-psychologist/create-psychologist.controller'; +import { DeletePsychologistController } from './use-cases/psychologist/delete-psychologist/delete-psychologist.controller'; + @Module({ imports: [ ConfigModule.forRoot({ @@ -62,7 +64,8 @@ import { NestjsUpdatePsychologistService } from './use-cases/psychologist/update CreatePatientAppointmentRegistryController, CreateAppointmentController, DeletePatientAppointmentRegistryController, - UpdatePatientAppointmentRegistryController + UpdatePatientAppointmentRegistryController, + UpdatePatientController, ], providers: [ BcryptHasherService, @@ -79,7 +82,8 @@ import { NestjsUpdatePsychologistService } from './use-cases/psychologist/update NestjsCreatePatientAppointmentRegistryService, NestjsCreateAppointmentService, NestjsDeletePatientAppointmentRegistryService, - NestjsUpdatePatientAppointmentRegistryService + NestjsUpdatePatientAppointmentRegistryService, + NestjsUpdatePatientService, ], }) export class ApiModule {} diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/docs.ts b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/docs.ts new file mode 100644 index 0000000..2b94513 --- /dev/null +++ b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/docs.ts @@ -0,0 +1,17 @@ +import { OperationObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; + +const description = ` +--- +\`Experimental\` +--- + +### Update an existing Patient + +This endpoint help you to update an existing Patient. +You must at least provide one of the following body parameters +`; + +export const patchMethodDocs: Partial = { + summary: 'Update an existing Patient', + description, +}; diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/input-dto.ts b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/input-dto.ts new file mode 100644 index 0000000..46530ba --- /dev/null +++ b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/input-dto.ts @@ -0,0 +1,33 @@ +import { IsEnum, IsMobilePhone, IsOptional, IsString, IsUUID } from 'class-validator'; +import { PaymentMethod } from '../../../../../../core/shared/interfaces/payments'; + +export class UpdatePatientControllerReqBodyInputDto { + @IsOptional() + @IsString() + name?: string; + + @IsOptional() + @IsString() + email?: string; + + @IsOptional() + @IsString() + cpf?: string; + + @IsOptional() + @IsMobilePhone('pt-BR') + telephone?: string; + + @IsOptional() + @IsEnum(PaymentMethod) + paymentMethod?: PaymentMethod; + + @IsOptional() + @IsString() + clinicId?: string; +} + +export class UpdatePatientControllerReqParamsInputDto { + @IsUUID() + id!: string; +} diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/nestjs-update-patient.service.ts b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/nestjs-update-patient.service.ts new file mode 100644 index 0000000..94e6984 --- /dev/null +++ b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/nestjs-update-patient.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; + +import { PatientDatabaseRepository } from '../../../../../../../app/core/domains/patient/repositories/database-repository'; +import { UpdatePatientService } from '../../../../../../../app/core/domains/patient/use-cases/update-patient/update-patient.service'; + +@Injectable() +export class NestjsUpdatePatientService extends UpdatePatientService { + constructor(patientDatabaseRepository: PatientDatabaseRepository) { + super(patientDatabaseRepository); + } +} diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.controller.ts b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.controller.ts new file mode 100644 index 0000000..9fe48e9 --- /dev/null +++ b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.controller.ts @@ -0,0 +1,40 @@ +import { BadRequestException, Body, Controller, Param, Patch } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { patchMethodDocs } from './docs'; + +import { GlobalAppHttpException } from '../../../../../../shared/errors/globalAppHttpException'; +import { + UpdatePatientControllerReqBodyInputDto, + UpdatePatientControllerReqParamsInputDto, +} from './input-dto'; +import { NestjsUpdatePatientService } from './nestjs-update-patient.service'; + +@ApiTags('Patient') +@ApiBearerAuth() +@Controller({ + path: 'patient', +}) +export class UpdatePatientController { + constructor(private updatePatientService: NestjsUpdatePatientService) {} + + @Patch(':id/update') + @ApiOperation(patchMethodDocs) + async execute( + @Param() { id }: UpdatePatientControllerReqParamsInputDto, + @Body() updatePsychologistDto: UpdatePatientControllerReqBodyInputDto, + ) { + try { + const isReqBodyEmpty = Object.keys(updatePsychologistDto).length === 0; + + if (isReqBodyEmpty) { + throw new BadRequestException('Must provide at least one field to update'); + } + + await this.updatePatientService.execute({ id, ...updatePsychologistDto }); + + return { message: 'Patient updated successfully' }; + } catch (error: unknown) { + throw new GlobalAppHttpException(error); + } + } +} diff --git a/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.e2e-spec.ts b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.e2e-spec.ts new file mode 100644 index 0000000..cb46e9a --- /dev/null +++ b/apps/core-rest-api/src/app/adapters/controllers/api/use-cases/patient/update-patient/update-patient.e2e-spec.ts @@ -0,0 +1,127 @@ +import request from 'supertest'; + +import { INestApplication } from '@nestjs/common'; + +import { faker } from '@faker-js/faker'; +import { setupE2ETest } from '../../../../../../../../tests/utils/e2e-tests-initial-setup'; +import { PatientEntity } from '../../../../../../core/domains/patient/entities/patient/entity'; + +describe('[E2E] - Update Psychologist Account', () => { + let app: INestApplication; + + let patient: PatientEntity; + let access_token: string; + let invalid_access_token: string; + + beforeAll(async () => { + const setup = await setupE2ETest(); + app = setup.app; + + patient = setup.patient; + + access_token = setup.access_token; + invalid_access_token = setup.invalid_access_token; + }); + + it('[PATCH] - Should successfully update a patient account', async () => { + const updateInfos = { + name: 'New Name', + email: 'new_email@email.com', + }; + + const response = await request(app.getHttpServer()) + .patch(`/patient/${patient.id}/update`) + .set('Authorization', `Bearer ${access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(200); + expect(response.body.message).toBe('Patient updated successfully'); + }); + + it('[PATCH] - Should return an error when trying to update a patient without access_token', async () => { + const updateInfos = { + name: 'New Name', + }; + + const response = await request(app.getHttpServer()) + .patch(`/patient/${patient.id}/update`) + .send(updateInfos); + + expect(response.statusCode).toBe(401); + expect(response.body.message).toBe('Invalid JWT token'); + }); + + it('[PATCH] - Should return an error when trying to update a patient with invalid access_token', async () => { + const updateInfos = { + name: 'New Name', + }; + + const response = await request(app.getHttpServer()) + .patch(`/patient/${patient.id}/update`) + .set('Authorization', `Bearer ${invalid_access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(401); + expect(response.body.message).toBe('Invalid JWT token'); + }); + + it('[PATCH] - Should return an error when trying to update a patient with invalid id', async () => { + const updateInfos = { + name: 'New Name', + }; + + const response = await request(app.getHttpServer()) + .patch(`/patient/invalid_id/update`) + .set('Authorization', `Bearer ${access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(400); + expect(response.body.message).toEqual(['id must be a UUID']); + }); + + it('[PATCH] - Should return an error when trying to update a patient with non existent id', async () => { + const nonExistentId = faker.string.uuid(); + + const updateInfos = { + name: 'New Name', + }; + + const response = await request(app.getHttpServer()) + .patch(`/patient/${nonExistentId}/update`) + .set('Authorization', `Bearer ${access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(404); + expect(response.body.message).toBe('patient not found'); + }); + + it('[PATCH] - Should return an error when trying to update a patient with empty request body', async () => { + const updateInfos = {}; + + const response = await request(app.getHttpServer()) + .patch(`/psychologist/${patient.id}/update`) + .set('Authorization', `Bearer ${access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(400); + expect(response.body.message).toBe('Must provide at least one field to update'); + }); + + it('[PATCH] - Should return an error when trying to update a patient with an invalid body type params', async () => { + const updateInfos = { + name: 123, + email: 123, + }; + + const response = await request(app.getHttpServer()) + .patch(`/psychologist/${patient.id}/update`) + .set('Authorization', `Bearer ${access_token}`) + .send(updateInfos); + + expect(response.statusCode).toBe(400); + expect(response.body.message).toEqual([ + 'name must be a string', + 'email must be a string', + ]); + }); +});