From 10218d1c62803b9d484589e3aa12bd230e19920a Mon Sep 17 00:00:00 2001 From: Lazaruster Date: Tue, 20 Oct 2026 09:43:05 +0000 Subject: [PATCH] feat(bookings): add idempotency guard to booking creation (BE-28) - Add UseInterceptors to @nestjs/common imports in bookings controller - Remove duplicate CreateRecurringBookingDto import (modules/bookings path) - Apply @UseInterceptors(IdempotencyGuard) to POST /recurring endpoint - Fix IdempotencyKey unique index to include endpoint column The unique index was [key, userId] but the guard scopes reservations by [key, userId, endpoint]. Without endpoint in the index, the same key on two different endpoints would hit a spurious unique constraint violation. A migration is required to drop the old index and create the new one. --- backend/src/bookings/bookings.controller.ts | 3 ++- backend/src/common/entities/idempotency-key.entity.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/bookings/bookings.controller.ts b/backend/src/bookings/bookings.controller.ts index f4a35436..ee42efe9 100644 --- a/backend/src/bookings/bookings.controller.ts +++ b/backend/src/bookings/bookings.controller.ts @@ -8,6 +8,7 @@ import { Query, ParseUUIDPipe, UseGuards, + UseInterceptors, HttpCode, HttpStatus, Res, @@ -38,7 +39,6 @@ import { ApiErrorDto } from '../common/dto/api-error.dto'; import { Booking } from './entities/booking.entity'; import { CsvExportService } from '../common/csv-export/csv-export.service'; import { ExportBookingsProvider } from './providers/export-bookings.provider'; -import { CreateRecurringBookingDto } from '../modules/bookings/recurring-booking'; import { IdempotencyGuard } from '../common/guards/idempotency.guard'; @ApiTags('bookings') @@ -69,6 +69,7 @@ export class BookingsController { } @Post('recurring') + @UseInterceptors(IdempotencyGuard) @ApiOperation({ summary: 'Create a recurring booking series atomically' }) @ApiOkResponse({ description: 'Recurring series created', type: Booking, isArray: true }) async createRecurring( diff --git a/backend/src/common/entities/idempotency-key.entity.ts b/backend/src/common/entities/idempotency-key.entity.ts index a6fcf57d..04656072 100644 --- a/backend/src/common/entities/idempotency-key.entity.ts +++ b/backend/src/common/entities/idempotency-key.entity.ts @@ -7,7 +7,7 @@ import { } from 'typeorm'; @Entity('idempotency_keys') -@Index(['key', 'userId'], { unique: true }) +@Index(['key', 'userId', 'endpoint'], { unique: true }) @Index(['expiresAt']) export class IdempotencyKey { @PrimaryGeneratedColumn('uuid')