From f81bb92beb6e6c8414b7eb66b273ac9ed52a6870 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:58:48 -0400 Subject: [PATCH] fix: increment iCalSequence when changing booking location (#22847) * fix: increment iCalSequence when changing booking location - Add iCalSequence parameter to buildCalEventFromBooking function - Update editLocation handler to increment sequence by 1 in CalendarEvent - Update database with incremented iCalSequence when location changes - Update buildCalEventFromBooking test to include new fields - Ensures ICS files properly update existing calendar events instead of creating duplicates Co-Authored-By: joe@cal.com * `BookingRepository.updateLocationById` to accept responses and iCalSequence * `editLocation.handler` use `BookingRepository` * Update iCalSequence * Use iCalSequence from booking param * Add iCalUID to evt object * Clean up * Fix test * Fix test --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../lib/__tests__/buildCalEventFromBooking.test.ts | 14 ++++++++++++++ packages/lib/buildCalEventFromBooking.ts | 4 ++++ packages/lib/server/repository/booking.ts | 6 +++++- .../viewer/bookings/editLocation.handler.ts | 13 ++++++------- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/lib/__tests__/buildCalEventFromBooking.test.ts b/packages/lib/__tests__/buildCalEventFromBooking.test.ts index a6dc6d0603..59d181df8f 100644 --- a/packages/lib/__tests__/buildCalEventFromBooking.test.ts +++ b/packages/lib/__tests__/buildCalEventFromBooking.test.ts @@ -52,6 +52,8 @@ const createBooking = (overrides = {}) => ({ }, destinationCalendar: null, user: null, + iCalSequence: 0, + iCalUID: "icaluid", ...overrides, }); @@ -121,6 +123,10 @@ describe("buildCalEventFromBooking", () => { destinationCalendar: [], seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot, seatsShowAttendees: true, + customReplyToEmail: undefined, + hideOrganizerEmail: undefined, + iCalSequence: 0, + iCalUID: booking.iCalUID, }); expect(parseRecurringEvent).toHaveBeenCalledWith(booking.eventType?.recurringEvent); @@ -135,6 +141,8 @@ describe("buildCalEventFromBooking", () => { userPrimaryEmail: null, attendees: [], eventType: null, + iCalUID: "icaluid", + iCalSequence: 0, }); const organizer = createOrganizer({ name: null, locale: null }); @@ -170,6 +178,10 @@ describe("buildCalEventFromBooking", () => { destinationCalendar: [], seatsPerTimeSlot: undefined, seatsShowAttendees: undefined, + customReplyToEmail: undefined, + hideOrganizerEmail: undefined, + iCalSequence: 0, + iCalUID: "icaluid", }); // @ts-expect-error - locale is set in mock @@ -192,6 +204,8 @@ describe("buildCalEventFromBooking", () => { credentialId: 1, }, }, + iCalUID: "icaluid", + iCalSequence: 0, }); const organizer = createOrganizer(); diff --git a/packages/lib/buildCalEventFromBooking.ts b/packages/lib/buildCalEventFromBooking.ts index 9e0cb474c6..ab49275c19 100644 --- a/packages/lib/buildCalEventFromBooking.ts +++ b/packages/lib/buildCalEventFromBooking.ts @@ -55,6 +55,8 @@ type Booking = { } | null; attendees: Attendee[]; eventType: EventType | null; + iCalUID: string | null; + iCalSequence: number; }; export const buildCalEventFromBooking = async ({ @@ -110,5 +112,7 @@ export const buildCalEventFromBooking = async ({ seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot, seatsShowAttendees: booking.eventType?.seatsShowAttendees, customReplyToEmail: booking.eventType?.customReplyToEmail, + iCalUID: booking.iCalUID ?? booking.uid, + iCalSequence: booking.iCalSequence ?? 0, }; }; diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index d70d5aeea3..f4e7a2495b 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -522,13 +522,15 @@ export class BookingRepository { async updateLocationById({ where: { id }, - data: { location, metadata, referencesToCreate }, + data: { location, metadata, referencesToCreate, responses, iCalSequence }, }: { where: { id: number }; data: { location: string; metadata: Record; referencesToCreate: Prisma.BookingReferenceCreateInput[]; + responses?: Record; + iCalSequence?: number; }; }) { await this.prismaClient.booking.update({ @@ -538,6 +540,8 @@ export class BookingRepository { data: { location, metadata, + ...(responses && { responses }), + ...(iCalSequence !== undefined && { iCalSequence }), references: { create: referencesToCreate, }, diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index e490952a19..31f37945f7 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -10,6 +10,7 @@ import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { BookingRepository } from "@calcom/lib/server/repository/booking"; import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import { UserRepository } from "@calcom/lib/server/repository/user"; import { prisma } from "@calcom/prisma"; @@ -97,19 +98,16 @@ async function updateBookingLocationInDb({ }; }); - await prisma.booking.update({ - where: { - id: booking.id, - }, + const bookingRepository = new BookingRepository(prisma); + await bookingRepository.updateLocationById({ + where: { id: booking.id }, data: { location: evt.location, metadata: { ...(typeof booking.metadata === "object" && booking.metadata), ...bookingMetadataUpdate, }, - references: { - create: referencesToCreate, - }, + referencesToCreate, responses: { ...(typeof booking.responses === "object" && booking.responses), location: { @@ -117,6 +115,7 @@ async function updateBookingLocationInDb({ optionValue: "", }, }, + iCalSequence: (evt.iCalSequence || 0) + 1, }, }); }