From d134d110f330378c9d8678ee6a0f01f32630dcd9 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Wed, 10 Sep 2025 17:31:20 +0530 Subject: [PATCH 1/2] Integrate booking services --- apps/api/v1/pages/api/bookings/_post.ts | 14 +- .../ee/bookings/2024-04-15/bookings.module.ts | 6 + .../bookings.controller.e2e-spec.ts | 16 +- .../controllers/bookings.controller.ts | 61 ++-- .../ee/bookings/2024-08-13/bookings.module.ts | 6 + .../2024-08-13/services/bookings.service.ts | 117 ++++--- .../src/lib/modules/instant-booking.module.ts | 8 + .../lib/modules/recurring-booking.module.ts | 10 + .../src/lib/modules/regular-booking.module.ts | 32 ++ .../instant-booking-create.service.ts | 6 + .../lib/services/recurring-booking.service.ts | 13 + .../lib/services/regular-booking.service.ts | 46 +++ apps/web/lib/types/booking.ts | 6 +- apps/web/pages/api/book/event.ts | 13 +- apps/web/pages/api/book/instant-event.ts | 11 +- apps/web/pages/api/book/recurring-event.ts | 21 +- .../InstantBookingCreateService.container.ts | 13 + .../di/InstantBookingCreateService.module.ts | 20 ++ .../di/RecurringBookingService.container.ts | 14 + .../di/RecurringBookingService.module.ts | 25 ++ .../di}/RegularBookingService.container.ts | 7 +- .../di}/RegularBookingService.module.ts | 15 +- .../{di/bookings => bookings/di}/tokens.ts | 0 .../bookings/lib/bookingCreateBodySchema.ts | 66 ++-- .../bookings/lib/create-instant-booking.ts | 10 +- packages/features/bookings/lib/dto/types.d.ts | 26 +- .../features/bookings/lib/handleNewBooking.ts | 125 +++---- .../test/email-verification-booking.test.ts | 12 +- .../test/getNewBookingHandler.ts | 15 +- .../test/handleNewRecurringBooking.test.ts | 315 +----------------- .../bookings/lib/handleNewRecurringBooking.ts | 35 +- packages/features/bookings/lib/index.ts | 1 + packages/features/bookings/types.ts | 12 +- .../InstantBookingCreateService.container.ts | 14 - .../RecurringBookingService.container.ts | 29 -- .../InstantBookingCreateService.module.ts | 11 - .../modules/RecurringBookingService.module.ts | 12 - packages/features/di/tokens.ts | 2 +- .../handleInstantMeeting.test.ts | 17 +- .../instant-meeting/handleInstantMeeting.ts | 20 +- packages/platform/libraries/bookings.ts | 7 + packages/platform/libraries/index.ts | 9 - 42 files changed, 566 insertions(+), 652 deletions(-) create mode 100644 apps/api/v2/src/lib/modules/instant-booking.module.ts create mode 100644 apps/api/v2/src/lib/modules/recurring-booking.module.ts create mode 100644 apps/api/v2/src/lib/modules/regular-booking.module.ts create mode 100644 apps/api/v2/src/lib/services/instant-booking-create.service.ts create mode 100644 apps/api/v2/src/lib/services/recurring-booking.service.ts create mode 100644 apps/api/v2/src/lib/services/regular-booking.service.ts create mode 100644 packages/features/bookings/di/InstantBookingCreateService.container.ts create mode 100644 packages/features/bookings/di/InstantBookingCreateService.module.ts create mode 100644 packages/features/bookings/di/RecurringBookingService.container.ts create mode 100644 packages/features/bookings/di/RecurringBookingService.module.ts rename packages/features/{di/bookings/containers => bookings/di}/RegularBookingService.container.ts (70%) rename packages/features/{di/bookings/modules => bookings/di}/RegularBookingService.module.ts (63%) rename packages/features/{di/bookings => bookings/di}/tokens.ts (100%) delete mode 100644 packages/features/di/bookings/containers/InstantBookingCreateService.container.ts delete mode 100644 packages/features/di/bookings/containers/RecurringBookingService.container.ts delete mode 100644 packages/features/di/bookings/modules/InstantBookingCreateService.module.ts delete mode 100644 packages/features/di/bookings/modules/RecurringBookingService.module.ts diff --git a/apps/api/v1/pages/api/bookings/_post.ts b/apps/api/v1/pages/api/bookings/_post.ts index 12caca5758aa58..3b1c1c57f23a66 100644 --- a/apps/api/v1/pages/api/bookings/_post.ts +++ b/apps/api/v1/pages/api/bookings/_post.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; +import { getRegularBookingService } from "@calcom/features/bookings/di/RegularBookingService.container"; import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi"; -import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; @@ -239,15 +239,17 @@ async function handler(req: NextApiRequest) { } try { - return await handleNewBooking( - { - bookingData: req.body, + const regularBookingService = getRegularBookingService(); + + return await regularBookingService.createBookingForApiV1({ + bookingData: req.body, + bookingMeta: { userId, hostname: req.headers.host || "", forcedSlug: req.headers["x-cal-force-slug"] as string | undefined, }, - getBookingDataSchemaForApi - ); + bookingDataSchemaGetter: getBookingDataSchemaForApi, + }); } catch (error: unknown) { const knownError = error as Error; if (knownError?.message === ErrorCode.NoAvailableUsersFound) { diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts index d645d46aae8a4e..bfd8d69e4e3048 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts @@ -6,6 +6,9 @@ import { CalendarsService } from "@/ee/calendars/services/calendars.service"; import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module"; import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module"; +import { InstantBookingModule } from "@/lib/modules/instant-booking.module"; +import { RecurringBookingModule } from "@/lib/modules/recurring-booking.module"; +import { RegularBookingModule } from "@/lib/modules/regular-booking.module"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { AppsRepository } from "@/modules/apps/apps.repository"; import { BillingModule } from "@/modules/billing/billing.module"; @@ -35,6 +38,9 @@ import { Module } from "@nestjs/common"; SchedulesModule_2024_04_15, EventTypesModule_2024_06_14, ProfilesModule, + RegularBookingModule, + RecurringBookingModule, + InstantBookingModule, ], providers: [ TokensRepository, diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.e2e-spec.ts index ceea40c9087af9..fe798022c2d632 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.e2e-spec.ts @@ -21,7 +21,7 @@ import { randomString } from "test/utils/randomString"; import { withApiAuth } from "test/utils/withApiAuth"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; -import { handleNewBooking } from "@calcom/platform-libraries"; +import { type RegularBookingCreateResult } from "@calcom/platform-libraries/bookings"; import type { ApiSuccessResponse, ApiErrorResponse } from "@calcom/platform-types"; import type { User } from "@calcom/prisma/client"; @@ -41,7 +41,7 @@ describe("Bookings Endpoints 2024-04-15", () => { let eventTypeId: number; - let createdBooking: Awaited>; + let createdBooking: RegularBookingCreateResult; beforeAll(async () => { const moduleRef = await withApiAuth( @@ -131,8 +131,7 @@ describe("Bookings Endpoints 2024-04-15", () => { .send(body) .expect(201) .then(async (response) => { - const responseBody: ApiSuccessResponse>> = - response.body; + const responseBody: ApiSuccessResponse = response.body; expect(responseBody.status).toEqual(SUCCESS_STATUS); expect(responseBody.data).toBeDefined(); expect(responseBody.data.userPrimaryEmail).toBeDefined(); @@ -231,8 +230,7 @@ describe("Bookings Endpoints 2024-04-15", () => { .set({ Authorization: `Bearer cal_test_${apiKeyString}` }) .expect(201) .then(async (response) => { - const responseBody: ApiSuccessResponse>> = - response.body; + const responseBody: ApiSuccessResponse = response.body; expect(responseBody.status).toEqual(SUCCESS_STATUS); expect(responseBody.data).toBeDefined(); expect(responseBody.data.userPrimaryEmail).toBeDefined(); @@ -289,8 +287,7 @@ describe("Bookings Endpoints 2024-04-15", () => { .send(body) .expect(201) .then(async (response) => { - const responseBody: ApiSuccessResponse>> = - response.body; + const responseBody: ApiSuccessResponse = response.body; expect(responseBody.status).toEqual(SUCCESS_STATUS); expect(responseBody.data).toBeDefined(); expect(responseBody.data.userPrimaryEmail).toBeDefined(); @@ -387,8 +384,7 @@ describe("Bookings Endpoints 2024-04-15", () => { .send(body) .expect(201) .then(async (response) => { - const responseBody: ApiSuccessResponse>> = - response.body; + const responseBody: ApiSuccessResponse = response.body; expect(responseBody.status).toEqual(SUCCESS_STATUS); expect(responseBody.data).toBeDefined(); expect(responseBody.data.userPrimaryEmail).toBeDefined(); 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 9b2c06301a9f7e..f02fdff352faa7 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 @@ -7,6 +7,9 @@ import { MarkNoShowOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/ma import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { sha256Hash, isApiKey, stripApiKey } from "@/lib/api-key"; import { VERSION_2024_04_15, VERSION_2024_06_11, VERSION_2024_06_14 } from "@/lib/api-versions"; +import { InstantBookingCreateService } from "@/lib/services/instant-booking-create.service"; +import { RecurringBookingService } from "@/lib/services/recurring-booking.service"; +import { RegularBookingService } from "@/lib/services/regular-booking.service"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator"; @@ -45,11 +48,8 @@ import { v4 as uuidv4 } from "uuid"; import { X_CAL_CLIENT_ID, X_CAL_PLATFORM_EMBED } from "@calcom/platform-constants"; import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants"; import { - handleNewRecurringBooking, - handleNewBooking, BookingResponse, HttpError, - handleInstantMeeting, handleMarkNoShow, getAllUserBookings, getBookingInfo, @@ -58,6 +58,7 @@ import { ErrorCode, } from "@calcom/platform-libraries"; import { CreationSource } from "@calcom/platform-libraries"; +import { type InstantBookingCreateResult } from "@calcom/platform-libraries/bookings"; import { GetBookingsInput_2024_04_15, CancelBookingInput_2024_04_15, @@ -109,7 +110,10 @@ export class BookingsController_2024_04_15 { private readonly apiKeyRepository: ApiKeysRepository, private readonly platformBookingsService: PlatformBookingsService, private readonly usersRepository: UsersRepository, - private readonly usersService: UsersService + private readonly usersService: UsersService, + private readonly regularBookingService: RegularBookingService, + private readonly recurringBookingService: RecurringBookingService, + private readonly instantBookingCreateService: InstantBookingCreateService ) {} @Get("/") @@ -187,17 +191,19 @@ export class BookingsController_2024_04_15 { const { orgSlug, locationUrl } = body; try { const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed); - const booking = await handleNewBooking({ + const booking = await this.regularBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - forcedSlug: orgSlug, - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + forcedSlug: orgSlug, + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); if (booking.userId && booking.uid && booking.startTime) { void (await this.billingService.increaseUsageByUserId(booking.userId, { @@ -315,15 +321,17 @@ export class BookingsController_2024_04_15 { const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed); - const createdBookings: BookingResponse[] = await handleNewRecurringBooking({ + const createdBookings: BookingResponse[] = await this.recurringBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + }, }); createdBookings.forEach(async (booking) => { @@ -351,14 +359,15 @@ export class BookingsController_2024_04_15 { @Body() body: CreateBookingInput_2024_04_15, @Headers(X_CAL_CLIENT_ID) clientId?: string, @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string - ): Promise>>> { + ): Promise> { const oAuthClientId = clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId)); req.userId = (await this.getOwnerId(req)) ?? -1; try { - const instantMeeting = await handleInstantMeeting( - await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed) - ); + const bookingReq = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed); + const instantMeeting = await this.instantBookingCreateService.createBooking({ + bookingData: bookingReq.body, + }); if (instantMeeting.userId && instantMeeting.bookingUid) { const now = new Date(); diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts index d0d37f9e979247..3359fa8d0caa8d 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts @@ -16,6 +16,9 @@ import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_0 import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module"; +import { InstantBookingModule } from "@/lib/modules/instant-booking.module"; +import { RecurringBookingModule } from "@/lib/modules/recurring-booking.module"; +import { RegularBookingModule } from "@/lib/modules/regular-booking.module"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { AppsRepository } from "@/modules/apps/apps.repository"; import { BillingModule } from "@/modules/billing/billing.module"; @@ -58,6 +61,9 @@ import { Module } from "@nestjs/common"; TeamsEventTypesModule, MembershipsModule, ProfilesModule, + RegularBookingModule, + RecurringBookingModule, + InstantBookingModule, ], providers: [ TokensRepository, 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 b3a91219243983..b9d94bc8f0cb4d 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 @@ -6,6 +6,9 @@ import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/servi import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; import { getPagination } from "@/lib/pagination/pagination"; +import { InstantBookingCreateService } from "@/lib/services/instant-booking-create.service"; +import { RecurringBookingService } from "@/lib/services/recurring-booking.service"; +import { RegularBookingService } from "@/lib/services/regular-booking.service"; import { AuthOptionalUser } from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator"; import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy"; import { BillingService } from "@/modules/billing/services/billing.service"; @@ -37,10 +40,8 @@ import { DateTime } from "luxon"; import { z } from "zod"; import { - handleNewRecurringBooking, getTranslation, getAllUserBookings, - handleInstantMeeting, handleCancelBooking, roundRobinReassignment, roundRobinManualReassignment, @@ -48,7 +49,6 @@ import { confirmBookingHandler, getCalendarLinks, } from "@calcom/platform-libraries"; -import { handleNewBooking } from "@calcom/platform-libraries"; import { CreateBookingInput_2024_08_13, CreateBookingInput, @@ -109,7 +109,10 @@ export class BookingsService_2024_08_13 { private readonly teamsEventTypesRepository: TeamsEventTypesRepository, private readonly membershipsRepository: MembershipsRepository, private readonly membershipsService: MembershipsService, - private readonly errorsBookingsService: ErrorsBookingsService_2024_08_13 + private readonly errorsBookingsService: ErrorsBookingsService_2024_08_13, + private readonly regularBookingService: RegularBookingService, + private readonly recurringBookingService: RecurringBookingService, + private readonly instantBookingCreateService: InstantBookingCreateService ) {} async createBooking(request: Request, body: CreateBookingInput, authUser: AuthOptionalUser) { @@ -464,7 +467,9 @@ export class BookingsService_2024_08_13 { } const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType); - const booking = await handleInstantMeeting(bookingRequest); + const booking = await this.instantBookingCreateService.createBooking({ + bookingData: bookingRequest.body, + }); const databaseBooking = await this.bookingsRepository.getByIdWithAttendeesAndUserAndEvent( booking.bookingId @@ -482,17 +487,19 @@ export class BookingsService_2024_08_13 { eventType: EventTypeWithOwnerAndTeam ) { const bookingRequest = await this.inputService.createRecurringBookingRequest(request, body, eventType); - const bookings = await handleNewRecurringBooking({ + const bookings = await this.recurringBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - noEmail: bookingRequest.noEmail, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + noEmail: bookingRequest.noEmail, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); const ids = bookings.map((booking) => booking.id || 0); return this.outputService.getOutputRecurringBookings(ids); @@ -505,16 +512,18 @@ export class BookingsService_2024_08_13 { userIsEventTypeAdminOrOwner: boolean ) { const bookingRequest = await this.inputService.createRecurringBookingRequest(request, body, eventType); - const bookings = await handleNewRecurringBooking({ + const bookings = await this.recurringBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); return this.outputService.getOutputCreateRecurringSeatedBookings( bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" })), @@ -528,16 +537,18 @@ export class BookingsService_2024_08_13 { eventType: EventTypeWithOwnerAndTeam ) { const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType); - const booking = await handleNewBooking({ + const booking = await this.regularBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); if (!booking.uid) { @@ -560,16 +571,18 @@ export class BookingsService_2024_08_13 { ) { const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType); try { - const booking = await handleNewBooking({ + const booking = await this.regularBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); if (!booking.uid) { @@ -755,16 +768,18 @@ export class BookingsService_2024_08_13 { bookingUid, body ); - const booking = await handleNewBooking({ + const booking = await this.regularBookingService.createBooking({ bookingData: bookingRequest.body, - userId: bookingRequest.userId, - hostname: bookingRequest.headers?.host || "", - platformClientId: bookingRequest.platformClientId, - platformRescheduleUrl: bookingRequest.platformRescheduleUrl, - platformCancelUrl: bookingRequest.platformCancelUrl, - platformBookingUrl: bookingRequest.platformBookingUrl, - platformBookingLocation: bookingRequest.platformBookingLocation, - areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + bookingMeta: { + userId: bookingRequest.userId, + hostname: bookingRequest.headers?.host || "", + platformClientId: bookingRequest.platformClientId, + platformRescheduleUrl: bookingRequest.platformRescheduleUrl, + platformCancelUrl: bookingRequest.platformCancelUrl, + platformBookingUrl: bookingRequest.platformBookingUrl, + platformBookingLocation: bookingRequest.platformBookingLocation, + areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled, + }, }); if (!booking.uid) { throw new Error("Booking missing uid"); diff --git a/apps/api/v2/src/lib/modules/instant-booking.module.ts b/apps/api/v2/src/lib/modules/instant-booking.module.ts new file mode 100644 index 00000000000000..e7b3e652448dff --- /dev/null +++ b/apps/api/v2/src/lib/modules/instant-booking.module.ts @@ -0,0 +1,8 @@ +import { InstantBookingCreateService } from "@/lib/services/instant-booking-create.service"; +import { Module } from "@nestjs/common"; + +@Module({ + providers: [InstantBookingCreateService], + exports: [InstantBookingCreateService], +}) +export class InstantBookingModule {} diff --git a/apps/api/v2/src/lib/modules/recurring-booking.module.ts b/apps/api/v2/src/lib/modules/recurring-booking.module.ts new file mode 100644 index 00000000000000..bcb5d43f821e6d --- /dev/null +++ b/apps/api/v2/src/lib/modules/recurring-booking.module.ts @@ -0,0 +1,10 @@ +import { RegularBookingModule } from "@/lib/modules/regular-booking.module"; +import { RecurringBookingService } from "@/lib/services/recurring-booking.service"; +import { Module } from "@nestjs/common"; + +@Module({ + imports: [RegularBookingModule], + providers: [RecurringBookingService], + exports: [RecurringBookingService], +}) +export class RecurringBookingModule {} diff --git a/apps/api/v2/src/lib/modules/regular-booking.module.ts b/apps/api/v2/src/lib/modules/regular-booking.module.ts new file mode 100644 index 00000000000000..01618100d1016a --- /dev/null +++ b/apps/api/v2/src/lib/modules/regular-booking.module.ts @@ -0,0 +1,32 @@ +import { PrismaAttributeRepository } from "@/lib/repositories/prisma-attribute.repository"; +import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; +import { PrismaHostRepository } from "@/lib/repositories/prisma-host.repository"; +import { PrismaOOORepository } from "@/lib/repositories/prisma-ooo.repository"; +import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository"; +import { CacheService } from "@/lib/services/cache.service"; +import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.service"; +import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; +import { LuckyUserService } from "@/lib/services/lucky-user.service"; +import { RegularBookingService } from "@/lib/services/regular-booking.service"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { Module } from "@nestjs/common"; + +@Module({ + imports: [PrismaModule], + providers: [ + PrismaAttributeRepository, + PrismaBookingRepository, + PrismaFeaturesRepository, + PrismaHostRepository, + PrismaOOORepository, + PrismaUserRepository, + CacheService, + CheckBookingAndDurationLimitsService, + CheckBookingLimitsService, + LuckyUserService, + RegularBookingService, + ], + exports: [RegularBookingService], +}) +export class RegularBookingModule {} diff --git a/apps/api/v2/src/lib/services/instant-booking-create.service.ts b/apps/api/v2/src/lib/services/instant-booking-create.service.ts new file mode 100644 index 00000000000000..e09e7f6ab1a549 --- /dev/null +++ b/apps/api/v2/src/lib/services/instant-booking-create.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from "@nestjs/common"; + +import { InstantBookingCreateService as BaseInstantBookingCreateService } from "@calcom/platform-libraries/bookings"; + +@Injectable() +export class InstantBookingCreateService extends BaseInstantBookingCreateService {} diff --git a/apps/api/v2/src/lib/services/recurring-booking.service.ts b/apps/api/v2/src/lib/services/recurring-booking.service.ts new file mode 100644 index 00000000000000..d40b53db4536ec --- /dev/null +++ b/apps/api/v2/src/lib/services/recurring-booking.service.ts @@ -0,0 +1,13 @@ +import { RegularBookingService } from "@/lib/services/regular-booking.service"; +import { Injectable } from "@nestjs/common"; + +import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/platform-libraries/bookings"; + +@Injectable() +export class RecurringBookingService extends BaseRecurringBookingService { + constructor(regularBookingService: RegularBookingService) { + super({ + regularBookingService, + }); + } +} diff --git a/apps/api/v2/src/lib/services/regular-booking.service.ts b/apps/api/v2/src/lib/services/regular-booking.service.ts new file mode 100644 index 00000000000000..f18775301b12cc --- /dev/null +++ b/apps/api/v2/src/lib/services/regular-booking.service.ts @@ -0,0 +1,46 @@ +import { PrismaAttributeRepository } from "@/lib/repositories/prisma-attribute.repository"; +import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; +import { PrismaHostRepository } from "@/lib/repositories/prisma-host.repository"; +import { PrismaOOORepository } from "@/lib/repositories/prisma-ooo.repository"; +import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository"; +import { CacheService } from "@/lib/services/cache.service"; +import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.service"; +import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; +import { LuckyUserService } from "@/lib/services/lucky-user.service"; +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +import { RegularBookingService as BaseRegularBookingService } from "@calcom/platform-libraries/bookings"; +import type { PrismaClient } from "@calcom/prisma"; + +@Injectable() +export class RegularBookingService extends BaseRegularBookingService { + constructor( + cacheService: CacheService, + checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService, + prismaWriteService: PrismaWriteService, + bookingRepository: PrismaBookingRepository, + featuresRepository: PrismaFeaturesRepository, + checkBookingLimitsService: CheckBookingLimitsService, + luckyUserService: LuckyUserService, + hostRepository: PrismaHostRepository, + oooRepository: PrismaOOORepository, + userRepository: PrismaUserRepository, + attributeRepository: PrismaAttributeRepository + ) { + super({ + cacheService, + checkBookingAndDurationLimitsService, + prismaClient: prismaWriteService.prisma as unknown as PrismaClient, + bookingRepository, + featuresRepository, + checkBookingLimitsService, + luckyUserService, + hostRepository, + oooRepository, + userRepository, + attributeRepository, + }); + } +} diff --git a/apps/web/lib/types/booking.ts b/apps/web/lib/types/booking.ts index 272cdc0d021277..df61be8354f42b 100644 --- a/apps/web/lib/types/booking.ts +++ b/apps/web/lib/types/booking.ts @@ -1,3 +1,3 @@ -export type BookingResponse = Awaited< - ReturnType ->; +import type { RegularBookingCreateResult } from "@calcom/features/bookings/lib/dto/types"; + +export type BookingResponse = RegularBookingCreateResult; diff --git a/apps/web/pages/api/book/event.ts b/apps/web/pages/api/book/event.ts index a4ca317048692b..ec4202214edc20 100644 --- a/apps/web/pages/api/book/event.ts +++ b/apps/web/pages/api/book/event.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking"; +import { getRegularBookingService } from "@calcom/features/bookings/di/RegularBookingService.container"; import { BotDetectionService } from "@calcom/features/bot-detection"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; @@ -45,11 +45,14 @@ async function handler(req: NextApiRequest & { userId?: number }) { creationSource: CreationSource.WEBAPP, }; - const booking = await handleNewBooking({ + const regularBookingService = getRegularBookingService(); + const booking = await regularBookingService.createBooking({ bookingData: req.body, - userId: session?.user?.id || -1, - hostname: req.headers.host || "", - forcedSlug: req.headers["x-cal-force-slug"] as string | undefined, + bookingMeta: { + userId: session?.user?.id || -1, + hostname: req.headers.host || "", + forcedSlug: req.headers["x-cal-force-slug"] as string | undefined, + }, }); // const booking = await createBookingThroughFactory(); return booking; diff --git a/apps/web/pages/api/book/instant-event.ts b/apps/web/pages/api/book/instant-event.ts index bcabfa472f4f44..f809795c10c744 100644 --- a/apps/web/pages/api/book/instant-event.ts +++ b/apps/web/pages/api/book/instant-event.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import handleInstantMeeting from "@calcom/features/instant-meeting/handleInstantMeeting"; +import { getInstantBookingCreateService } from "@calcom/features/bookings/di/InstantBookingCreateService.container"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import getIP from "@calcom/lib/getIP"; import { piiHasher } from "@calcom/lib/server/PiiHasher"; @@ -19,7 +19,14 @@ async function handler(req: NextApiRequest & { userId?: number }) { const session = await getServerSession({ req }); req.userId = session?.user?.id || -1; req.body.creationSource = CreationSource.WEBAPP; - const booking = await handleInstantMeeting(req); + + const instantBookingService = getInstantBookingCreateService(); + // Even though req.body is any type, createBooking validates the schema on run-time. + // TODO: We should do the run-time schema validation here and pass a typed bookingData instead and then run-time schema could be removed from createBooking. Then we can remove the any type from req.body. + const booking = await instantBookingService.createBooking({ + bookingData: req.body, + }); + return booking; } export default defaultResponder(handler); diff --git a/apps/web/pages/api/book/recurring-event.ts b/apps/web/pages/api/book/recurring-event.ts index d7dae48827ec2c..23b9a28fae670c 100644 --- a/apps/web/pages/api/book/recurring-event.ts +++ b/apps/web/pages/api/book/recurring-event.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import { handleNewRecurringBooking } from "@calcom/features/bookings/lib/handleNewRecurringBooking"; +import { getRecurringBookingService } from "@calcom/features/bookings/di/RecurringBookingService.container"; import type { BookingResponse } from "@calcom/features/bookings/types"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import getIP from "@calcom/lib/getIP"; @@ -43,15 +43,18 @@ async function handler(req: NextApiRequest & RequestMeta) { const session = await getServerSession({ req }); /* To mimic API behavior and comply with types */ - const createdBookings: BookingResponse[] = await handleNewRecurringBooking({ + const recurringBookingService = getRecurringBookingService(); + const createdBookings: BookingResponse[] = await recurringBookingService.createBooking({ bookingData: req.body, - userId: session?.user?.id || -1, - platformClientId: req.platformClientId, - platformCancelUrl: req.platformCancelUrl, - platformBookingUrl: req.platformBookingUrl, - platformRescheduleUrl: req.platformRescheduleUrl, - platformBookingLocation: req.platformBookingLocation, - noEmail: req.noEmail, + bookingMeta: { + userId: session?.user?.id || -1, + platformClientId: req.platformClientId, + platformCancelUrl: req.platformCancelUrl, + platformBookingUrl: req.platformBookingUrl, + platformRescheduleUrl: req.platformRescheduleUrl, + platformBookingLocation: req.platformBookingLocation, + noEmail: req.noEmail, + }, }); return createdBookings; diff --git a/packages/features/bookings/di/InstantBookingCreateService.container.ts b/packages/features/bookings/di/InstantBookingCreateService.container.ts new file mode 100644 index 00000000000000..78d10f0947f78f --- /dev/null +++ b/packages/features/bookings/di/InstantBookingCreateService.container.ts @@ -0,0 +1,13 @@ +import { createContainer } from "@calcom/features/di/di"; + +import { + type InstantBookingCreateService, + moduleLoader as instantBookingCreateServiceModule, +} from "./InstantBookingCreateService.module"; + +const container = createContainer(); + +export function getInstantBookingCreateService(): InstantBookingCreateService { + instantBookingCreateServiceModule.loadModule(container); + return container.get(instantBookingCreateServiceModule.token); +} diff --git a/packages/features/bookings/di/InstantBookingCreateService.module.ts b/packages/features/bookings/di/InstantBookingCreateService.module.ts new file mode 100644 index 00000000000000..76c3319e33fae9 --- /dev/null +++ b/packages/features/bookings/di/InstantBookingCreateService.module.ts @@ -0,0 +1,20 @@ +import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di"; +import { DI_TOKENS } from "@calcom/features/di/tokens"; +import { InstantBookingCreateService } from "@calcom/features/instant-meeting/handleInstantMeeting"; + +export const instantBookingCreateServiceModule = createModule(); +const token = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE; +const moduleToken = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE_MODULE; +const loadModule = bindModuleToClassOnToken({ + module: instantBookingCreateServiceModule, + moduleToken, + token, + classs: InstantBookingCreateService, + depsMap: {}, +}); + +export type { InstantBookingCreateService }; +export const moduleLoader = { + token, + loadModule, +}; diff --git a/packages/features/bookings/di/RecurringBookingService.container.ts b/packages/features/bookings/di/RecurringBookingService.container.ts new file mode 100644 index 00000000000000..6c3f5a65c48206 --- /dev/null +++ b/packages/features/bookings/di/RecurringBookingService.container.ts @@ -0,0 +1,14 @@ +import { createContainer } from "@calcom/features/di/di"; + +import { + type RecurringBookingService, + moduleLoader as recurringBookingServiceModule, +} from "./RecurringBookingService.module"; + +const container = createContainer(); + +export function getRecurringBookingService(): RecurringBookingService { + recurringBookingServiceModule.loadModule(container); + + return container.get(recurringBookingServiceModule.token); +} diff --git a/packages/features/bookings/di/RecurringBookingService.module.ts b/packages/features/bookings/di/RecurringBookingService.module.ts new file mode 100644 index 00000000000000..8e71dacfa23099 --- /dev/null +++ b/packages/features/bookings/di/RecurringBookingService.module.ts @@ -0,0 +1,25 @@ +import { RecurringBookingService } from "@calcom/features/bookings/lib/handleNewRecurringBooking"; +import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di"; +import { DI_TOKENS } from "@calcom/features/di/tokens"; + +import { moduleLoader as regularBookingServiceModuleLoader } from "./RegularBookingService.module"; + +const token = DI_TOKENS.RECURRING_BOOKING_SERVICE; +const moduleToken = DI_TOKENS.RECURRING_BOOKING_SERVICE_MODULE; +export const recurringBookingServiceModule = createModule(); + +const loadModule = bindModuleToClassOnToken({ + module: recurringBookingServiceModule, + moduleToken, + token, + classs: RecurringBookingService, + depsMap: { + regularBookingService: regularBookingServiceModuleLoader, + }, +}); + +export const moduleLoader = { + token, + loadModule, +}; +export type { RecurringBookingService }; diff --git a/packages/features/di/bookings/containers/RegularBookingService.container.ts b/packages/features/bookings/di/RegularBookingService.container.ts similarity index 70% rename from packages/features/di/bookings/containers/RegularBookingService.container.ts rename to packages/features/bookings/di/RegularBookingService.container.ts index b4df8cb790c2ef..7ec215a2a893be 100644 --- a/packages/features/di/bookings/containers/RegularBookingService.container.ts +++ b/packages/features/bookings/di/RegularBookingService.container.ts @@ -1,8 +1,9 @@ -import { createContainer } from "../../di"; +import { createContainer } from "@calcom/features/di/di"; + import { type RegularBookingService, - regularBookingServiceModule, -} from "../modules/RegularBookingService.module"; + moduleLoader as regularBookingServiceModule, +} from "./RegularBookingService.module"; const regularBookingServiceContainer = createContainer(); diff --git a/packages/features/di/bookings/modules/RegularBookingService.module.ts b/packages/features/bookings/di/RegularBookingService.module.ts similarity index 63% rename from packages/features/di/bookings/modules/RegularBookingService.module.ts rename to packages/features/bookings/di/RegularBookingService.module.ts index b00b03a1eb5ec3..187b12a2ec4f9f 100644 --- a/packages/features/di/bookings/modules/RegularBookingService.module.ts +++ b/packages/features/bookings/di/RegularBookingService.module.ts @@ -1,14 +1,18 @@ import { RegularBookingService } from "@calcom/features/bookings/lib/handleNewBooking"; +import { bindModuleToClassOnToken, createModule } from "@calcom/features/di/di"; +import { moduleLoader as attributeRepositoryModuleLoader } from "@calcom/features/di/modules/Attribute"; import { moduleLoader as bookingRepositoryModuleLoader } from "@calcom/features/di/modules/Booking"; import { moduleLoader as cacheModuleLoader } from "@calcom/features/di/modules/Cache"; import { moduleLoader as checkBookingAndDurationLimitsModuleLoader } from "@calcom/features/di/modules/CheckBookingAndDurationLimits"; import { moduleLoader as checkBookingLimitsModuleLoader } from "@calcom/features/di/modules/CheckBookingLimits"; import { moduleLoader as featuresRepositoryModuleLoader } from "@calcom/features/di/modules/Features"; +import { moduleLoader as hostRepositoryModuleLoader } from "@calcom/features/di/modules/Host"; +import { moduleLoader as luckyUserServiceModuleLoader } from "@calcom/features/di/modules/LuckyUser"; +import { moduleLoader as oooRepositoryModuleLoader } from "@calcom/features/di/modules/Ooo"; +import { moduleLoader as userRepositoryModuleLoader } from "@calcom/features/di/modules/User"; import { DI_TOKENS } from "@calcom/features/di/tokens"; import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module"; -import { bindModuleToClassOnToken, createModule } from "../../di"; - const thisModule = createModule(); const token = DI_TOKENS.REGULAR_BOOKING_SERVICE; const moduleToken = DI_TOKENS.REGULAR_BOOKING_SERVICE_MODULE; @@ -24,10 +28,15 @@ const loadModule = bindModuleToClassOnToken({ bookingRepository: bookingRepositoryModuleLoader, featuresRepository: featuresRepositoryModuleLoader, checkBookingLimitsService: checkBookingLimitsModuleLoader, + luckyUserService: luckyUserServiceModuleLoader, + hostRepository: hostRepositoryModuleLoader, + oooRepository: oooRepositoryModuleLoader, + userRepository: userRepositoryModuleLoader, + attributeRepository: attributeRepositoryModuleLoader, }, }); -export const regularBookingServiceModule = { +export const moduleLoader = { token, loadModule, }; diff --git a/packages/features/di/bookings/tokens.ts b/packages/features/bookings/di/tokens.ts similarity index 100% rename from packages/features/di/bookings/tokens.ts rename to packages/features/bookings/di/tokens.ts diff --git a/packages/features/bookings/lib/bookingCreateBodySchema.ts b/packages/features/bookings/lib/bookingCreateBodySchema.ts index 9da43a92398edd..086354f57b8ef5 100644 --- a/packages/features/bookings/lib/bookingCreateBodySchema.ts +++ b/packages/features/bookings/lib/bookingCreateBodySchema.ts @@ -57,36 +57,48 @@ export const bookingCreateBodySchema = z.object({ export type BookingCreateBody = z.input; +// TODO: Plan to create different schemas for Recurring Bookings, Instant Bookings and Regular Bookings. +// We later want to make these properties required. They were optional because they are part of general schema which is used for all types of bookings. +const recurringBookingCreateBodyPartialSchema = z.object({ + recurringCount: z.number().optional(), + isFirstRecurringSlot: z.boolean().optional(), + thirdPartyRecurringEventId: z.string().nullish(), + numSlotsToCheckForAvailability: z.number().optional(), + allRecurringDates: z + .array( + z.object({ + start: z.string(), + end: z.string().optional(), + }) + ) + .optional(), + currentRecurringIndex: z.number().optional(), +}); + export const extendedBookingCreateBody = bookingCreateBodySchema.merge( - z.object({ - noEmail: z.boolean().optional(), - recurringCount: z.number().optional(), - allRecurringDates: z - .array( - z.object({ - start: z.string(), - end: z.string(), - }) - ) - .optional(), - currentRecurringIndex: z.number().optional(), - appsStatus: z - .array( - z.object({ - appName: z.string(), - success: z.number(), - failures: z.number(), - type: z.string(), - errors: z.string().array(), - warnings: z.string().array().optional(), - }) - ) - .optional(), - luckyUsers: z.array(z.number()).optional(), - customInputs: z.undefined().optional(), - }) + z + .object({ + noEmail: z.boolean().optional(), + appsStatus: z + .array( + z.object({ + appName: z.string(), + success: z.number(), + failures: z.number(), + type: z.string(), + errors: z.string().array(), + warnings: z.string().array().optional(), + }) + ) + .optional(), + luckyUsers: z.array(z.number()).optional(), + customInputs: z.undefined().optional(), + }) + .merge(recurringBookingCreateBodyPartialSchema) ); +export type ExtendedBookingCreateBody = z.input; + // It has only the legacy props that are part of `responses` now. The API can still hit old props export const bookingCreateSchemaLegacyPropsForApi = z.object({ email: z.string(), diff --git a/packages/features/bookings/lib/create-instant-booking.ts b/packages/features/bookings/lib/create-instant-booking.ts index 672faecdb3b6d3..82289d496927cb 100644 --- a/packages/features/bookings/lib/create-instant-booking.ts +++ b/packages/features/bookings/lib/create-instant-booking.ts @@ -1,15 +1,13 @@ import { post } from "@calcom/lib/fetch-wrapper"; -import type { BookingCreateBody, InstantBookingResponse } from "../types"; +import type { BookingCreateBody } from "../types"; +import type { InstantBookingCreateResult } from "./dto/types"; export const createInstantBooking = async (data: BookingCreateBody) => { const response = await post< BookingCreateBody, - // fetch response can't have a Date type, it must be a string - Omit & { - startTime: string; - endTime: string; - } + // TODO: Fetch response can't have a Date type, it must be a string. We need to type expires as a string here but it breaks types down the line, fix it in a follow-up PR. + InstantBookingCreateResult >("/api/book/instant-event", data); return response; }; diff --git a/packages/features/bookings/lib/dto/types.d.ts b/packages/features/bookings/lib/dto/types.d.ts index e79f6bed0c925a..652cd2b47c1863 100644 --- a/packages/features/bookings/lib/dto/types.d.ts +++ b/packages/features/bookings/lib/dto/types.d.ts @@ -1,22 +1,22 @@ /** - * Domain types for BookingCreateService + * Domain types for Booking Services * These types are framework-agnostic and contain only the data required for booking operations */ -import type { z } from "zod"; - import type getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema"; import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi"; -import type { BookingCreateBody as BaseCreateBookingData } from "@calcom/prisma/zod/custom/booking"; -import type { extendedBookingCreateBody } from "@calcom/prisma/zod/custom/booking"; +import type { SchedulingType } from "@calcom/prisma/enums"; + +import type { ExtendedBookingCreateBody } from "../bookingCreateBodySchema"; +import type { RegularBookingService } from "../handleNewBooking"; -export type ExtendedBookingCreateData = z.input; +export type { BookingCreateBody } from "../bookingCreateBodySchema"; export type BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi; -export type CreateRegularBookingData = BaseCreateBookingData; +export type CreateRegularBookingData = ExtendedBookingCreateBody; -export type CreateInstantBookingData = BaseCreateBookingData; +export type CreateInstantBookingData = ExtendedBookingCreateBody; -export type CreateRecurringBookingData = (BaseCreateBookingData & { +export type CreateRecurringBookingData = (ExtendedBookingCreateBody & { schedulingType?: SchedulingType; })[]; @@ -27,6 +27,9 @@ export type PlatformParams = { platformRescheduleUrl?: string; platformBookingLocation?: string; areCalendarEventsEnabled?: boolean; + skipAvailabilityCheck?: boolean; + skipEventLimitsCheck?: boolean; + skipCalendarSyncTaskCreation?: boolean; }; export type CreateBookingMeta = { @@ -41,7 +44,7 @@ export type BookingHandlerInput = { bookingData: CreateRegularBookingData; } & CreateBookingMeta; -export type CreateInstantBookingResponse = { +export type InstantBookingCreateResult = { message: string; meetingTokenId: number; bookingId: number; @@ -49,3 +52,6 @@ export type CreateInstantBookingResponse = { expires: Date; userId: number | null; }; + +// TODO: In a followup PR, we working on defining the type here itself instead of inferring it. +export type RegularBookingCreateResult = Awaited>; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index d473c146dd7b1f..a82f933db70e6e 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,7 +1,7 @@ import short, { uuid } from "short-uuid"; import { v5 as uuidv5 } from "uuid"; - +import type { PlatformParams } from "@calcom/features/bookings/lib/dto/types"; import processExternalId from "@calcom/app-store/_utils/calendars/processExternalId"; import { getPaymentAppData } from "@calcom/app-store/_utils/payments/getPaymentAppData"; import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata"; @@ -11,15 +11,21 @@ import { OrganizerDefaultConferencingAppType, } from "@calcom/app-store/locations"; import { getAppFromSlug } from "@calcom/app-store/utils"; -import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; -import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; +import { + eventTypeMetaDataSchemaWithTypedApps, + eventTypeAppMetadataOptionalSchema, +} from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder"; import getICalUID from "@calcom/emails/lib/getICalUID"; import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder"; import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import type { BookingDataSchemaGetter } from "@calcom/features/bookings/lib/dto/types"; -import type { CreateRegularBookingData, CreateBookingMeta } from "@calcom/features/bookings/lib/dto/types"; +import type { + CreateRegularBookingData, + CreateBookingMeta, + BookingHandlerInput, +} from "@calcom/features/bookings/lib/dto/types"; import type { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; import { handlePayment } from "@calcom/features/bookings/lib/handlePayment"; import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger"; @@ -47,9 +53,6 @@ import { enrichHostsWithDelegationCredentials, getFirstDelegationConferencingCredentialAppLocation, } from "@calcom/lib/delegationCredential/server"; -import { getCheckBookingAndDurationLimitsService } from "@calcom/features/di/containers/BookingLimits"; -import { getCacheService } from "@calcom/features/di/containers/Cache"; -import { getLuckyUserService } from "@calcom/features/di/containers/LuckyUser"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; @@ -61,14 +64,18 @@ import type { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/serve import logger from "@calcom/lib/logger"; import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; +import type { LuckyUserService } from "@calcom/lib/server/getLuckyUser"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { BookingRepository } from "@calcom/lib/server/repository/booking"; +import type { PrismaAttributeRepository as AttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; +import type { HostRepository } from "@calcom/lib/server/repository/host"; +import type { PrismaOOORepository as OooRepository } from "@calcom/lib/server/repository/ooo"; +import type { UserRepository } from "@calcom/lib/server/repository/user"; import { WorkflowRepository } from "@calcom/lib/server/repository/workflow"; import { HashedLinkService } from "@calcom/lib/server/service/hashedLinkService"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import type { PrismaClient } from "@calcom/prisma"; -import { prisma } from "@calcom/prisma"; import type { DestinationCalendar, Prisma, User, AssignmentReasonEnum } from "@calcom/prisma/client"; import { BookingStatus, @@ -387,27 +394,6 @@ function buildTroubleshooterData({ return troubleshooterData; } -export type PlatformParams = { - platformClientId?: string; - platformCancelUrl?: string; - platformBookingUrl?: string; - platformRescheduleUrl?: string; - platformBookingLocation?: string; - areCalendarEventsEnabled?: boolean; -}; - -export type BookingHandlerInput = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - bookingData: Record; - userId?: number; - // These used to come from headers but now we're passing them as params - hostname?: string; - forcedSlug?: string; - skipAvailabilityCheck?: boolean; - skipEventLimitsCheck?: boolean; - skipCalendarSyncTaskCreation?: boolean; -} & PlatformParams; - function formatAvailabilitySnapshot(data: { dateRanges: { start: dayjs.Dayjs; end: dayjs.Dayjs }[]; oooExcludedDateRanges: { start: dayjs.Dayjs; end: dayjs.Dayjs }[]; @@ -425,8 +411,23 @@ function formatAvailabilitySnapshot(data: { }; } +export interface IBookingServiceDependencies { + cacheService: CacheService; + checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService; + prismaClient: PrismaClient; + bookingRepository: BookingRepository; + featuresRepository: FeaturesRepository; + checkBookingLimitsService: CheckBookingLimitsService; + luckyUserService: LuckyUserService; + hostRepository: HostRepository; + oooRepository: OooRepository; + userRepository: UserRepository; + attributeRepository: AttributeRepository; +} + async function handler( input: BookingHandlerInput, + deps: IBookingServiceDependencies, bookingDataSchemaGetter: BookingDataSchemaGetter = getBookingDataSchema ) { const { @@ -445,6 +446,14 @@ async function handler( skipCalendarSyncTaskCreation = false, } = input; + const { + prismaClient: prisma, + bookingRepository, + cacheService, + checkBookingAndDurationLimitsService, + luckyUserService, + } = deps; + const isPlatformBooking = !!platformClientId; const eventType = await getEventType({ @@ -546,7 +555,7 @@ async function handler( const bookingSeat = reqBody.rescheduleUid ? await getSeatedBooking(reqBody.rescheduleUid) : null; const rescheduleUid = bookingSeat ? bookingSeat.booking.uid : reqBody.rescheduleUid; const isNormalBookingOrFirstRecurringSlot = input.bookingData.allRecurringDates - ? input.bookingData.isFirstRecurringSlot + ? !!input.bookingData.isFirstRecurringSlot : true; let originalRescheduledBooking = rescheduleUid @@ -572,11 +581,9 @@ async function handler( (!isConfirmedByDefault && !userReschedulingIsOwner) || eventType.schedulingType === SchedulingType.ROUND_ROBIN ) { - const bookingRepo = new BookingRepository(prisma); - const requiresPayment = !Number.isNaN(paymentAppData.price) && paymentAppData.price > 0; - const existingBooking = await bookingRepo.getValidBookingFromEventTypeForAttendee({ + const existingBooking = await bookingRepository.getValidBookingFromEventTypeForAttendee({ eventTypeId, bookerEmail, bookerPhoneNumber, @@ -613,7 +620,6 @@ async function handler( } } - const cacheService = getCacheService(); const shouldServeCache = await cacheService.getShouldServeCache(_shouldServeCache, eventType.team?.id); const isTeamEventType = @@ -726,7 +732,6 @@ async function handler( }); if (!skipEventLimitsCheck) { - const checkBookingAndDurationLimitsService = getCheckBookingAndDurationLimitsService(); await checkBookingAndDurationLimitsService.checkBookingAndDurationLimits({ eventType, reqBodyStart: reqBody.start, @@ -786,7 +791,11 @@ async function handler( }, }), }; - if (input.bookingData.allRecurringDates && input.bookingData.isFirstRecurringSlot) { + if ( + input.bookingData.allRecurringDates && + input.bookingData.isFirstRecurringSlot && + input.bookingData.numSlotsToCheckForAvailability + ) { const isTeamEvent = eventType.schedulingType === SchedulingType.COLLECTIVE || eventType.schedulingType === SchedulingType.ROUND_ROBIN; @@ -943,7 +952,6 @@ async function handler( memberId: eventTypeWithUsers.users[0].id ?? null, teamId: eventType.teamId, }); - const luckyUserService = getLuckyUserService(); const newLuckyUser = await luckyUserService.getLuckyUser({ // find a lucky user that is not already in the luckyUsers array availableUsers: freeUsers, @@ -968,7 +976,9 @@ async function handler( } if ( input.bookingData.isFirstRecurringSlot && - eventType.schedulingType === SchedulingType.ROUND_ROBIN + eventType.schedulingType === SchedulingType.ROUND_ROBIN && + input.bookingData.numSlotsToCheckForAvailability && + input.bookingData.allRecurringDates ) { // for recurring round robin events check if lucky user is available for next slots try { @@ -1619,9 +1629,9 @@ async function handler( if (booking && booking.id && eventType.seatsPerTimeSlot) { const currentAttendee = booking.attendees.find( (attendee) => - attendee.email === input.bookingData.responses.email || - (input.bookingData.responses.attendeePhoneNumber && - attendee.phoneNumber === input.bookingData.responses.attendeePhoneNumber) + attendee.email === bookingData.responses.email || + (bookingData.responses.attendeePhoneNumber && + attendee.phoneNumber === bookingData.responses.attendeePhoneNumber) ); // Save description to bookingSeat @@ -2434,17 +2444,6 @@ async function handler( }; } -export default handler; - -export interface IBookingServiceDependencies { - cacheService: CacheService; - checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService; - prismaClient: PrismaClient; - bookingRepository: BookingRepository; - featuresRepository: FeaturesRepository; - checkBookingLimitsService: CheckBookingLimitsService; -} - /** * Takes care of creating/rescheduling non-recurring, non-instant bookings. Such bookings could be TeamBooking, UserBooking, SeatedUserBooking, SeatedTeamBooking, etc. * We can't name it CoreBookingService because non-instant booking also creates a booking but it is entirely different from the regular booking. @@ -2454,19 +2453,29 @@ export class RegularBookingService implements IBookingService { constructor(private readonly deps: IBookingServiceDependencies) {} async createBooking(input: { bookingData: CreateRegularBookingData; bookingMeta?: CreateBookingMeta }) { - // deps to be passed to handler in follow-up PR - return handler({ bookingData: input.bookingData, ...input.bookingMeta }); + return handler({ bookingData: input.bookingData, ...input.bookingMeta }, this.deps); } async rescheduleBooking(input: { bookingData: CreateRegularBookingData; bookingMeta?: CreateBookingMeta }) { - return handler({ bookingData: input.bookingData, ...input.bookingMeta }); + return handler({ bookingData: input.bookingData, ...input.bookingMeta }, this.deps); } - async rescheduleBookingForApiV1(input: { + /** + * @deprecated Exists only till API v1 is removed. + */ + async createBookingForApiV1(input: { bookingData: CreateRegularBookingData; bookingMeta?: CreateBookingMeta; bookingDataSchemaGetter: BookingDataSchemaGetter; }) { - return handler({ bookingData: input.bookingData, ...input.bookingMeta }, input.bookingDataSchemaGetter); + const bookingMeta = input.bookingMeta ?? {}; + return handler( + { + bookingData: input.bookingData, + ...bookingMeta, + }, + this.deps, + input.bookingDataSchemaGetter + ); } } diff --git a/packages/features/bookings/lib/handleNewBooking/test/email-verification-booking.test.ts b/packages/features/bookings/lib/handleNewBooking/test/email-verification-booking.test.ts index 07ff10c9e0dbbd..2424d3dbea0130 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/email-verification-booking.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/email-verification-booking.test.ts @@ -19,6 +19,8 @@ import { vi, describe, expect, beforeEach } from "vitest"; import { test } from "@calcom/web/test/fixtures/fixtures"; +import { getNewBookingHandler } from "./getNewBookingHandler"; + vi.mock("@calcom/trpc/server/routers/viewer/auth/util", () => ({ verifyCodeUnAuthenticated: vi.fn(), })); @@ -36,7 +38,7 @@ describe("handleNewBooking - Email Verification", () => { test( "should throw error when email verification is required but no verification code is provided", async () => { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const handleNewBooking = getNewBookingHandler(); const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util"); const booker = getBooker({ @@ -96,7 +98,7 @@ describe("handleNewBooking - Email Verification", () => { test( "should throw error when email verification is required and verification code is invalid", async () => { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const handleNewBooking = getNewBookingHandler(); const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util"); vi.mocked(verifyCodeUnAuthenticated).mockRejectedValue(new Error("Invalid verification code")); @@ -159,7 +161,7 @@ describe("handleNewBooking - Email Verification", () => { test( "should successfully create booking when email verification is required and valid verification code is provided", async () => { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const handleNewBooking = getNewBookingHandler(); const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util"); vi.mocked(verifyCodeUnAuthenticated).mockResolvedValue(undefined); @@ -220,7 +222,7 @@ describe("handleNewBooking - Email Verification", () => { test( "should handle rate limiting error from verification service", async () => { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const handleNewBooking = getNewBookingHandler(); const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util"); const rateLimitError = new Error("Rate limit exceeded"); @@ -285,7 +287,7 @@ describe("handleNewBooking - Email Verification", () => { test( "should proceed normally when email verification is not required", async () => { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const handleNewBooking = getNewBookingHandler(); const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util"); const booker = getBooker({ diff --git a/packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts b/packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts index 7db09dc1e263ba..9f221a8d904646 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts @@ -1,6 +1,15 @@ -async function handler(input: any) { - const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; - return handleNewBooking(input); +import type { BookingHandlerInput } from "@calcom/features/bookings/lib/dto/types"; + +async function handler(input: BookingHandlerInput) { + const { getRegularBookingService } = await import( + "@calcom/features/bookings/di/RegularBookingService.container" + ); + const regularBookingService = getRegularBookingService(); + const { bookingData, ...bookingMeta } = input; + return regularBookingService.createBooking({ + bookingData, + bookingMeta, + }); } export function getNewBookingHandler() { diff --git a/packages/features/bookings/lib/handleNewBooking/test/handleNewRecurringBooking.test.ts b/packages/features/bookings/lib/handleNewBooking/test/handleNewRecurringBooking.test.ts index 0b1b2b6f065922..9453f3e3200465 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/handleNewRecurringBooking.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/handleNewRecurringBooking.test.ts @@ -8,7 +8,6 @@ import { mockCalendarToHaveNoBusySlots, mockSuccessfulVideoMeetingCreation, TestData, - Timezones, } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; import { expectBookingCreatedWebhookToHaveBeenFired, @@ -22,13 +21,12 @@ import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAn import { v4 as uuidv4 } from "uuid"; import { describe, expect } from "vitest"; +import { getRecurringBookingService } from "@calcom/features/bookings/di/RecurringBookingService.container"; import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; -import { BookingStatus, SchedulingType } from "@calcom/prisma/enums"; +import { BookingStatus } from "@calcom/prisma/enums"; import { test } from "@calcom/web/test/fixtures/fixtures"; -import { handleNewRecurringBooking } from "../../handleNewRecurringBooking"; - const DAY_IN_MS = 1000 * 60 * 60 * 24; function getPlusDayDate(date: string, days: number) { @@ -153,10 +151,13 @@ describe("handleNewRecurringBooking", () => { }; }); + const recurringBookingService = getRecurringBookingService(); // Call handleNewRecurringBooking directly instead of through API - const createdBookings = await handleNewRecurringBooking({ + const createdBookings = await recurringBookingService.createBooking({ bookingData: bookingDataArray, - userId: -1, // Simulating anonymous user like in the API test + bookingMeta: { + userId: -1, // Simulating anonymous user like in the API test + }, }); expect(createdBookings.length).toBe(numOfSlotsToBeBooked); @@ -177,7 +178,7 @@ describe("handleNewRecurringBooking", () => { await expectBookingToBeInDatabase({ description: "", - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, eventTypeId: mockBookingData.eventTypeId, status: BookingStatus.ACCEPTED, @@ -211,16 +212,14 @@ describe("handleNewRecurringBooking", () => { expectSuccessfulBookingCreationEmails({ booker, booking: { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion uid: createdBookings[0].uid!, urlOrigin: WEBSITE_URL, }, organizer, emails, bookingTimeRange: { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion start: createdBookings[0].startTime!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + end: createdBookings[0].endTime!, }, iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", @@ -253,302 +252,6 @@ describe("handleNewRecurringBooking", () => { }, timeout ); - - test.skip( - `should fail recurring booking if second slot is already booked`, - async ({}) => { - const booker = getBooker({ - email: "booker@example.com", - name: "Booker", - }); - - const organizer = getOrganizer({ - name: "Organizer", - email: "organizer@example.com", - id: 101, - schedules: [TestData.schedules.IstWorkHours], - credentials: [getGoogleCalendarCredential()], - selectedCalendars: [TestData.selectedCalendars.google], - destinationCalendar: { - integration: "google_calendar", - externalId: "organizer@google-calendar.com", - }, - }); - - const recurrence = getRecurrence({ - type: "weekly", - numberOfOccurrences: 3, - }); - const plus1DateString = getDate({ dateIncrement: 1 }).dateString; - const plus2DateString = getDate({ dateIncrement: 2 }).dateString; - await createBookingScenario( - getScenarioData({ - webhooks: [ - { - userId: organizer.id, - eventTriggers: ["BOOKING_CREATED"], - subscriberUrl: "http://my-webhook.example.com", - active: true, - eventTypeId: 1, - appId: null, - }, - ], - eventTypes: [ - { - id: 1, - slotInterval: 30, - length: 30, - recurringEvent: recurrence, - users: [ - { - id: 101, - }, - ], - destinationCalendar: { - integration: "google_calendar", - externalId: "event-type-1@google-calendar.com", - }, - }, - ], - bookings: [ - { - uid: "booking-1-uid", - eventTypeId: 1, - userId: organizer.id, - status: BookingStatus.ACCEPTED, - startTime: `${plus2DateString}T04:00:00.000Z`, - endTime: `${plus2DateString}T04:30:00.000Z`, - }, - ], - organizer, - apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]], - }) - ); - - mockSuccessfulVideoMeetingCreation({ - metadataLookupKey: "dailyvideo", - videoMeetingData: { - id: "MOCK_ID", - password: "MOCK_PASS", - url: `http://mock-dailyvideo.example.com/meeting-1`, - }, - }); - - mockCalendarToHaveNoBusySlots("googlecalendar", { - create: { - id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", - }, - }); - - const recurringCountInRequest = 2; - const mockBookingData = getMockRequestDataForBooking({ - data: { - eventTypeId: 1, - start: `${plus1DateString}T04:00:00.000Z`, - end: `${plus1DateString}T04:30:00.000Z`, - recurringEventId: uuidv4(), - recurringCount: recurringCountInRequest, - responses: { - email: booker.email, - name: booker.name, - location: { optionValue: "", value: "integrations:daily" }, - }, - }, - }); - - const numOfSlotsToBeBooked = 4; - - // Create an array of booking data for multiple slots - const bookingDataArray = Array(numOfSlotsToBeBooked) - .fill(mockBookingData) - .map((mockBookingData, index) => { - return { - ...mockBookingData, - start: getPlusDayDate(mockBookingData.start, index).toISOString(), - end: getPlusDayDate(mockBookingData.end, index).toISOString(), - }; - }); - - await expect( - async () => - await handleNewRecurringBooking({ - bookingData: bookingDataArray, - userId: -1, - }) - ).rejects.toThrow(ErrorCode.NoAvailableUsersFound); - }, - timeout - ); - }); - - describe("Round robin event type:", () => { - test.skip("should fail recurring booking if a fixed host is not available on the second slot", async () => { - const booker = getBooker({ - email: "booker@example.com", - name: "Booker", - }); - - const organizer = getOrganizer({ - name: "Organizer", - email: "organizer@example.com", - id: 101, - defaultScheduleId: null, - teams: [ - { - membership: { - accepted: true, - }, - team: { - id: 1, - name: "Team 1", - slug: "team-1", - }, - }, - ], - schedules: [TestData.schedules.IstMorningShift], - credentials: [getGoogleCalendarCredential()], - selectedCalendars: [TestData.selectedCalendars.google], - destinationCalendar: { - integration: TestData.apps["google-calendar"].type, - externalId: "organizer@google-calendar.com", - }, - }); - - const otherTeamMembers = [ - { - name: "Other Team Member 1", - username: "other-team-member-1", - timeZone: Timezones["+5:30"], - defaultScheduleId: null, - email: "other-team-member-1@example.com", - id: 102, - schedules: [TestData.schedules.IstMorningShift], - credentials: [getGoogleCalendarCredential()], - selectedCalendars: [TestData.selectedCalendars.google], - destinationCalendar: { - integration: TestData.apps["google-calendar"].type, - externalId: "other-team-member-1@google-calendar.com", - }, - }, - ]; - - const recurrence = getRecurrence({ - type: "weekly", - numberOfOccurrences: 3, - }); - - const plus1DateString = getDate({ dateIncrement: 1 }).dateString; - const plus2DateString = getDate({ dateIncrement: 2 }).dateString; - - await createBookingScenario( - getScenarioData({ - webhooks: [ - { - userId: organizer.id, - eventTriggers: ["BOOKING_CREATED"], - subscriberUrl: "http://my-webhook.example.com", - active: true, - eventTypeId: 1, - appId: null, - }, - ], - eventTypes: [ - { - id: 1, - slotInterval: 30, - schedulingType: SchedulingType.ROUND_ROBIN, - length: 30, - recurringEvent: recurrence, - hosts: [ - { - userId: 101, - isFixed: false, - }, - { - userId: 102, - isFixed: true, - }, - ], - destinationCalendar: { - integration: "google_calendar", - externalId: "event-type-1@google-calendar.com", - }, - }, - ], - bookings: [ - { - userId: 102, - attendees: [ - { - email: "IntegrationTestUser102@example.com", - }, - ], - eventTypeId: 1, - status: "ACCEPTED", - startTime: `${plus2DateString}T04:00:00.000Z`, - endTime: `${plus2DateString}T04:30:00.000Z`, - }, - ], - organizer, - usersApartFromOrganizer: otherTeamMembers, - apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]], - }) - ); - - mockSuccessfulVideoMeetingCreation({ - metadataLookupKey: "dailyvideo", - videoMeetingData: { - id: "MOCK_ID", - password: "MOCK_PASS", - url: `http://mock-dailyvideo.example.com/meeting-1`, - }, - }); - - mockCalendarToHaveNoBusySlots("googlecalendar", { - create: { - id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", - }, - }); - - const recurringCountInRequest = 4; - const mockBookingData = getMockRequestDataForBooking({ - data: { - eventTypeId: 1, - start: `${plus1DateString}T04:00:00.000Z`, - end: `${plus1DateString}T04:30:00.000Z`, - recurringEventId: uuidv4(), - recurringCount: recurringCountInRequest, - responses: { - email: booker.email, - name: booker.name, - location: { optionValue: "", value: "integrations:daily" }, - }, - }, - }); - - const numOfSlotsToBeBooked = 4; - - // Create an array of booking data for multiple slots - const bookingDataArray = Array(numOfSlotsToBeBooked) - .fill(mockBookingData) - .map((mockBookingData, index) => { - return { - ...mockBookingData, - schedulingType: SchedulingType.ROUND_ROBIN, - start: getPlusDayDate(mockBookingData.start, index).toISOString(), - end: getPlusDayDate(mockBookingData.end, index).toISOString(), - }; - }); - - await expect(() => - handleNewRecurringBooking({ - bookingData: bookingDataArray, - userId: -1, - }) - ).rejects.toThrow(ErrorCode.NoAvailableUsersFound); - }); }); }); diff --git a/packages/features/bookings/lib/handleNewRecurringBooking.ts b/packages/features/bookings/lib/handleNewRecurringBooking.ts index f188c0fe2013a8..47a8f143508437 100644 --- a/packages/features/bookings/lib/handleNewRecurringBooking.ts +++ b/packages/features/bookings/lib/handleNewRecurringBooking.ts @@ -1,5 +1,4 @@ import type { CreateBookingMeta, CreateRecurringBookingData } from "@calcom/features/bookings/lib/dto/types"; -import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking"; import type { BookingResponse } from "@calcom/features/bookings/types"; import { SchedulingType } from "@calcom/prisma/enums"; import type { AppsStatus } from "@calcom/types/Calendar"; @@ -11,10 +10,14 @@ export type BookingHandlerInput = { bookingData: CreateRecurringBookingData; } & CreateBookingMeta; -export const handleNewRecurringBooking = async (input: BookingHandlerInput): Promise => { +export const handleNewRecurringBooking = async ( + input: BookingHandlerInput, + deps: IRecurringBookingServiceDependencies +): Promise => { const data = input.bookingData; + const { regularBookingService } = deps; const createdBookings: BookingResponse[] = []; - const allRecurringDates: { start: string | undefined; end: string | undefined }[] = data.map((booking) => { + const allRecurringDates: { start: string; end: string | undefined }[] = data.map((booking) => { return { start: booking.start, end: booking.end }; }); const appsStatus: AppsStatus[] | undefined = undefined; @@ -51,11 +54,13 @@ export const handleNewRecurringBooking = async (input: BookingHandlerInput): Pro noEmail: input.noEmail !== undefined ? input.noEmail : false, }; - const firstBookingResult = await handleNewBooking({ + const firstBookingResult = await regularBookingService.createBooking({ bookingData: recurringEventData, - hostname: input.hostname || "", - forcedSlug: input.forcedSlug as string | undefined, - ...handleBookingMeta, + bookingMeta: { + hostname: input.hostname || "", + forcedSlug: input.forcedSlug as string | undefined, + ...handleBookingMeta, + }, }); luckyUsers = firstBookingResult.luckyUsers; } @@ -92,11 +97,13 @@ export const handleNewRecurringBooking = async (input: BookingHandlerInput): Pro luckyUsers, }; - const promiseEachRecurringBooking: ReturnType = handleNewBooking({ - hostname: input.hostname || "", - forcedSlug: input.forcedSlug as string | undefined, + const promiseEachRecurringBooking = regularBookingService.createBooking({ bookingData: recurringEventData, - ...handleBookingMeta, + bookingMeta: { + hostname: input.hostname || "", + forcedSlug: input.forcedSlug as string | undefined, + ...handleBookingMeta, + }, }); const eachRecurringBooking = await promiseEachRecurringBooking; @@ -132,8 +139,7 @@ export class RecurringBookingService implements IBookingService { bookingMeta?: CreateBookingMeta; }): Promise { const handlerInput = { bookingData: input.bookingData, ...(input.bookingMeta || {}) }; - // FOLLOW-UP: Pass on dependencies to the handler - return handleNewRecurringBooking(handlerInput); + return handleNewRecurringBooking(handlerInput, this.deps); } async rescheduleBooking(input: { @@ -141,7 +147,6 @@ export class RecurringBookingService implements IBookingService { bookingMeta?: CreateBookingMeta; }): Promise { const handlerInput = { bookingData: input.bookingData, ...(input.bookingMeta || {}) }; - // FOLLOW-UP: Pass on dependencies to the handler - return handleNewRecurringBooking(handlerInput); + return handleNewRecurringBooking(handlerInput, this.deps); } } diff --git a/packages/features/bookings/lib/index.ts b/packages/features/bookings/lib/index.ts index c0602ec11d5b47..a6c78e87f1fe16 100644 --- a/packages/features/bookings/lib/index.ts +++ b/packages/features/bookings/lib/index.ts @@ -6,3 +6,4 @@ export { export { createBooking } from "./create-booking"; export { createRecurringBooking } from "./create-recurring-booking"; export { createInstantBooking } from "./create-instant-booking"; +export type { BookingResponse } from "../types"; diff --git a/packages/features/bookings/types.ts b/packages/features/bookings/types.ts index 1e5f131d707cfc..a3c03769b1e5cb 100644 --- a/packages/features/bookings/types.ts +++ b/packages/features/bookings/types.ts @@ -1,6 +1,7 @@ import type { ErrorOption, FieldPath } from "react-hook-form"; -import type { SchedulingType } from "@calcom/prisma/client"; +import type { RegularBookingCreateResult } from "@calcom/features/bookings/lib/dto/types"; +import type { SchedulingType } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc/react"; import type { AppsStatus } from "@calcom/types/Calendar"; @@ -93,13 +94,8 @@ export type RecurringBookingCreateBody = BookingCreateBody & { schedulingType?: SchedulingType; }; -export type BookingResponse = Awaited< - ReturnType ->; - -export type InstantBookingResponse = Awaited< - ReturnType ->; +// TODO: Instead of using the two different names, we want to use RegularBookingCreateResult name only but the name BookingResponse is used at ton of places and would be fixed in a separate followup PR. +export type BookingResponse = RegularBookingCreateResult; export type MarkNoShowResponse = Awaited< ReturnType diff --git a/packages/features/di/bookings/containers/InstantBookingCreateService.container.ts b/packages/features/di/bookings/containers/InstantBookingCreateService.container.ts deleted file mode 100644 index 4be7f406cce70c..00000000000000 --- a/packages/features/di/bookings/containers/InstantBookingCreateService.container.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DI_TOKENS } from "@calcom/features/di/tokens"; - -import { createContainer } from "../../di"; -import { - type InstantBookingCreateService, - instantBookingCreateServiceModule, -} from "../modules/InstantBookingCreateService.module"; - -const container = createContainer(); -container.load(DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE_MODULE, instantBookingCreateServiceModule); - -export function getInstantBookingCreateService(): InstantBookingCreateService { - return container.get(DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE); -} diff --git a/packages/features/di/bookings/containers/RecurringBookingService.container.ts b/packages/features/di/bookings/containers/RecurringBookingService.container.ts deleted file mode 100644 index 86575191f47f53..00000000000000 --- a/packages/features/di/bookings/containers/RecurringBookingService.container.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { bookingRepositoryModule } from "@calcom/features/di/modules/Booking"; -import { cacheModule } from "@calcom/features/di/modules/Cache"; -import { checkBookingAndDurationLimitsModule } from "@calcom/features/di/modules/CheckBookingAndDurationLimits"; -import { checkBookingLimitsModule } from "@calcom/features/di/modules/CheckBookingLimits"; -import { featuresRepositoryModule } from "@calcom/features/di/modules/Features"; -import { DI_TOKENS } from "@calcom/features/di/tokens"; -import { prismaModule } from "@calcom/prisma/prisma.module"; - -import { createContainer } from "../../di"; -import { - type RecurringBookingService, - recurringBookingServiceModule, -} from "../modules/RecurringBookingService.module"; - -const container = createContainer(); -container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); -container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); -container.load(DI_TOKENS.CACHE_SERVICE_MODULE, cacheModule); -container.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule); -container.load(DI_TOKENS.FEATURES_REPOSITORY_MODULE, featuresRepositoryModule); -container.load( - DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE, - checkBookingAndDurationLimitsModule -); -container.load(DI_TOKENS.RECURRING_BOOKING_SERVICE_MODULE, recurringBookingServiceModule); - -export function getRecurringBookingService(): RecurringBookingService { - return container.get(DI_TOKENS.RECURRING_BOOKING_SERVICE); -} diff --git a/packages/features/di/bookings/modules/InstantBookingCreateService.module.ts b/packages/features/di/bookings/modules/InstantBookingCreateService.module.ts deleted file mode 100644 index aebfe66c6f793e..00000000000000 --- a/packages/features/di/bookings/modules/InstantBookingCreateService.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { InstantBookingCreateService } from "@calcom/features/instant-meeting/handleInstantMeeting"; -import { DI_TOKENS } from "@calcom/features/di/tokens"; - -import { createModule } from "../../di"; - -export const instantBookingCreateServiceModule = createModule(); - -instantBookingCreateServiceModule - .bind(DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE) - .toClass(InstantBookingCreateService); -export type { InstantBookingCreateService }; diff --git a/packages/features/di/bookings/modules/RecurringBookingService.module.ts b/packages/features/di/bookings/modules/RecurringBookingService.module.ts deleted file mode 100644 index b93e760fe59fca..00000000000000 --- a/packages/features/di/bookings/modules/RecurringBookingService.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RecurringBookingService } from "@calcom/features/bookings/lib/handleNewRecurringBooking"; -import { DI_TOKENS } from "@calcom/features/di/tokens"; - -import { createModule } from "../../di"; - -export const recurringBookingServiceModule = createModule(); - -recurringBookingServiceModule.bind(DI_TOKENS.RECURRING_BOOKING_SERVICE).toClass(RecurringBookingService, { - regularBookingService: DI_TOKENS.REGULAR_BOOKING_SERVICE, -}); - -export type { RecurringBookingService }; diff --git a/packages/features/di/tokens.ts b/packages/features/di/tokens.ts index 9936078b304d5a..b03267131f7cf6 100644 --- a/packages/features/di/tokens.ts +++ b/packages/features/di/tokens.ts @@ -1,4 +1,4 @@ -import { BOOKING_DI_TOKENS } from "./bookings/tokens"; +import { BOOKING_DI_TOKENS } from "@calcom/features/bookings/di/tokens"; export const DI_TOKENS = { PRISMA_CLIENT: Symbol("PrismaClient"), diff --git a/packages/features/instant-meeting/handleInstantMeeting.test.ts b/packages/features/instant-meeting/handleInstantMeeting.test.ts index 74909bd02b0b1c..a133eeb016954b 100644 --- a/packages/features/instant-meeting/handleInstantMeeting.test.ts +++ b/packages/features/instant-meeting/handleInstantMeeting.test.ts @@ -14,6 +14,7 @@ import { import type { NextApiRequest } from "next"; import { describe, it, expect, vi, beforeEach } from "vitest"; +import { getInstantBookingCreateService } from "@calcom/features/bookings/di/InstantBookingCreateService.container"; import { BookingStatus } from "@calcom/prisma/enums"; vi.mock("@calcom/features/notifications/sendNotification", () => ({ @@ -35,7 +36,7 @@ describe("handleInstantMeeting", () => { }); describe("team event instant meeting", () => { it("should successfully create instant meeting for team event", async () => { - const handler = (await import("./handleInstantMeeting")).default; + const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", @@ -109,7 +110,9 @@ describe("handleInstantMeeting", () => { url: "/api/instant-meeting", } as NextApiRequest; - const result = await handler(mockRequest); + const result = await instantBookingCreateService.createBooking({ + bookingData: mockRequest.body, + }); expect(result.message).toBe("Success"); expect(result.bookingId).toBeDefined(); @@ -132,7 +135,7 @@ describe("handleInstantMeeting", () => { }); it("should throw error for non-team event types", async () => { - const handler = (await import("./handleInstantMeeting")).default; + const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", @@ -188,9 +191,11 @@ describe("handleInstantMeeting", () => { url: "/api/instant-meeting", } as NextApiRequest; - await expect(handler(mockRequest)).rejects.toThrow( - "Only Team Event Types are supported for Instant Meeting" - ); + await expect( + instantBookingCreateService.createBooking({ + bookingData: mockRequest.body, + }) + ).rejects.toThrow("Only Team Event Types are supported for Instant Meeting"); }); }); }); diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts index 557216612d2a1a..00319bba8adc64 100644 --- a/packages/features/instant-meeting/handleInstantMeeting.ts +++ b/packages/features/instant-meeting/handleInstantMeeting.ts @@ -1,12 +1,12 @@ import { randomBytes } from "crypto"; -import type { NextApiRequest } from "next"; import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; +import { createInstantMeetingWithCalVideo } from "@calcom/app-store/videoClient"; import dayjs from "@calcom/dayjs"; import type { CreateInstantBookingData, - CreateInstantBookingResponse, + InstantBookingCreateResult, } from "@calcom/features/bookings/lib/dto/types"; import getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; @@ -22,7 +22,6 @@ import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { createInstantMeetingWithCalVideo } from "@calcom/app-store/videoClient"; import prisma from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums"; @@ -156,7 +155,7 @@ const triggerBrowserNotifications = async (args: { await Promise.allSettled(promises); }; -async function _handler(bookingData: CreateInstantBookingData) { +async function handler(bookingData: CreateInstantBookingData) { let eventType = await getEventTypesFromDB(bookingData.eventTypeId); const isOrgTeamEvent = !!eventType?.team && !!eventType?.team?.parentId; eventType = { @@ -330,21 +329,14 @@ async function _handler(bookingData: CreateInstantBookingData) { bookingUid: newBooking.uid, expires: instantMeetingToken.expires, userId: newBooking.userId, - } satisfies CreateInstantBookingResponse; + } satisfies InstantBookingCreateResult; } /** * Instant booking service that handles instant/immediate bookings */ export class InstantBookingCreateService implements IBookingCreateService { - async createBooking(input: { - bookingData: CreateInstantBookingData; - }): Promise { - return _handler(input.bookingData); + async createBooking(input: { bookingData: CreateInstantBookingData }): Promise { + return handler(input.bookingData); } } - -// TODO: Remove it in a follow-up PR -export default async function handler(req: NextApiRequest) { - return _handler(req.body); -} diff --git a/packages/platform/libraries/bookings.ts b/packages/platform/libraries/bookings.ts index 472bd0a5206fd9..e38ba571758f41 100644 --- a/packages/platform/libraries/bookings.ts +++ b/packages/platform/libraries/bookings.ts @@ -2,3 +2,10 @@ export { LuckyUserService } from "@calcom/lib/server/getLuckyUser"; export { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; export { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; +export { RegularBookingService } from "@calcom/features/bookings/lib/handleNewBooking"; +export { RecurringBookingService } from "@calcom/features/bookings/lib/handleNewRecurringBooking"; +export { InstantBookingCreateService } from "@calcom/features/instant-meeting/handleInstantMeeting"; +export type { + InstantBookingCreateResult, + RegularBookingCreateResult, +} from "@calcom/features/bookings/lib/dto/types"; diff --git a/packages/platform/libraries/index.ts b/packages/platform/libraries/index.ts index 3c5e1ee85865f5..6b7a9701948548 100644 --- a/packages/platform/libraries/index.ts +++ b/packages/platform/libraries/index.ts @@ -2,7 +2,6 @@ import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booki import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; import getBookingInfo from "@calcom/features/bookings/lib/getBookingInfo"; import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking"; -import * as newBookingMethods from "@calcom/features/bookings/lib/handleNewBooking"; import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment"; import { getTeamMemberEmailForResponseOrContactUsingUrlQuery } from "@calcom/features/ee/teams/lib/getTeamMemberEmailFromCrm"; import { @@ -11,7 +10,6 @@ import { } from "@calcom/features/ee/workflows/lib/reminders/verifyPhoneNumber"; import { handleCreatePhoneCall } from "@calcom/features/handleCreatePhoneCall"; import handleMarkNoShow from "@calcom/features/handleMarkNoShow"; -import * as instantMeetingMethods from "@calcom/features/instant-meeting/handleInstantMeeting"; import { getRoutedUrl } from "@calcom/features/routing-forms/lib/getRoutedUrl"; import getAllUserBookings from "@calcom/lib/bookings/getAllUserBookings"; import { symmetricEncrypt, symmetricDecrypt } from "@calcom/lib/crypto"; @@ -41,16 +39,9 @@ export { export { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents"; -const handleNewBooking = newBookingMethods.default; -export { handleNewBooking }; -const handleInstantMeeting = instantMeetingMethods.default; -export { handleInstantMeeting }; - export { handleMarkNoShow }; export { handleCreatePhoneCall }; -export { handleNewRecurringBooking } from "@calcom/features/bookings/lib/handleNewRecurringBooking"; - export { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/lib/getConnectedDestinationCalendars"; export { getBusyCalendarTimes } from "@calcom/features/calendars/lib/CalendarManager"; From d26282c7fcd494426136692d05f43b8d05133be4 Mon Sep 17 00:00:00 2001 From: Volnei Munhoz Date: Thu, 9 Oct 2025 14:12:56 -0300 Subject: [PATCH 2/2] Fix imports --- .../features/bookings/lib/handleNewBooking.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 2a1ff1a9eb4e8a..9f7605f2e8f3a8 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,10 +1,12 @@ import short, { uuid } from "short-uuid"; import { v5 as uuidv5 } from "uuid"; -import type { PlatformParams } from "@calcom/features/bookings/lib/dto/types"; + import processExternalId from "@calcom/app-store/_utils/calendars/processExternalId"; import { getPaymentAppData } from "@calcom/app-store/_utils/payments/getPaymentAppData"; -import { getFirstDelegationConferencingCredentialAppLocation } from "@calcom/app-store/delegationCredential"; -import { enrichHostsWithDelegationCredentials } from "@calcom/app-store/delegationCredential"; +import { + enrichHostsWithDelegationCredentials, + getFirstDelegationConferencingCredentialAppLocation, +} from "@calcom/app-store/delegationCredential"; import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata"; import { getLocationValueForDB, @@ -32,9 +34,6 @@ import { handlePayment } from "@calcom/features/bookings/lib/handlePayment"; import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger"; import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled"; import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache"; -import { getCheckBookingAndDurationLimitsService } from "@calcom/features/di/containers/BookingLimits"; -import { getCacheService } from "@calcom/features/di/containers/Cache"; -import { getLuckyUserService } from "@calcom/features/di/containers/LuckyUser"; import AssignmentReasonRecorder from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming"; @@ -53,10 +52,6 @@ import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { groupHostsByGroupId } from "@calcom/lib/bookings/hostGroupUtils"; import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils"; import { DEFAULT_GROUP_ID } from "@calcom/lib/constants"; -import { - enrichHostsWithDelegationCredentials, - getFirstDelegationConferencingCredentialAppLocation, -} from "@calcom/lib/delegationCredential/server"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; @@ -68,7 +63,6 @@ import type { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/serve import logger from "@calcom/lib/logger"; import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; -import type { LuckyUserService } from "@calcom/lib/server/getLuckyUser"; import { getTranslation } from "@calcom/lib/server/i18n"; import type { PrismaAttributeRepository as AttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository"; import type { BookingRepository } from "@calcom/lib/server/repository/booking"; @@ -105,6 +99,7 @@ import { BookingActionMap, BookingEmailSmsHandler } from "./BookingEmailSmsHandl import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials"; import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCredentials"; import getBookingDataSchema from "./getBookingDataSchema"; +import { LuckyUserService } from "./getLuckyUser"; import { addVideoCallDataToEvent } from "./handleNewBooking/addVideoCallDataToEvent"; import { checkActiveBookingsLimitForBooker } from "./handleNewBooking/checkActiveBookingsLimitForBooker"; import { checkIfBookerEmailIsBlocked } from "./handleNewBooking/checkIfBookerEmailIsBlocked";