From 8ad62d183cf021a5dbbeb87a0b9ae7f8675c6e48 Mon Sep 17 00:00:00 2001 From: Somay Chauhan Date: Thu, 8 May 2025 22:53:09 +0530 Subject: [PATCH] feat: booking references api type filter (#21179) * feat: add `type` filter for booking references API endpointa * Update openapi.json * feat: add enum validation for booking reference types in filter input * refactor: rename externalUid to eventUid and add destinationCalendarId in booking references * refactor: replace enum with const array for BookingReferenceType * fix: type safety for booking references where clause using Prisma types * refactor: rename BookingReferenceType to BookingReferences --- .../booking-references.repository.ts | 15 +++-- .../controllers/bookings.controller.ts | 10 +++- .../inputs/booking-references-filter.input.ts | 23 ++++++++ .../outputs/booking-references.output.ts | 13 ++++- .../services/booking-references.service.ts | 13 +++-- .../output-booking-references.service.ts | 4 +- ...organizations-teams-bookings.controller.ts | 6 +- apps/api/v2/swagger/documentation.json | 56 ++++++++++++++++--- docs/api-reference/v2/openapi.json | 49 ++++++++++++++-- 9 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 apps/api/v2/src/ee/bookings/2024-08-13/inputs/booking-references-filter.input.ts diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/booking-references.repository.ts b/apps/api/v2/src/ee/bookings/2024-08-13/booking-references.repository.ts index 83a8d91c14..a99869c811 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/booking-references.repository.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/booking-references.repository.ts @@ -1,19 +1,26 @@ +import { BookingReferencesFilterInput_2024_08_13 } from "@/ee/bookings/2024-08-13/inputs/booking-references-filter.input"; import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { Injectable } from "@nestjs/common"; +import type { Prisma } from "@prisma/client"; @Injectable() export class BookingReferencesRepository_2024_08_13 { constructor(private readonly dbRead: PrismaReadService) {} - async getBookingReferences(bookingId: number) { + async getBookingReferences(bookingId: number, filter?: BookingReferencesFilterInput_2024_08_13) { + const whereClause: Prisma.BookingReferenceWhereInput = { bookingId }; + + if (filter?.type) { + whereClause.type = filter.type; + } + return this.dbRead.prisma.bookingReference.findMany({ - where: { - bookingId, - }, + where: whereClause, select: { type: true, uid: true, id: true, + externalCalendarId: true, }, }); } diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts index bc66f87098..c000b44c38 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts @@ -1,4 +1,5 @@ import { BookingUidGuard } from "@/ee/bookings/2024-08-13/guards/booking-uid.guard"; +import { BookingReferencesFilterInput_2024_08_13 } from "@/ee/bookings/2024-08-13/inputs/booking-references-filter.input"; import { BookingReferencesOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/booking-references.output"; import { CalendarLinksOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/calendar-links.output"; import { CancelBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/cancel-booking.output"; @@ -403,9 +404,14 @@ export class BookingsController_2024_08_13 { @HttpCode(HttpStatus.OK) async getBookingReferences( @Param("bookingUid") bookingUid: string, - @GetUser("id") userId: number + @GetUser("id") userId: number, + @Query() filter: BookingReferencesFilterInput_2024_08_13 ): Promise { - const bookingReferences = await this.bookingReferencesService.getBookingReferences(bookingUid, userId); + const bookingReferences = await this.bookingReferencesService.getBookingReferences( + bookingUid, + userId, + filter + ); return { status: SUCCESS_STATUS, diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/inputs/booking-references-filter.input.ts b/apps/api/v2/src/ee/bookings/2024-08-13/inputs/booking-references-filter.input.ts new file mode 100644 index 0000000000..49cecb0ea4 --- /dev/null +++ b/apps/api/v2/src/ee/bookings/2024-08-13/inputs/booking-references-filter.input.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsIn, IsOptional } from "class-validator"; + +export const BookingReferences = [ + "google_calendar", + "office365_calendar", + "daily_video", + "google_video", + "office365_video", + "zoom_video", +] as const; + +export class BookingReferencesFilterInput_2024_08_13 { + @ApiProperty({ + description: "Filter booking references by type", + required: false, + enum: BookingReferences, + example: "google_calendar", + }) + @IsOptional() + @IsIn(BookingReferences) + type?: (typeof BookingReferences)[number]; +} diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/outputs/booking-references.output.ts b/apps/api/v2/src/ee/bookings/2024-08-13/outputs/booking-references.output.ts index 1e82c42cf4..2d9384e575 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/outputs/booking-references.output.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/outputs/booking-references.output.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { IsEnum, IsNumber, IsString } from "class-validator"; import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; @@ -12,9 +12,16 @@ export class BookingReference { @IsString() @ApiProperty({ - description: "The external uid of the booking reference", + description: "The event uid of the booking", }) - externalUid!: string; + eventUid!: string; + + @IsString() + @ApiProperty({ + description: "The id of the calendar the event is created in", + nullable: true, + }) + destinationCalendarId!: string | null; @IsNumber() @ApiProperty({ diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-references.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-references.service.ts index 3f56f81268..c1440505a6 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-references.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-references.service.ts @@ -1,5 +1,6 @@ import { BookingReferencesRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/booking-references.repository"; import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings.repository"; +import { BookingReferencesFilterInput_2024_08_13 } from "@/ee/bookings/2024-08-13/inputs/booking-references-filter.input"; import { OutputBookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output-booking-references.service"; import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; @@ -11,7 +12,11 @@ export class BookingReferencesService_2024_08_13 { private readonly outputBookingReferencesService: OutputBookingReferencesService_2024_08_13 ) {} - async getBookingReferences(bookingUid: string, userId: number) { + async getBookingReferences( + bookingUid: string, + userId: number, + filter?: BookingReferencesFilterInput_2024_08_13 + ) { const booking = await this.bookingsRepository.getByUidWithUser(bookingUid); if (!booking) { @@ -22,19 +27,19 @@ export class BookingReferencesService_2024_08_13 { throw new BadRequestException(`Booking with uid ${bookingUid} does not belong to user`); } - const bookingReferences = await this.bookingReferencesRepository.getBookingReferences(booking.id); + const bookingReferences = await this.bookingReferencesRepository.getBookingReferences(booking.id, filter); return this.outputBookingReferencesService.getOutputBookingReferences(bookingReferences); } - async getOrgBookingReferences(bookingUid: string) { + async getOrgBookingReferences(bookingUid: string, filter?: BookingReferencesFilterInput_2024_08_13) { const booking = await this.bookingsRepository.getByUidWithUser(bookingUid); if (!booking) { throw new NotFoundException(`Booking with uid ${bookingUid} not found`); } - const bookingReferences = await this.bookingReferencesRepository.getBookingReferences(booking.id); + const bookingReferences = await this.bookingReferencesRepository.getBookingReferences(booking.id, filter); return this.outputBookingReferencesService.getOutputBookingReferences(bookingReferences); } diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/output-booking-references.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/output-booking-references.service.ts index c7c4861c26..a17ef022db 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/output-booking-references.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/output-booking-references.service.ts @@ -4,6 +4,7 @@ interface IBookingReference { type: string; uid: string; id: number; + externalCalendarId: string | null; } @Injectable() @@ -11,8 +12,9 @@ export class OutputBookingReferencesService_2024_08_13 { getOutputBookingReferences(bookingReferences: IBookingReference[]) { return bookingReferences.map((bookingReference) => ({ type: bookingReference.type, - externalUid: bookingReference.uid, + eventUid: bookingReference.uid, id: bookingReference.id, + destinationCalendarId: bookingReference?.externalCalendarId, })); } } diff --git a/apps/api/v2/src/modules/organizations/teams/bookings/organizations-teams-bookings.controller.ts b/apps/api/v2/src/modules/organizations/teams/bookings/organizations-teams-bookings.controller.ts index b9bd6ba134..c5083a18b2 100644 --- a/apps/api/v2/src/modules/organizations/teams/bookings/organizations-teams-bookings.controller.ts +++ b/apps/api/v2/src/modules/organizations/teams/bookings/organizations-teams-bookings.controller.ts @@ -1,4 +1,5 @@ import { BookingUidGuard } from "@/ee/bookings/2024-08-13/guards/booking-uid.guard"; +import { BookingReferencesFilterInput_2024_08_13 } from "@/ee/bookings/2024-08-13/inputs/booking-references-filter.input"; import { BookingReferencesOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/booking-references.output"; import { BookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/booking-references.service"; import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service"; @@ -82,9 +83,10 @@ export class OrganizationsTeamsBookingsController { }) @HttpCode(HttpStatus.OK) async getBookingReferences( - @Param("bookingUid") bookingUid: string + @Param("bookingUid") bookingUid: string, + @Query() filter: BookingReferencesFilterInput_2024_08_13 ): Promise { - const bookingReferences = await this.bookingReferencesService.getOrgBookingReferences(bookingUid); + const bookingReferences = await this.bookingReferencesService.getOrgBookingReferences(bookingUid, filter); return { status: SUCCESS_STATUS, diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 1d382cfc61..21f5c6bdc4 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -3321,6 +3321,24 @@ "schema": { "type": "string" } + }, + { + "name": "type", + "required": false, + "in": "query", + "description": "Filter booking references by type", + "example": "google_calendar", + "schema": { + "enum": [ + "google_calendar", + "office365_calendar", + "daily_video", + "google_video", + "office365_video", + "zoom_video" + ], + "type": "string" + } } ], "responses": { @@ -7529,6 +7547,24 @@ "type": "string" } }, + { + "name": "type", + "required": false, + "in": "query", + "description": "Filter booking references by type", + "example": "google_calendar", + "schema": { + "enum": [ + "google_calendar", + "office365_calendar", + "daily_video", + "google_video", + "office365_video", + "zoom_video" + ], + "type": "string" + } + }, { "name": "Authorization", "in": "header", @@ -15063,7 +15099,7 @@ "eightxeight-video", "discord-video", "demodesk-video", - "campfire-video", + "campfire-video" ], "example": "cal-video" }, @@ -21743,7 +21779,7 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "number" }, "formId": { "type": "string" @@ -22296,7 +22332,7 @@ "eightxeight-video", "discord-video", "demodesk-video", - "campfire-video", + "campfire-video" ] } }, @@ -24257,9 +24293,14 @@ "type": "string", "description": "The type of the booking reference" }, - "externalUid": { + "eventUid": { "type": "string", - "description": "The external uid of the booking reference" + "description": "The event uid of the booking" + }, + "destinationCalendarId": { + "type": "string", + "nullable": true, + "description": "The id of the calendar the event is created in" }, "id": { "type": "number", @@ -24268,7 +24309,8 @@ }, "required": [ "type", - "externalUid", + "eventUid", + "destinationCalendarId", "id" ] }, @@ -25168,4 +25210,4 @@ } } } -} +} \ No newline at end of file diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index b538d03944..a564c87098 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -3184,6 +3184,24 @@ "schema": { "type": "string" } + }, + { + "name": "type", + "required": false, + "in": "query", + "description": "Filter booking references by type", + "example": "google_calendar", + "schema": { + "enum": [ + "google_calendar", + "office365_calendar", + "daily_video", + "google_video", + "office365_video", + "zoom_video" + ], + "type": "string" + } } ], "responses": { @@ -7197,6 +7215,24 @@ "type": "string" } }, + { + "name": "type", + "required": false, + "in": "query", + "description": "Filter booking references by type", + "example": "google_calendar", + "schema": { + "enum": [ + "google_calendar", + "office365_calendar", + "daily_video", + "google_video", + "office365_video", + "zoom_video" + ], + "type": "string" + } + }, { "name": "Authorization", "in": "header", @@ -19940,7 +19976,7 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "number" }, "formId": { "type": "string" @@ -22237,16 +22273,21 @@ "type": "string", "description": "The type of the booking reference" }, - "externalUid": { + "eventUid": { "type": "string", - "description": "The external uid of the booking reference" + "description": "The event uid of the booking" + }, + "destinationCalendarId": { + "type": "string", + "nullable": true, + "description": "The id of the calendar the event is created in" }, "id": { "type": "number", "description": "The id of the booking reference" } }, - "required": ["type", "externalUid", "id"] + "required": ["type", "eventUid", "destinationCalendarId", "id"] }, "BookingReferencesOutput_2024_08_13": { "type": "object",