diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 133f2d5..99f16ad 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -876,6 +876,52 @@ ] } }, + "/shifts/upsert/{email}/{date}": { + "put": { + "operationId": "ShiftsController_upsert_by_date", + "parameters": [ + { + "name": "email", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "date", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertShiftDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "access-token": [] + } + ], + "tags": [ + "Shifts" + ] + } + }, "/shifts": { "post": { "operationId": "ShiftsController_create", @@ -2513,6 +2559,10 @@ } } }, + "UpsertShiftDto": { + "type": "object", + "properties": {} + }, "CreateShiftDto": { "type": "object", "properties": { diff --git a/src/app.module.ts b/src/app.module.ts index 7a4aadf..ebd5c5d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { BadRequestException, Module, ValidationPipe } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ArchivalModule } from './modules/archival/archival.module'; @@ -22,6 +22,9 @@ import { ShiftsModule } from './modules/shifts/shifts.module'; import { TimesheetsModule } from './modules/timesheets/timesheets.module'; import { UsersModule } from './modules/users-management/users.module'; import { ConfigModule } from '@nestjs/config'; +import { APP_FILTER, APP_PIPE } from '@nestjs/core'; +import { HttpExceptionFilter } from './common/filters/http-exception.filter'; +import { ValidationError } from 'class-validator'; @Module({ imports: [ @@ -46,6 +49,29 @@ import { ConfigModule } from '@nestjs/config'; UsersModule, ], controllers: [AppController, HealthController], - providers: [AppService, OvertimeService], + providers: [ + AppService, + OvertimeService, + { + provide: APP_FILTER, + useClass: HttpExceptionFilter + }, + { + provide: APP_PIPE, + useValue: new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + exceptionFactory: (errors: ValidationError[] = [])=> { + const messages = errors.flatMap((e)=> Object.values(e.constraints ?? {})); + return new BadRequestException({ + statusCode: 400, + error: 'Bad Request', + message: messages.length ? messages : errors, + }); + }, + }), + }, + ], }) export class AppModule {} diff --git a/src/common/filters/http-exception.filter.ts b/src/common/filters/http-exception.filter.ts new file mode 100644 index 0000000..a44c4c9 --- /dev/null +++ b/src/common/filters/http-exception.filter.ts @@ -0,0 +1,24 @@ +import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common"; +import { Request, Response } from 'express'; + +@Catch(HttpException) +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: HttpException, host: ArgumentsHost) { + const http_context = host.switchToHttp(); + const response = http_context.getResponse(); + const request = http_context.getRequest(); + const http_status = exception.getStatus(); + + const exception_response = exception.getResponse(); + const normalized = typeof exception_response === 'string' + ? { message: exception_response } + : (exception_response as Record); + const response_body = { + statusCode: http_status, + timestamp: new Date().toISOString(), + path: request.url, + ...normalized, + }; + response.status(http_status).json(response_body); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 504ffe1..88237b6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,6 @@ import { ATT_TMP_DIR } from './config/attachment.config'; // log to be removed p import { ModuleRef, NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; -import { ValidationPipe } from '@nestjs/common'; // import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard'; import { RolesGuard } from './common/guards/roles.guard'; import { OwnershipGuard } from './common/guards/ownership.guard'; @@ -25,13 +24,11 @@ async function bootstrap() { const reflector = app.get(Reflector); //setup Reflector for Roles() - app.useGlobalPipes( - new ValidationPipe({ whitelist: true, transform: true})); app.useGlobalGuards( // new JwtAuthGuard(reflector), //Authentification JWT new RolesGuard(reflector), //deny-by-default and Role-based Access Control new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet - ); + ); // Authentication and session app.use(session({ diff --git a/src/modules/shifts/helpers/shifts-date-time-helpers.ts b/src/modules/shifts/helpers/shifts-date-time-helpers.ts index 3cf3683..94ecf5e 100644 --- a/src/modules/shifts/helpers/shifts-date-time-helpers.ts +++ b/src/modules/shifts/helpers/shifts-date-time-helpers.ts @@ -16,4 +16,3 @@ export function toDateOnlyUTC(input: string | Date): Date { const date = new Date(input); return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); } - diff --git a/src/modules/shifts/services/shifts-command.service.ts b/src/modules/shifts/services/shifts-command.service.ts index a864ace..451c189 100644 --- a/src/modules/shifts/services/shifts-command.service.ts +++ b/src/modules/shifts/services/shifts-command.service.ts @@ -4,7 +4,6 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { PrismaService } from "src/prisma/prisma.service"; import { ShiftPayloadDto, UpsertShiftDto } from "../dtos/upsert-shift.dto"; import { timeFromHHMMUTC, toDateOnlyUTC, weekStartMondayUTC } from "../helpers/shifts-date-time-helpers"; -import { error, time } from "console"; type DayShiftResponse = { start_time: string; @@ -16,14 +15,10 @@ type DayShiftResponse = { type UpsertAction = 'created' | 'updated' | 'deleted'; - - - @Injectable() export class ShiftsCommandService extends BaseApprovalService { constructor(prisma: PrismaService) { super(prisma); } - //create/update/delete master method async upsertShfitsByDate(email:string, date_string: string, dto: UpsertShiftDto): Promise<{ action: UpsertAction; day: DayShiftResponse[] }> { @@ -230,7 +225,6 @@ async upsertShfitsByDate(email:string, date_string: string, dto: UpsertShiftDto) return result; } - private normalize_shift_payload(payload: ShiftPayloadDto) { //normalize shift's infos const start_time = timeFromHHMMUTC(payload.start_time); @@ -271,8 +265,6 @@ async upsertShfitsByDate(email:string, date_string: string, dto: UpsertShiftDto) return `${hh}:${mm}`; } - - //approval methods protected get delegate() { @@ -288,17 +280,4 @@ async upsertShfitsByDate(email:string, date_string: string, dto: UpsertShiftDto) this.updateApprovalWithTransaction(transaction, id, is_approved), ); } - - - - - - /* - old without new = delete - new without old = post - old with new = patch old with new - */ - async upsertShift(old_shift?: UpsertShiftDto, new_shift?: UpsertShiftDto) { - - } } \ No newline at end of file