From f94e80558a521d839bc720556e8b1b497f89a97f Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Tue, 1 Apr 2025 15:52:24 +0100 Subject: [PATCH] feat: refactor handleCancelBooking to not have next request (#20241) * refactor handleCancelBooking to not have next request * fix platform v2 function calls * dont fall back to "" if no seat refrence * add missing type from merge * chore: bump platform libraries --------- Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com> Co-authored-by: Lauris Skraucis Co-authored-by: supalarry --- .../api/v1/pages/api/bookings/[id]/_delete.ts | 7 ++-- apps/api/v2/package.json | 2 +- .../controllers/bookings.controller.ts | 13 ++++-- .../2024-08-13/services/bookings.service.ts | 11 ++++- apps/web/app/api/cancel/route.ts | 4 +- .../bookings/lib/handleCancelBooking.ts | 35 +++++++++++----- .../test/handleCancelBooking.test.ts | 15 ++----- .../handleSeats/cancel/cancelAttendeeSeat.ts | 17 ++++---- .../lib/handleSeats/test/handleSeats.test.ts | 42 ++++++++----------- yarn.lock | 10 ++--- 10 files changed, 89 insertions(+), 67 deletions(-) diff --git a/apps/api/v1/pages/api/bookings/[id]/_delete.ts b/apps/api/v1/pages/api/bookings/[id]/_delete.ts index fd911ad976..476b1de577 100644 --- a/apps/api/v1/pages/api/bookings/[id]/_delete.ts +++ b/apps/api/v1/pages/api/bookings/[id]/_delete.ts @@ -79,9 +79,10 @@ async function handler(req: NextApiRequest) { allRemainingBookings: req.query.allRemainingBookings === "true", }); - // Normalizing for universal handler - req.body = { id, allRemainingBookings, cancellationReason, cancelledBy }; - return await handleCancelBooking(req); + return await handleCancelBooking({ + bookingData: { id, allRemainingBookings, cancellationReason, cancelledBy }, + userId: req.userId, + }); } export default defaultResponder(handler); diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index d9242e6737..b656de72d7 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -38,7 +38,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.160", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.164", "@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts index 8aaede75fc..226cf89ed3 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts @@ -214,9 +214,16 @@ export class BookingsController_2024_04_15 { if (bookingUid) { try { req.body.uid = bookingUid; - const res = await handleCancelBooking( - await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed) - ); + const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed); + const res = await handleCancelBooking({ + bookingData: bookingRequest.body, + userId: bookingRequest.userId, + arePlatformEmailsEnabled: bookingRequest.arePlatformEmailsEnabled, + platformClientId: bookingRequest.platformClientId, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + }); if (!res.onlyRemovedAttendee) { void (await this.billingService.cancelUsageByBookingUid(res.bookingUid)); } diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts index 75a98e7a66..132c7afd5b 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts @@ -344,7 +344,16 @@ export class BookingsService_2024_08_13 { } const bookingRequest = await this.inputService.createCancelBookingRequest(request, bookingUid, body); - const res = await handleCancelBooking(bookingRequest); + const res = await handleCancelBooking({ + bookingData: bookingRequest.body, + userId: bookingRequest.userId, + arePlatformEmailsEnabled: bookingRequest.arePlatformEmailsEnabled, + platformClientId: bookingRequest.platformClientId, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + }); + if (!res.onlyRemovedAttendee) { await this.billingService.cancelUsageByBookingUid(res.bookingUid); } diff --git a/apps/web/app/api/cancel/route.ts b/apps/web/app/api/cancel/route.ts index 32bd1e8096..5ed42c280c 100644 --- a/apps/web/app/api/cancel/route.ts +++ b/apps/web/app/api/cancel/route.ts @@ -5,6 +5,7 @@ import type { NextRequest } from "next/server"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking"; +import { bookingCancelInput } from "@calcom/prisma/zod-utils"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; @@ -16,8 +17,9 @@ async function handler(req: NextRequest) { return NextResponse.json({ success: false, message: "Invalid JSON" }, { status: 400 }); } const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); + const bookingData = bookingCancelInput.parse(appDirRequestBody); const result = await handleCancelBooking({ - appDirRequestBody, + bookingData, userId: session?.user?.id || -1, }); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index ac5c6d1230..1ddcddfeb1 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -1,5 +1,5 @@ import type { Prisma, WorkflowReminder } from "@prisma/client"; -import type { NextApiRequest } from "next"; +import type { z } from "zod"; import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter"; import { DailyLocationType } from "@calcom/app-store/locations"; @@ -34,12 +34,26 @@ import type { CalendarEvent } from "@calcom/types/Calendar"; import { getAllCredentials } from "./getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingToDelete } from "./getBookingToDelete"; -import type { CustomRequest, AppRouterRequest } from "./handleCancelBooking/types"; import { handleInternalNote } from "./handleInternalNote"; import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat"; const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] }); +type PlatformParams = { + platformClientId?: string; + platformRescheduleUrl?: string; + platformCancelUrl?: string; + platformBookingUrl?: string; + arePlatformEmailsEnabled?: boolean; +}; + +export type BookingToDelete = Awaited>; + +export type CancelBookingInput = { + userId?: number; + bookingData: z.infer; +} & PlatformParams; + export type HandleCancelBookingResponse = { success: boolean; message: string; @@ -48,8 +62,8 @@ export type HandleCancelBookingResponse = { bookingUid: string; }; -async function handler(req: CustomRequest) { - const body = (req as AppRouterRequest).appDirRequestBody ?? (req as NextApiRequest).body; +async function handler(input: CancelBookingInput) { + const body = input.bookingData; const { id, uid, @@ -60,22 +74,21 @@ async function handler(req: CustomRequest) { cancelSubsequentBookings, internalNote, } = bookingCancelInput.parse(body); - req.bookingToDelete = await getBookingToDelete(id, uid); + const bookingToDelete = await getBookingToDelete(id, uid); const { - bookingToDelete, userId, platformBookingUrl, platformCancelUrl, platformClientId, platformRescheduleUrl, arePlatformEmailsEnabled, - } = req; + } = input; if (!bookingToDelete.userId || !bookingToDelete.user) { throw new HttpError({ statusCode: 400, message: "User not found" }); } - if (!platformClientId && !cancellationReason && req.bookingToDelete.userId == userId) { + if (!platformClientId && !cancellationReason && bookingToDelete.userId == userId) { throw new HttpError({ statusCode: 400, message: "Cancellation reason is required when you are the host", @@ -248,7 +261,10 @@ async function handler(req: CustomRequest) { // If it's just an attendee of a booking then just remove them from that booking const result = await cancelAttendeeSeat( - req, + { + seatReferenceUid: seatReferenceUid, + bookingToDelete, + }, dataForWebhooks, bookingToDelete?.eventType?.metadata as EventTypeMetadata ); @@ -477,7 +493,6 @@ async function handler(req: CustomRequest) { } catch (error) { console.error("Error deleting event", error); } - (req as NextApiRequest).statusCode = 200; return { success: true, message: "Booking successfully cancelled.", diff --git a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts index 9905803430..bf304fcb98 100644 --- a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts +++ b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts @@ -10,7 +10,6 @@ import { TestData, getDate, } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; -import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest"; import { expectBookingCancelledWebhookToHaveBeenFired } from "@calcom/web/test/utils/bookingScenario/expects"; import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; @@ -110,17 +109,14 @@ describe("Cancel Booking", () => { }, }); - const { req } = createMockNextJsRequest({ - method: "POST", - body: { + await handleCancelBooking({ + bookingData: { id: idOfBookingToBeCancelled, uid: uidOfBookingToBeCancelled, cancelledBy: organizer.email, }, }); - await handleCancelBooking(req); - expectBookingCancelledWebhookToHaveBeenFired({ booker, organizer, @@ -240,17 +236,14 @@ describe("Cancel Booking", () => { }, }); - const { req } = createMockNextJsRequest({ - method: "POST", - body: { + await handleCancelBooking({ + bookingData: { id: idOfBookingToBeCancelled, uid: uidOfBookingToBeCancelled, cancelledBy: organizer.email, }, }); - await handleCancelBooking(req); - expectBookingCancelledWebhookToHaveBeenFired({ booker, organizer, diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index d769c39a47..a5d391089a 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -1,5 +1,3 @@ -import type { NextApiRequest } from "next"; - import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; @@ -18,10 +16,13 @@ import { bookingCancelAttendeeSeatSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import type { AppRouterRequest, CustomRequest } from "../../handleCancelBooking/types"; +import type { BookingToDelete } from "../../handleCancelBooking"; async function cancelAttendeeSeat( - req: CustomRequest, + data: { + seatReferenceUid?: string; + bookingToDelete: BookingToDelete; + }, dataForWebhooks: { webhooks: { id: string; @@ -35,12 +36,13 @@ async function cancelAttendeeSeat( }, eventTypeMetadata: EventTypeMetadata ) { - const body = (req as AppRouterRequest).appDirRequestBody ?? (req as NextApiRequest).body; - const input = bookingCancelAttendeeSeatSchema.safeParse(body); + const input = bookingCancelAttendeeSeatSchema.safeParse({ + seatReferenceUid: data.seatReferenceUid, + }); const { webhooks, evt, eventTypeInfo } = dataForWebhooks; if (!input.success) return; const { seatReferenceUid } = input.data; - const bookingToDelete = req.bookingToDelete; + const bookingToDelete = data.bookingToDelete; if (!bookingToDelete?.attendees.length || bookingToDelete.attendees.length < 2) return; if (!bookingToDelete.userId) { @@ -65,7 +67,6 @@ async function cancelAttendeeSeat( }, }), ]); - (req as NextApiRequest).statusCode = 200; const attendee = bookingToDelete?.attendees.find((attendee) => attendee.id === seatReference.attendeeId); const bookingToDeleteUser = bookingToDelete.user ?? null; diff --git a/packages/features/bookings/lib/handleSeats/test/handleSeats.test.ts b/packages/features/bookings/lib/handleSeats/test/handleSeats.test.ts index 83186d2386..17cabef47d 100644 --- a/packages/features/bookings/lib/handleSeats/test/handleSeats.test.ts +++ b/packages/features/bookings/lib/handleSeats/test/handleSeats.test.ts @@ -1671,16 +1671,14 @@ describe("handleSeats", () => { seatReferenceUid: bookingSeatToBeCancelledUid, }); - const { req } = createMockNextJsRequest({ - method: "POST", - body: mockCancelBookingData, + await handleCancelBooking({ + bookingData: { + ...mockCancelBookingData, + cancellationReason: "test cancellation reason", + }, + userId: organizer.id, }); - req.userId = organizer.id; - req.body.cancellationReason = "test cancellation reason"; - - await handleCancelBooking(req); - // Ensure that the booking has been cancelled const cancelledBooking = await prismaMock.booking.findFirst({ where: { @@ -1812,16 +1810,14 @@ describe("handleSeats", () => { seatReferenceUid: bookingSeatToBeCancelledUid, }); - const { req } = createMockNextJsRequest({ - method: "POST", - body: mockCancelBookingData, + await handleCancelBooking({ + bookingData: { + ...mockCancelBookingData, + cancellationReason: "test cancellation reason", + }, + userId: organizer.id, }); - req.userId = organizer.id; - req.body.cancellationReason = "test cancellation reason"; - - await handleCancelBooking(req); - // Ensure that the booking has been cancelled const cancelledBooking = await prismaMock.booking.findFirst({ where: { @@ -2678,16 +2674,14 @@ describe("handleSeats", () => { }, }); - const { req } = createMockNextJsRequest({ - method: "POST", - body: mockBookingData, + await handleCancelBooking({ + bookingData: { + ...mockBookingData, + cancellationReason: "test cancellation reason", + }, + userId: organizer.id, }); - req.userId = organizer.id; - req.body.cancellationReason = "test cancellation reason"; - - await handleCancelBooking(req); - // Ensure that the booking has been cancelled const cancelledBooking = await prismaMock.booking.findFirst({ where: { diff --git a/yarn.lock b/yarn.lock index 5f06e7be4f..b6d435a99e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2297,7 +2297,7 @@ __metadata: "@axiomhq/winston": ^1.2.0 "@calcom/platform-constants": "*" "@calcom/platform-enums": "*" - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.160" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.164" "@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" @@ -3335,13 +3335,13 @@ __metadata: languageName: node linkType: hard -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.160": - version: 0.0.160 - resolution: "@calcom/platform-libraries@npm:0.0.160" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.164": + version: 0.0.164 + resolution: "@calcom/platform-libraries@npm:0.0.164" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 483d57040ddcaa93d9a77aeb5af2778cf341986e387955363cb17266d46ce297d7a8a3184056dbda3dba539382ddd8d6f0b9086ed52b3f1c96f5c997a3216d2e + checksum: f612a3e2002950ed00d3479a121b92ee3245f9cb00b663824cac45bb5ab8f74182271377cd8866563a5d11983bc8af08072bfdb58bf459e1c7cd2ec025a50466 languageName: node linkType: hard