diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index faffb6192c..ce0adc2d5d 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -38,7 +38,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.268", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.271", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/api/v2/src/lib/modules/available-slots.module.ts b/apps/api/v2/src/lib/modules/available-slots.module.ts index c1f55f55f3..0eb223fc73 100644 --- a/apps/api/v2/src/lib/modules/available-slots.module.ts +++ b/apps/api/v2/src/lib/modules/available-slots.module.ts @@ -7,6 +7,7 @@ import { PrismaSelectedSlotRepository } from "@/lib/repositories/prisma-selected import { PrismaTeamRepository } from "@/lib/repositories/prisma-team.repository"; import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository"; import { AvailableSlotsService } from "@/lib/services/available-slots.service"; +import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { Module } from "@nestjs/common"; @@ -21,6 +22,7 @@ import { Module } from "@nestjs/common"; PrismaEventTypeRepository, PrismaRoutingFormResponseRepository, PrismaTeamRepository, + CheckBookingLimitsService, AvailableSlotsService, ], exports: [AvailableSlotsService], diff --git a/apps/api/v2/src/lib/services/available-slots.service.ts b/apps/api/v2/src/lib/services/available-slots.service.ts index 7f6ce1725c..df2850364d 100644 --- a/apps/api/v2/src/lib/services/available-slots.service.ts +++ b/apps/api/v2/src/lib/services/available-slots.service.ts @@ -6,6 +6,7 @@ import { PrismaScheduleRepository } from "@/lib/repositories/prisma-schedule.rep import { PrismaSelectedSlotRepository } from "@/lib/repositories/prisma-selected-slot.repository"; import { PrismaTeamRepository } from "@/lib/repositories/prisma-team.repository"; import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository"; +import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; import { Injectable } from "@nestjs/common"; import { AvailableSlotsService as BaseAvailableSlotsService } from "@calcom/platform-libraries/slots"; @@ -31,6 +32,7 @@ export class AvailableSlotsService extends BaseAvailableSlotsService { selectedSlotRepo: selectedSlotRepository, eventTypeRepo: eventTypeRepository, userRepo: userRepository, + checkBookingLimitsService: new CheckBookingLimitsService(bookingRepository), }); } } diff --git a/apps/api/v2/src/lib/services/check-booking-and-duration-limits.service.ts b/apps/api/v2/src/lib/services/check-booking-and-duration-limits.service.ts new file mode 100644 index 0000000000..8ac610ceaa --- /dev/null +++ b/apps/api/v2/src/lib/services/check-booking-and-duration-limits.service.ts @@ -0,0 +1,13 @@ +import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; +import { Injectable } from "@nestjs/common"; + +import { CheckBookingAndDurationLimitsService as BaseCheckBookingAndDurationLimitsService } from "@calcom/platform-libraries/bookings"; + +@Injectable() +export class CheckBookingAndDurationLimitsService extends BaseCheckBookingAndDurationLimitsService { + constructor(checkBookingLimitsService: CheckBookingLimitsService) { + super({ + checkBookingLimitsService: checkBookingLimitsService, + }); + } +} diff --git a/apps/api/v2/src/lib/services/check-booking-limits.service.ts b/apps/api/v2/src/lib/services/check-booking-limits.service.ts new file mode 100644 index 0000000000..2e034149b7 --- /dev/null +++ b/apps/api/v2/src/lib/services/check-booking-limits.service.ts @@ -0,0 +1,13 @@ +import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository"; +import { Injectable } from "@nestjs/common"; + +import { CheckBookingLimitsService as BaseCheckBookingLimitsService } from "@calcom/platform-libraries/bookings"; + +@Injectable() +export class CheckBookingLimitsService extends BaseCheckBookingLimitsService { + constructor(bookingRepository: PrismaBookingRepository) { + super({ + bookingRepo: bookingRepository, + }); + } +} diff --git a/apps/api/v2/tsconfig.json b/apps/api/v2/tsconfig.json index b4eb8fc148..8460e8e22c 100644 --- a/apps/api/v2/tsconfig.json +++ b/apps/api/v2/tsconfig.json @@ -26,7 +26,8 @@ "@calcom/platform-libraries/app-store": ["../../../packages/platform/libraries/app-store.ts"], "@calcom/platform-libraries/workflows": ["../../../packages/platform/libraries/workflows.ts"], "@calcom/platform-libraries/conferencing": ["../../../packages/platform/libraries/conferencing.ts"], - "@calcom/platform-libraries/repositories": ["../../../packages/platform/libraries/repositories.ts"] + "@calcom/platform-libraries/repositories": ["../../../packages/platform/libraries/repositories.ts"], + "@calcom/platform-libraries/bookings": ["../../../packages/platform/libraries/bookings.ts"] }, "incremental": true, "skipLibCheck": true, diff --git a/apps/web/test/lib/checkBookingLimits.test.ts b/apps/web/test/lib/checkBookingLimits.test.ts index f7061da0a6..24b2bfa5bc 100644 --- a/apps/web/test/lib/checkBookingLimits.test.ts +++ b/apps/web/test/lib/checkBookingLimits.test.ts @@ -3,8 +3,8 @@ import prismaMock from "../../../../tests/libs/__mocks__/prismaMock"; import { describe, expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; +import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/booking-limits"; import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema"; -import { checkBookingLimits, checkBookingLimit } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder"; type Mockdata = { @@ -21,25 +21,27 @@ const MOCK_DATA: Mockdata = { }, }; +const checkBookingLimitsService = getCheckBookingLimitsService(); + describe("Check Booking Limits Tests", () => { it("Should return no errors", async () => { prismaMock.booking.count.mockResolvedValue(0); expect( - checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id) + checkBookingLimitsService.checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id) ).resolves.toBeTruthy(); }); it("Should throw an error", async () => { // Mock there being two a day prismaMock.booking.count.mockResolvedValue(2); expect( - checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id) + checkBookingLimitsService.checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id) ).rejects.toThrowError(); }); it("Should pass with multiple booking limits", async () => { prismaMock.booking.count.mockResolvedValue(0); expect( - checkBookingLimits( + checkBookingLimitsService.checkBookingLimits( { PER_DAY: 1, PER_WEEK: 2, @@ -51,8 +53,9 @@ describe("Check Booking Limits Tests", () => { }); it("Should pass with multiple booking limits with one undefined", async () => { prismaMock.booking.count.mockResolvedValue(0); + expect( - checkBookingLimits( + checkBookingLimitsService.checkBookingLimits( { PER_DAY: 1, PER_WEEK: undefined, @@ -65,7 +68,7 @@ describe("Check Booking Limits Tests", () => { it("Should handle multiple limits correctly", async () => { prismaMock.booking.count.mockResolvedValue(1); expect( - checkBookingLimit({ + checkBookingLimitsService.checkBookingLimit({ key: "PER_DAY", limitingNumber: 2, eventStartDate: MOCK_DATA.startDate, @@ -74,7 +77,7 @@ describe("Check Booking Limits Tests", () => { ).resolves.not.toThrow(); prismaMock.booking.count.mockResolvedValue(3); expect( - checkBookingLimit({ + checkBookingLimitsService.checkBookingLimit({ key: "PER_WEEK", limitingNumber: 2, eventStartDate: MOCK_DATA.startDate, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index e14825844c..109028e358 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -51,6 +51,7 @@ import { enrichHostsWithDelegationCredentials, getFirstDelegationConferencingCredentialAppLocation, } from "@calcom/lib/delegationCredential/server"; +import { getCheckBookingAndDurationLimitsService } from "@calcom/lib/di/containers/booking-limits"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { getEventName, updateHostInEventName } from "@calcom/lib/event"; @@ -90,7 +91,6 @@ import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCr import getBookingDataSchema from "./getBookingDataSchema"; import { addVideoCallDataToEvent } from "./handleNewBooking/addVideoCallDataToEvent"; import { checkActiveBookingsLimitForBooker } from "./handleNewBooking/checkActiveBookingsLimitForBooker"; -import { checkBookingAndDurationLimits } from "./handleNewBooking/checkBookingAndDurationLimits"; import { checkIfBookerEmailIsBlocked } from "./handleNewBooking/checkIfBookerEmailIsBlocked"; import { createBooking } from "./handleNewBooking/createBooking"; import type { Booking } from "./handleNewBooking/createBooking"; @@ -650,7 +650,8 @@ async function handler( location, }); - await checkBookingAndDurationLimits({ + const checkBookingAndDurationLimitsService = getCheckBookingAndDurationLimitsService(); + await checkBookingAndDurationLimitsService.checkBookingAndDurationLimits({ eventType, reqBodyStart: reqBody.start, reqBodyRescheduleUid: reqBody.rescheduleUid, diff --git a/packages/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits.ts b/packages/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits.ts index 7963c3b5f6..fc7775d1be 100644 --- a/packages/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits.ts +++ b/packages/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits.ts @@ -1,6 +1,6 @@ import dayjs from "@calcom/dayjs"; import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema"; -import { checkBookingLimits } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; +import type { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; import { checkDurationLimits } from "@calcom/lib/intervalLimits/server/checkDurationLimits"; import { withReporting } from "@calcom/lib/sentryWrapper"; @@ -14,37 +14,41 @@ type InputProps = { reqBodyRescheduleUid?: string; }; -const _checkBookingAndDurationLimits = async ({ - eventType, - reqBodyStart, - reqBodyRescheduleUid, -}: InputProps) => { - if ( - Object.prototype.hasOwnProperty.call(eventType, "bookingLimits") || - Object.prototype.hasOwnProperty.call(eventType, "durationLimits") - ) { - const startAsDate = dayjs(reqBodyStart).toDate(); - if (eventType.bookingLimits && Object.keys(eventType.bookingLimits).length > 0) { - await checkBookingLimits( - eventType.bookingLimits as IntervalLimit, - startAsDate, - eventType.id, - reqBodyRescheduleUid, - eventType.schedule?.timeZone - ); - } - if (eventType.durationLimits) { - await checkDurationLimits( - eventType.durationLimits as IntervalLimit, - startAsDate, - eventType.id, - reqBodyRescheduleUid - ); +export interface ICheckBookingAndDurationLimitsService { + checkBookingLimitsService: CheckBookingLimitsService; +} + +export class CheckBookingAndDurationLimitsService { + constructor(private readonly dependencies: ICheckBookingAndDurationLimitsService) {} + + checkBookingAndDurationLimits = withReporting( + this._checkBookingAndDurationLimits.bind(this), + "checkBookingAndDurationLimits" + ); + + async _checkBookingAndDurationLimits({ eventType, reqBodyStart, reqBodyRescheduleUid }: InputProps) { + if ( + Object.prototype.hasOwnProperty.call(eventType, "bookingLimits") || + Object.prototype.hasOwnProperty.call(eventType, "durationLimits") + ) { + const startAsDate = dayjs(reqBodyStart).toDate(); + if (eventType.bookingLimits && Object.keys(eventType.bookingLimits).length > 0) { + await this.dependencies.checkBookingLimitsService.checkBookingLimits( + eventType.bookingLimits as IntervalLimit, + startAsDate, + eventType.id, + reqBodyRescheduleUid, + eventType.schedule?.timeZone + ); + } + if (eventType.durationLimits) { + await checkDurationLimits( + eventType.durationLimits as IntervalLimit, + startAsDate, + eventType.id, + reqBodyRescheduleUid + ); + } } } -}; - -export const checkBookingAndDurationLimits = withReporting( - _checkBookingAndDurationLimits, - "checkBookingAndDurationLimits" -); +} diff --git a/packages/lib/di/containers/available-slots.ts b/packages/lib/di/containers/available-slots.ts index 927b042d66..d5b81d9b54 100644 --- a/packages/lib/di/containers/available-slots.ts +++ b/packages/lib/di/containers/available-slots.ts @@ -6,6 +6,7 @@ import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/s import { availableSlotsModule } from "../modules/available-slots"; import { bookingRepositoryModule } from "../modules/booking"; +import { checkBookingLimitsModule } from "../modules/check-booking-limits"; import { eventTypeRepositoryModule } from "../modules/eventType"; import { oooRepositoryModule } from "../modules/ooo"; import { routingFormResponseRepositoryModule } from "../modules/routingFormResponse"; @@ -24,6 +25,7 @@ container.load(DI_TOKENS.USER_REPOSITORY_MODULE, userRepositoryModule); container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); container.load(DI_TOKENS.EVENT_TYPE_REPOSITORY_MODULE, eventTypeRepositoryModule); container.load(DI_TOKENS.ROUTING_FORM_RESPONSE_REPOSITORY_MODULE, routingFormResponseRepositoryModule); +container.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule); container.load(DI_TOKENS.AVAILABLE_SLOTS_SERVICE_MODULE, availableSlotsModule); export function getAvailableSlotsService() { diff --git a/packages/lib/di/containers/booking-limits.ts b/packages/lib/di/containers/booking-limits.ts new file mode 100644 index 0000000000..3f22003c59 --- /dev/null +++ b/packages/lib/di/containers/booking-limits.ts @@ -0,0 +1,29 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { prismaModule } from "@calcom/prisma/prisma.module"; + +import type { CheckBookingAndDurationLimitsService } from "../../../features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; +import type { CheckBookingLimitsService } from "../../intervalLimits/server/checkBookingLimits"; +import { bookingRepositoryModule } from "../modules/booking"; +import { checkBookingAndDurationLimitsModule } from "../modules/check-booking-and-duration-limits"; +import { checkBookingLimitsModule } from "../modules/check-booking-limits"; + +const container = createContainer(); +container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); +container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); +container.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule); +container.load( + DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE, + checkBookingAndDurationLimitsModule +); + +export function getCheckBookingLimitsService() { + return container.get(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE); +} + +export function getCheckBookingAndDurationLimitsService() { + return container.get( + DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE + ); +} diff --git a/packages/lib/di/modules/available-slots.ts b/packages/lib/di/modules/available-slots.ts index 5c34f94f50..f893eb2469 100644 --- a/packages/lib/di/modules/available-slots.ts +++ b/packages/lib/di/modules/available-slots.ts @@ -15,4 +15,5 @@ availableSlotsModule.bind(DI_TOKENS.AVAILABLE_SLOTS_SERVICE).toClass(AvailableSl bookingRepo: DI_TOKENS.BOOKING_REPOSITORY, eventTypeRepo: DI_TOKENS.EVENT_TYPE_REPOSITORY, routingFormResponseRepo: DI_TOKENS.ROUTING_FORM_RESPONSE_REPOSITORY, + checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE, } satisfies Record); diff --git a/packages/lib/di/modules/check-booking-and-duration-limits.ts b/packages/lib/di/modules/check-booking-and-duration-limits.ts new file mode 100644 index 0000000000..d78a56d802 --- /dev/null +++ b/packages/lib/di/modules/check-booking-and-duration-limits.ts @@ -0,0 +1,13 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { ICheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; +import { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; + +import { DI_TOKENS } from "../tokens"; + +export const checkBookingAndDurationLimitsModule = createModule(); +checkBookingAndDurationLimitsModule + .bind(DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE) + .toClass(CheckBookingAndDurationLimitsService, { + checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE, + } satisfies Record); diff --git a/packages/lib/di/modules/check-booking-limits.ts b/packages/lib/di/modules/check-booking-limits.ts new file mode 100644 index 0000000000..fda897236f --- /dev/null +++ b/packages/lib/di/modules/check-booking-limits.ts @@ -0,0 +1,9 @@ +import { createModule } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; + +export const checkBookingLimitsModule = createModule(); +checkBookingLimitsModule + .bind(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE) + .toClass(CheckBookingLimitsService, { bookingRepo: DI_TOKENS.BOOKING_REPOSITORY }); diff --git a/packages/lib/di/tokens.ts b/packages/lib/di/tokens.ts index b3ba3d1d32..586c3dde23 100644 --- a/packages/lib/di/tokens.ts +++ b/packages/lib/di/tokens.ts @@ -22,4 +22,8 @@ export const DI_TOKENS = { AVAILABLE_SLOTS_SERVICE_MODULE: Symbol("AvailableSlotsModule"), INSIGHTS_ROUTING_SERVICE: Symbol("InsightsRoutingService"), INSIGHTS_ROUTING_SERVICE_MODULE: Symbol("InsightsRoutingServiceModule"), + CHECK_BOOKING_LIMITS_SERVICE: Symbol("CheckBookingLimitsService"), + CHECK_BOOKING_LIMITS_SERVICE_MODULE: Symbol("CheckBookingLimitsServiceModule"), + CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE: Symbol("CheckBookingAndDurationLimitsService"), + CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE: Symbol("CheckBookingAndDurationLimitsServiceModule"), }; diff --git a/packages/lib/intervalLimits/server/checkBookingLimits.ts b/packages/lib/intervalLimits/server/checkBookingLimits.ts index d9265e83c4..5b8ca77781 100644 --- a/packages/lib/intervalLimits/server/checkBookingLimits.ts +++ b/packages/lib/intervalLimits/server/checkBookingLimits.ts @@ -2,69 +2,73 @@ import dayjs from "@calcom/dayjs"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; import { withReporting } from "@calcom/lib/sentryWrapper"; -import { BookingRepository } from "@calcom/lib/server/repository/booking"; -import prisma from "@calcom/prisma"; -import { BookingStatus } from "@calcom/prisma/enums"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; import { ascendingLimitKeys, intervalLimitKeyToUnit } from "../intervalLimit"; import type { IntervalLimit, IntervalLimitKey } from "../intervalLimitSchema"; import { parseBookingLimit } from "../isBookingLimits"; -async function _checkBookingLimits( - bookingLimits: IntervalLimit, - eventStartDate: Date, - eventId: number, - rescheduleUid?: string | undefined, - timeZone?: string | null, - includeManagedEvents?: boolean -) { - const parsedBookingLimits = parseBookingLimit(bookingLimits); - if (!parsedBookingLimits) return false; - - // not iterating entries to preserve types - const limitCalculations = ascendingLimitKeys.map((key) => - checkBookingLimit({ - key, - limitingNumber: parsedBookingLimits[key], - eventStartDate, - eventId, - timeZone, - rescheduleUid, - includeManagedEvents, - }) - ); - - try { - return !!(await Promise.all(limitCalculations)); - } catch (error) { - throw new HttpError({ message: getErrorFromUnknown(error).message, statusCode: 401 }); - } +export interface ICheckBookingLimitsService { + bookingRepo: BookingRepository; } -export const checkBookingLimits = withReporting(_checkBookingLimits, "checkBookingLimits"); +export class CheckBookingLimitsService { + constructor(private readonly dependencies: ICheckBookingLimitsService) {} -async function _checkBookingLimit({ - eventStartDate, - eventId, - key, - limitingNumber, - rescheduleUid, - timeZone, - teamId, - user, - includeManagedEvents = false, -}: { - eventStartDate: Date; - eventId?: number; - key: IntervalLimitKey; - limitingNumber: number | undefined; - rescheduleUid?: string | undefined; - timeZone?: string | null; - teamId?: number; - user?: { id: number; email: string }; - includeManagedEvents?: boolean; -}) { - { + private async _checkBookingLimits( + bookingLimits: IntervalLimit, + eventStartDate: Date, + eventId: number, + rescheduleUid?: string | undefined, + timeZone?: string | null, + includeManagedEvents?: boolean + ) { + const parsedBookingLimits = parseBookingLimit(bookingLimits); + if (!parsedBookingLimits) return false; + + // not iterating entries to preserve types + const limitCalculations = ascendingLimitKeys.map((key) => + this.checkBookingLimit({ + key, + limitingNumber: parsedBookingLimits[key], + eventStartDate, + eventId, + timeZone, + rescheduleUid, + includeManagedEvents, + }) + ); + + try { + return !!(await Promise.all(limitCalculations)); + } catch (error) { + throw new HttpError({ message: getErrorFromUnknown(error).message, statusCode: 401 }); + } + } + + checkBookingLimits = withReporting(this._checkBookingLimits.bind(this), "checkBookingLimits"); + + private async _checkBookingLimit({ + eventStartDate, + eventId, + key, + limitingNumber, + rescheduleUid, + timeZone, + teamId, + user, + includeManagedEvents = false, + }: { + eventStartDate: Date; + eventId?: number; + key: IntervalLimitKey; + limitingNumber: number | undefined; + rescheduleUid?: string | undefined; + timeZone?: string | null; + teamId?: number; + user?: { id: number; email: string }; + includeManagedEvents?: boolean; + }) { const eventDateInOrganizerTz = timeZone ? dayjs(eventStartDate).tz(timeZone) : dayjs(eventStartDate); if (!limitingNumber) return; @@ -77,8 +81,7 @@ async function _checkBookingLimit({ let bookingsInPeriod; if (teamId && user) { - const bookingRepo = new BookingRepository(prisma); - bookingsInPeriod = await bookingRepo.getAllAcceptedTeamBookingsOfUser({ + bookingsInPeriod = await this.dependencies.bookingRepo.getAllAcceptedTeamBookingsOfUser({ user: { id: user.id, email: user.email }, teamId, startDate: startDate, @@ -88,21 +91,11 @@ async function _checkBookingLimit({ includeManagedEvents, }); } else { - bookingsInPeriod = await prisma.booking.count({ - where: { - status: BookingStatus.ACCEPTED, - eventTypeId: eventId, - // FIXME: bookings that overlap on one side will never be counted - startTime: { - gte: startDate, - }, - endTime: { - lte: endDate, - }, - uid: { - not: rescheduleUid, - }, - }, + bookingsInPeriod = await this.dependencies.bookingRepo.countBookingsByEventTypeAndDateRange({ + eventTypeId: eventId!, + startDate, + endDate, + excludedUid: rescheduleUid, }); } @@ -113,6 +106,6 @@ async function _checkBookingLimit({ statusCode: 403, }); } -} -export const checkBookingLimit = withReporting(_checkBookingLimit, "checkBookingLimit"); + checkBookingLimit = withReporting(this._checkBookingLimit.bind(this), "checkBookingLimit"); +} diff --git a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts index a3fecdbe99..c6d84a2bd2 100644 --- a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts +++ b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts @@ -1,5 +1,6 @@ import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; +import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/booking-limits"; import { getStartEndDateforLimitCheck } from "@calcom/lib/getBusyTimes"; import type { EventType } from "@calcom/lib/getUserAvailability"; import { getPeriodStartDatesBetween } from "@calcom/lib/getUserAvailability"; @@ -14,7 +15,6 @@ import { descendingLimitKeys, intervalLimitKeyToUnit } from "../intervalLimit"; import type { IntervalLimit } from "../intervalLimitSchema"; import LimitManager from "../limitManager"; import { isBookingWithinPeriod } from "../utils"; -import { checkBookingLimit } from "./checkBookingLimits"; const _getBusyTimesFromLimits = async ( bookingLimits: IntervalLimit | null, @@ -113,7 +113,8 @@ const _getBusyTimesFromBookingLimits = async (params: { // special handling of yearly limits to improve performance if (unit === "year") { try { - await checkBookingLimit({ + const checkBookingLimitsService = getCheckBookingLimitsService(); + await checkBookingLimitsService.checkBookingLimit({ eventStartDate: periodStart.toDate(), limitingNumber: limit, eventId: eventTypeId, diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 861f323bc9..d70d5aeea3 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -837,4 +837,32 @@ export class BookingRepository { }, }); } + + async countBookingsByEventTypeAndDateRange({ + eventTypeId, + startDate, + endDate, + excludedUid, + }: { + eventTypeId: number; + startDate: Date; + endDate: Date; + excludedUid?: string; + }) { + return await this.prismaClient.booking.count({ + where: { + status: BookingStatus.ACCEPTED, + eventTypeId, + startTime: { + gte: startDate, + }, + endTime: { + lte: endDate, + }, + uid: { + not: excludedUid, + }, + }, + }); + } } diff --git a/packages/platform/libraries/bookings.ts b/packages/platform/libraries/bookings.ts new file mode 100644 index 0000000000..e63970f7a9 --- /dev/null +++ b/packages/platform/libraries/bookings.ts @@ -0,0 +1,2 @@ +export { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; +export { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; diff --git a/packages/platform/libraries/package.json b/packages/platform/libraries/package.json index 6bf5fac7b3..bdd6c88926 100644 --- a/packages/platform/libraries/package.json +++ b/packages/platform/libraries/package.json @@ -73,6 +73,11 @@ "import": "./dist/repositories.js", "require": "./dist/repositories.cjs", "types": "./dist/repositories.d.ts" + }, + "./bookings": { + "import": "./dist/bookings.js", + "require": "./dist/bookings.cjs", + "types": "./dist/bookings.d.ts" } }, "typesVersions": { @@ -100,6 +105,9 @@ ], "repositories": [ "dist/repositories.d.ts" + ], + "bookings": [ + "dist/bookings.d.ts" ] } } diff --git a/packages/platform/libraries/vite.config.js b/packages/platform/libraries/vite.config.js index 42093a2126..07b83d36eb 100644 --- a/packages/platform/libraries/vite.config.js +++ b/packages/platform/libraries/vite.config.js @@ -26,6 +26,7 @@ export default defineConfig({ slots: resolve(__dirname, "./slots.ts"), conferencing: resolve(__dirname, "./conferencing.ts"), repositories: resolve(__dirname, "./repositories.ts"), + bookings: resolve(__dirname, "./bookings.ts"), }, name: "calcom-lib", fileName: "calcom-lib", diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index ed5d4a2354..a7d1ae2988 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -29,7 +29,7 @@ import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSche import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits"; import { parseDurationLimit } from "@calcom/lib/intervalLimits/isDurationLimits"; import LimitManager from "@calcom/lib/intervalLimits/limitManager"; -import { checkBookingLimit } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; +import type { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits"; import { isBookingWithinPeriod } from "@calcom/lib/intervalLimits/utils"; import { calculatePeriodLimits, @@ -99,10 +99,11 @@ export interface IAvailableSlotsService { bookingRepo: BookingRepository; eventTypeRepo: EventTypeRepository; routingFormResponseRepo: RoutingFormResponseRepository; + checkBookingLimitsService: CheckBookingLimitsService; } export class AvailableSlotsService { - constructor(private readonly dependencies: IAvailableSlotsService) {} + constructor(public readonly dependencies: IAvailableSlotsService) {} private async _getReservedSlotsAndCleanupExpired({ bookerClientUid, @@ -397,8 +398,7 @@ export class AvailableSlotsService { if (unit === "year") { try { - // TODO: DI checkBookingLimit - await checkBookingLimit({ + await this.dependencies.checkBookingLimitsService.checkBookingLimit({ eventStartDate: periodStart.toDate(), limitingNumber: limit, eventId: eventType.id, @@ -597,8 +597,7 @@ export class AvailableSlotsService { if (unit === "year") { try { - // TODO: DI checkBookingLimit - await checkBookingLimit({ + await this.dependencies.checkBookingLimitsService.checkBookingLimit({ eventStartDate: periodStart.toDate(), limitingNumber: limit, key, diff --git a/yarn.lock b/yarn.lock index 6a3bea1de4..029776ab5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2519,7 +2519,7 @@ __metadata: "@axiomhq/winston": ^1.2.0 "@calcom/platform-constants": "*" "@calcom/platform-enums": "*" - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.268" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.271" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -3575,13 +3575,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.268": - version: 0.0.268 - resolution: "@calcom/platform-libraries@npm:0.0.268" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.271": + version: 0.0.271 + resolution: "@calcom/platform-libraries@npm:0.0.271" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: d361c067dd33e3807c3ba965b3582eadaeec2ef1e2a3114c68e0c2ab1d7149fcca2c72ce00f8f3f341bdf3caeca578898d3345d8615c58da3a66e541e62d2ca5 + checksum: 5b55a05c5cd1d471fe36c90b8b2f16e70f6155065b3b13e12c06bde73f3ceffde74de3921c3a9fe05d04eed7fb587836ac79a46a521d35fe83812d66b13d8733 languageName: node linkType: hard