From e8a315ac4eb3a26d2eef379b55ae6178d4b70251 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Wed, 5 Nov 2025 18:31:22 +0530 Subject: [PATCH] Remove cal.cache query parameter to prevent stale calendar data (#24939) - Remove cal.cache query parameter parsing from booking and slots flows - Set shouldServeCache to false for all booking availability checks - Remove _shouldServeCache from schemas and type definitions - Clean up all references in platform atoms and API endpoints This ensures fresh Google Calendar data is always fetched for conflict checking, preventing bookings when there are actual calendar conflicts. --- .../2024-04-15/inputs/create-booking.input.ts | 5 -- .../bookings/lib/bookingCreateBodySchema.ts | 2 - .../booking-to-mutation-input-mapper.tsx | 3 - .../lib/service/RegularBookingService.ts | 43 ++++++----- .../use-schedule/useApiV2AvailableSlots.ts | 1 - .../schedules/lib/use-schedule/useSchedule.ts | 3 - .../useGetRoutingFormUrlProps.tsx | 3 - .../atoms/booker/BookerPlatformWrapper.tsx | 4 - .../platform/atoms/hooks/useAvailableSlots.ts | 1 - packages/platform/types/embed.ts | 1 - .../slots/slots-2024-04-15/inputs/index.ts | 6 -- .../trpc/server/routers/viewer/slots/types.ts | 1 - .../trpc/server/routers/viewer/slots/util.ts | 74 +++++++++---------- 13 files changed, 56 insertions(+), 91 deletions(-) diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts b/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts index da7cc4c641..7fdc39cbb7 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts @@ -194,11 +194,6 @@ export class CreateBookingInput_2024_04_15 { @ApiHideProperty() skipContactOwner?: boolean; - @IsBoolean() - @IsOptional() - @ApiHideProperty() - _shouldServeCache?: boolean; - @IsBoolean() @IsOptional() @ApiHideProperty() diff --git a/packages/features/bookings/lib/bookingCreateBodySchema.ts b/packages/features/bookings/lib/bookingCreateBodySchema.ts index 086354f57b..c30947f343 100644 --- a/packages/features/bookings/lib/bookingCreateBodySchema.ts +++ b/packages/features/bookings/lib/bookingCreateBodySchema.ts @@ -39,8 +39,6 @@ export const bookingCreateBodySchema = z.object({ * Used to identify if the booking is a dry run. */ _isDryRun: z.boolean().optional(), - /** Whether to override the cache */ - _shouldServeCache: z.boolean().optional(), tracking: z .object({ utm_source: z.string().optional(), diff --git a/packages/features/bookings/lib/client/booking-event-form/booking-to-mutation-input-mapper.tsx b/packages/features/bookings/lib/client/booking-event-form/booking-to-mutation-input-mapper.tsx index 42d3f44e9d..3ef9aeced3 100644 --- a/packages/features/bookings/lib/client/booking-event-form/booking-to-mutation-input-mapper.tsx +++ b/packages/features/bookings/lib/client/booking-event-form/booking-to-mutation-input-mapper.tsx @@ -64,8 +64,6 @@ export const mapBookingToMutationInput = ({ const skipContactOwner = searchParams.get("cal.skipContactOwner") === "true"; const reroutingFormResponses = searchParams.get("cal.reroutingFormResponses"); const _isDryRun = isDryRunProp !== undefined ? isDryRunProp : isBookingDryRun(searchParams); - const _cacheParam = searchParams?.get("cal.cache"); - const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined; const dub_id = searchParams?.get("dub_id"); return { @@ -98,7 +96,6 @@ export const mapBookingToMutationInput = ({ // In case of rerouting, the form responses are actually the responses that we need to update. reroutingFormResponses: reroutingFormResponses ? JSON.parse(reroutingFormResponses) : undefined, _isDryRun, - _shouldServeCache, dub_id, verificationCode, }; diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index e5d0057130..98ad810758 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -520,7 +520,6 @@ async function handler( reroutingFormResponses, routingFormResponseId, _isDryRun: isDryRun = false, - _shouldServeCache: shouldServeCache = false, ...reqBody } = bookingData; @@ -665,11 +664,11 @@ async function handler( } } - // const shouldServeCache = await cacheService.getShouldServeCache(_shouldServeCache, eventType.team?.id); - const isTeamEventType = !!eventType.schedulingType && ["COLLECTIVE", "ROUND_ROBIN"].includes(eventType.schedulingType); + const shouldServeCache = false; + loggerWithEventDetails.info( `Booking eventType ${eventTypeId} started`, safeStringify({ @@ -1244,9 +1243,9 @@ async function handler( // This ensures that createMeeting isn't called for static video apps as bookingLocation becomes just a regular value for them. const { bookingLocation, conferenceCredentialId } = organizerOrFirstDynamicGroupMemberDefaultLocationUrl ? { - bookingLocation: organizerOrFirstDynamicGroupMemberDefaultLocationUrl, - conferenceCredentialId: undefined, - } + bookingLocation: organizerOrFirstDynamicGroupMemberDefaultLocationUrl, + conferenceCredentialId: undefined, + } : getLocationValueForDB(locationBodyString, eventType.locations); log.info("locationBodyString", locationBodyString); @@ -1292,8 +1291,8 @@ async function handler( const destinationCalendar = eventType.destinationCalendar ? [eventType.destinationCalendar] : organizerUser.destinationCalendar - ? [organizerUser.destinationCalendar] - : null; + ? [organizerUser.destinationCalendar] + : null; let organizerEmail = organizerUser.email || "Email-less"; if (eventType.useEventTypeDestinationCalendarEmail && destinationCalendar?.[0]?.primaryEmail) { @@ -1912,14 +1911,14 @@ async function handler( } const updateManager = !skipCalendarSyncTaskCreation ? await eventManager.reschedule( - evt, - originalRescheduledBooking.uid, - undefined, - changedOrganizer, - previousHostDestinationCalendar, - isBookingRequestedReschedule, - skipDeleteEventsAndMeetings - ) + evt, + originalRescheduledBooking.uid, + undefined, + changedOrganizer, + previousHostDestinationCalendar, + isBookingRequestedReschedule, + skipDeleteEventsAndMeetings + ) : placeholderCreatedEvent; // This gets overridden when updating the event - to check if notes have been hidden or not. We just reset this back // to the default description when we are sending the emails. @@ -2217,8 +2216,8 @@ async function handler( const metadata = videoCallUrl ? { - videoCallUrl: getVideoCallUrlFromCalEvent(evt) || videoCallUrl, - } + videoCallUrl: getVideoCallUrlFromCalEvent(evt) || videoCallUrl, + } : undefined; const bookingFlowConfig = { @@ -2307,9 +2306,9 @@ async function handler( ...eventType, metadata: eventType.metadata ? { - ...eventType.metadata, - apps: eventType.metadata?.apps as Prisma.JsonValue, - } + ...eventType.metadata, + apps: eventType.metadata?.apps as Prisma.JsonValue, + } : {}, }, paymentAppCredentials: eventTypePaymentAppCredential as IEventTypePaymentCredentialType, @@ -2606,7 +2605,7 @@ async function handler( * We are open to renaming it to something more descriptive. */ export class RegularBookingService implements IBookingService { - constructor(private readonly deps: IBookingServiceDependencies) {} + constructor(private readonly deps: IBookingServiceDependencies) { } async createBooking(input: { bookingData: CreateRegularBookingData; bookingMeta?: CreateBookingMeta }) { return handler({ bookingData: input.bookingData, ...input.bookingMeta }, this.deps); diff --git a/packages/features/schedules/lib/use-schedule/useApiV2AvailableSlots.ts b/packages/features/schedules/lib/use-schedule/useApiV2AvailableSlots.ts index d4713b2556..50b7a3f8fe 100644 --- a/packages/features/schedules/lib/use-schedule/useApiV2AvailableSlots.ts +++ b/packages/features/schedules/lib/use-schedule/useApiV2AvailableSlots.ts @@ -26,7 +26,6 @@ export const useApiV2AvailableSlots = ({ rest.usernameList, rest.routedTeamMemberIds, rest.skipContactOwner, - rest._shouldServeCache, rest.teamMemberEmail, rest.embedConnectVersion ?? false, ], diff --git a/packages/features/schedules/lib/use-schedule/useSchedule.ts b/packages/features/schedules/lib/use-schedule/useSchedule.ts index f07790372d..85370abb2a 100644 --- a/packages/features/schedules/lib/use-schedule/useSchedule.ts +++ b/packages/features/schedules/lib/use-schedule/useSchedule.ts @@ -76,8 +76,6 @@ export const useSchedule = ({ ? getRoutedTeamMemberIdsFromSearchParams(new URLSearchParams(searchParams.toString())) : null; const skipContactOwner = searchParams ? searchParams.get("cal.skipContactOwner") === "true" : false; - const _cacheParam = searchParams?.get("cal.cache"); - const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined; const utils = trpc.useUtils(); const routingFormResponseIdParam = searchParams?.get("cal.routingFormResponseId"); const queuedFormResponseId = searchParams?.get("cal.queuedFormResponseId"); @@ -108,7 +106,6 @@ export const useSchedule = ({ teamMemberEmail, routedTeamMemberIds, skipContactOwner, - _shouldServeCache, ...(queuedFormResponseId ? { queuedFormResponseId } : { routingFormResponseId }), email, // Ensures that connectVersion causes a refresh of the data diff --git a/packages/platform/atoms/booker-embed/useGetRoutingFormUrlProps.tsx b/packages/platform/atoms/booker-embed/useGetRoutingFormUrlProps.tsx index 33a8a480e2..0447d8ce18 100644 --- a/packages/platform/atoms/booker-embed/useGetRoutingFormUrlProps.tsx +++ b/packages/platform/atoms/booker-embed/useGetRoutingFormUrlProps.tsx @@ -71,9 +71,6 @@ export const useGetRoutingFormUrlProps = ({ routingFormUrl }: { routingFormUrl?: ...(routingSearchParams.get("cal.isBookingDryRun") && { ["cal.isBookingDryRun"]: routingSearchParams.get("cal.isBookingDryRun") ?? undefined, }), - ...(routingSearchParams.get("cal.cache") && { - ["cal.cache"]: routingSearchParams.get("cal.cache") ?? undefined, - }), ...(routingSearchParams.get("cal.routingFormResponseId") && { ["cal.routingFormResponseId"]: routingSearchParams.get("cal.routingFormResponseId") ?? undefined, }), diff --git a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx index f3670b38f7..6c5cab384f 100644 --- a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx +++ b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx @@ -259,7 +259,6 @@ const BookerPlatformWrapperComponent = ( const [routingParams, setRoutingParams] = useState<{ routedTeamMemberIds?: number[]; - _shouldServeCache?: boolean; skipContactOwner?: boolean; isBookingDryRun?: boolean; }>({}); @@ -272,15 +271,12 @@ const BookerPlatformWrapperComponent = ( const routedTeamMemberIds = getRoutedTeamMemberIdsFromSearchParams(searchParams); const skipContactOwner = searchParams.get("cal.skipContactOwner") === "true"; - const _cacheParam = searchParams?.get("cal.cache"); - const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined; const isBookingDryRun = searchParams?.get("cal.isBookingDryRun")?.toLowerCase() === "true" || searchParams?.get("cal.sandbox")?.toLowerCase() === "true"; setRoutingParams({ ...(skipContactOwner ? { skipContactOwner } : {}), ...(routedTeamMemberIds ? { routedTeamMemberIds } : {}), - ...(_shouldServeCache ? { _shouldServeCache } : {}), ...(isBookingDryRun ? { isBookingDryRun } : {}), }); }, [routingFormSearchParams]); diff --git a/packages/platform/atoms/hooks/useAvailableSlots.ts b/packages/platform/atoms/hooks/useAvailableSlots.ts index c21248a73f..5cafafd640 100644 --- a/packages/platform/atoms/hooks/useAvailableSlots.ts +++ b/packages/platform/atoms/hooks/useAvailableSlots.ts @@ -28,7 +28,6 @@ export const useAvailableSlots = ({ rest.usernameList, rest.routedTeamMemberIds, rest.skipContactOwner, - rest._shouldServeCache, rest.teamMemberEmail, ], queryFn: () => { diff --git a/packages/platform/types/embed.ts b/packages/platform/types/embed.ts index 9769fd5b8e..3938eb82d3 100644 --- a/packages/platform/types/embed.ts +++ b/packages/platform/types/embed.ts @@ -13,7 +13,6 @@ export type RoutingFormSearchParams = { ["cal.reroutingFormResponses"]?: string; ["cal.skipContactOwner"]?: string; ["cal.isBookingDryRun"]?: string; - ["cal.cache"]?: string; ["cal.routingFormResponseId"]?: string; ["cal.crmAppSlug"]?: string; ["cal.crmOwnerRecordType"]?: string; diff --git a/packages/platform/types/slots/slots-2024-04-15/inputs/index.ts b/packages/platform/types/slots/slots-2024-04-15/inputs/index.ts index 4c16e05bf5..3fe600534a 100644 --- a/packages/platform/types/slots/slots-2024-04-15/inputs/index.ts +++ b/packages/platform/types/slots/slots-2024-04-15/inputs/index.ts @@ -176,12 +176,6 @@ export class GetAvailableSlotsInput_2024_04_15 { @ApiHideProperty() routingFormResponseId?: number; - @Transform(({ value }) => value && value.toLowerCase() === "true") - @IsBoolean() - @IsOptional() - @ApiHideProperty() - _shouldServeCache?: boolean; - @Transform(({ value }) => value && value.toLowerCase() === "true") @IsBoolean() @IsOptional() diff --git a/packages/trpc/server/routers/viewer/slots/types.ts b/packages/trpc/server/routers/viewer/slots/types.ts index 14235a5f55..60cc8ccb38 100644 --- a/packages/trpc/server/routers/viewer/slots/types.ts +++ b/packages/trpc/server/routers/viewer/slots/types.ts @@ -36,7 +36,6 @@ export const getScheduleSchemaObject = z.object({ _enableTroubleshooter: z.boolean().optional(), _bypassCalendarBusyTimes: z.boolean().optional(), _silentCalendarFailures: z.boolean().optional(), - _shouldServeCache: z.boolean().optional(), routingFormResponseId: z.number().optional(), queuedFormResponseId: z.string().nullish(), email: z.string().nullish(), diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 8d688bdc83..3520870508 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -149,7 +149,7 @@ function withSlotsCache( } export class AvailableSlotsService { - constructor(public readonly dependencies: IAvailableSlotsService) {} + constructor(public readonly dependencies: IAvailableSlotsService) { } private async _getReservedSlotsAndCleanupExpired({ bookerClientUid, @@ -200,8 +200,8 @@ export class AvailableSlotsService { usernameList: Array.isArray(input.usernameList) ? input.usernameList : input.usernameList - ? [input.usernameList] - : [], + ? [input.usernameList] + : [], }); const usersWithOldSelectedCalendars = usersForDynamicEventType.map((user) => withSelectedCalendars(user)); @@ -791,15 +791,15 @@ export class AvailableSlotsService { const bookingLimits = eventType?.bookingLimits && - typeof eventType?.bookingLimits === "object" && - Object.keys(eventType?.bookingLimits).length > 0 + typeof eventType?.bookingLimits === "object" && + Object.keys(eventType?.bookingLimits).length > 0 ? parseBookingLimit(eventType?.bookingLimits) : null; const durationLimits = eventType?.durationLimits && - typeof eventType?.durationLimits === "object" && - Object.keys(eventType?.durationLimits).length > 0 + typeof eventType?.durationLimits === "object" && + Object.keys(eventType?.durationLimits).length > 0 ? parseDurationLimit(eventType?.durationLimits) : null; @@ -962,15 +962,14 @@ export class AvailableSlotsService { _enableTroubleshooter: enableTroubleshooter = false, _bypassCalendarBusyTimes: bypassBusyCalendarTimes = false, _silentCalendarFailures: silentCalendarFailures = false, - _shouldServeCache, routingFormResponseId, queuedFormResponseId, } = input; const orgDetails = input?.orgSlug ? { - currentOrgDomain: input.orgSlug, - isValidOrgDomain: !!input.orgSlug && !RESERVED_SUBDOMAINS.includes(input.orgSlug), - } + currentOrgDomain: input.orgSlug, + isValidOrgDomain: !!input.orgSlug && !RESERVED_SUBDOMAINS.includes(input.orgSlug), + } : orgDomainConfig(ctx?.req); if (process.env.INTEGRATION_TEST_MODE === "true") { @@ -983,10 +982,7 @@ export class AvailableSlotsService { throw new TRPCError({ code: "NOT_FOUND" }); } - const shouldServeCache = await this.dependencies.cacheService.getShouldServeCache( - _shouldServeCache, - eventType.team?.id - ); + const shouldServeCache = false if (isEventTypeLoggingEnabled({ eventTypeId: eventType.id })) { logger.settings.minLevel = 2; } @@ -1189,10 +1185,10 @@ export class AvailableSlotsService { const travelSchedules = isDefaultSchedule && !eventType.useBookerTimezone ? restrictionSchedule.user.travelSchedules.map((schedule) => ({ - startDate: dayjs(schedule.startDate), - endDate: schedule.endDate ? dayjs(schedule.endDate) : undefined, - timeZone: schedule.timeZone, - })) + startDate: dayjs(schedule.startDate), + endDate: schedule.endDate ? dayjs(schedule.endDate) : undefined, + timeZone: schedule.timeZone, + })) : []; const { dateRanges: restrictionRanges } = buildDateRanges({ @@ -1287,9 +1283,9 @@ export class AvailableSlotsService { ( item: | { - time: dayjs.Dayjs; - userIds?: number[] | undefined; - } + time: dayjs.Dayjs; + userIds?: number[] | undefined; + } | undefined ): item is { time: dayjs.Dayjs; @@ -1458,23 +1454,23 @@ export class AvailableSlotsService { const troubleshooterData = enableTroubleshooter ? { - troubleshooter: { - routedTeamMemberIds: routedTeamMemberIds, - // One that Salesforce asked for - askedContactOwner: contactOwnerEmailFromInput, - // One that we used as per Routing skipContactOwner flag - consideredContactOwner: contactOwnerEmail, - // All hosts that have been checked for availability. If no routedTeamMemberIds are provided, this will be same as hosts. - routedHosts: usersWithCredentials.map((user) => { - return { - userId: user.id, - }; - }), - hostsAfterSegmentMatching: allHosts.map((host) => ({ - userId: host.user.id, - })), - }, - } + troubleshooter: { + routedTeamMemberIds: routedTeamMemberIds, + // One that Salesforce asked for + askedContactOwner: contactOwnerEmailFromInput, + // One that we used as per Routing skipContactOwner flag + consideredContactOwner: contactOwnerEmail, + // All hosts that have been checked for availability. If no routedTeamMemberIds are provided, this will be same as hosts. + routedHosts: usersWithCredentials.map((user) => { + return { + userId: user.id, + }; + }), + hostsAfterSegmentMatching: allHosts.map((host) => ({ + userId: host.user.id, + })), + }, + } : null; return {