From 29314cde752bf134c6c15e5b04a21e3005cfbfc2 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:14:08 +0300 Subject: [PATCH] refactor: convert getBusyTimes to service class with dependency injection (#22949) * refactor: convert getBusyTimes to service class with dependency injection - Create BusyTimesService following UserAvailabilityService pattern - Add DI tokens, module, and container setup for BusyTimesService - Update all usage locations to use service instead of direct function calls - Maintain existing function signatures for backward compatibility - Add legacy exports to ensure smooth transition - Update type references in trpc util to use service prototype - Fix linting issues by making legacy exports async with proper imports - All tests pass and type checking succeeds Co-Authored-By: morgan@cal.com * refactor: busy time service * fix: api v2 eslint plugins version mismatch * chore: bump platform libs * chore: bump platform libs * fix: missing di busyTimesModule in slots service --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> --- apps/api/v2/package.json | 4 +- .../src/lib/modules/available-slots.module.ts | 6 +- .../lib/services/available-slots.service.ts | 2 + .../v2/src/lib/services/busy-times.service.ts | 13 + .../handleNewBooking/ensureAvailableUsers.ts | 13 +- packages/lib/di/containers/available-slots.ts | 7 +- packages/lib/di/containers/busy-times.ts | 17 + .../di/containers/get-user-availability.ts | 11 +- packages/lib/di/modules/available-slots.ts | 3 +- packages/lib/di/modules/busy-times.ts | 10 + .../lib/di/modules/get-user-availability.ts | 3 +- packages/lib/di/tokens.ts | 2 + packages/lib/getBusyTimes.test.ts | 11 +- packages/lib/getBusyTimes.ts | 668 +++++++++--------- packages/lib/getUserAvailability.ts | 5 +- .../server/getBusyTimesFromLimits.ts | 5 +- packages/platform/libraries/slots.ts | 3 + .../trpc/server/routers/viewer/slots/util.ts | 35 +- yarn.lock | 99 ++- 19 files changed, 533 insertions(+), 384 deletions(-) create mode 100644 apps/api/v2/src/lib/services/busy-times.service.ts create mode 100644 packages/lib/di/containers/busy-times.ts create mode 100644 packages/lib/di/modules/busy-times.ts diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index fb81e0ed67..c7d8abaab2 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.288", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.291", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", @@ -98,6 +98,8 @@ "@types/luxon": "^3.3.7", "@types/passport-jwt": "^3.0.13", "@types/supertest": "^2.0.12", + "@typescript-eslint/eslint-plugin": "^6", + "@typescript-eslint/parser": "^6", "jest": "^29.7.0", "jest-date-mock": "^1.0.10", "node-mocks-http": "^1.16.2", 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 d2cc48376d..ea2284b5f6 100644 --- a/apps/api/v2/src/lib/modules/available-slots.module.ts +++ b/apps/api/v2/src/lib/modules/available-slots.module.ts @@ -8,12 +8,13 @@ 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 { BusyTimesService } from "@/lib/services/busy-times.service"; import { CacheService } from "@/lib/services/cache.service"; import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; +import { UserAvailabilityService } from "@/lib/services/user-availability.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { RedisService } from "@/modules/redis/redis.service"; import { Module } from "@nestjs/common"; -import { UserAvailabilityService } from "@/lib/services/user-availability.service"; @Module({ imports: [PrismaModule], @@ -31,7 +32,8 @@ import { UserAvailabilityService } from "@/lib/services/user-availability.servic CheckBookingLimitsService, CacheService, AvailableSlotsService, - UserAvailabilityService + UserAvailabilityService, + BusyTimesService, ], 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 e9ec45e4aa..474a9acfc9 100644 --- a/apps/api/v2/src/lib/services/available-slots.service.ts +++ b/apps/api/v2/src/lib/services/available-slots.service.ts @@ -7,6 +7,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 { BusyTimesService } from "@/lib/services/busy-times.service"; import { CacheService } from "@/lib/services/cache.service"; import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service"; import { RedisService } from "@/modules/redis/redis.service"; @@ -48,6 +49,7 @@ export class AvailableSlotsService extends BaseAvailableSlotsService { eventTypeRepository, redisService ), + busyTimesService: new BusyTimesService(bookingRepository), }); } } diff --git a/apps/api/v2/src/lib/services/busy-times.service.ts b/apps/api/v2/src/lib/services/busy-times.service.ts new file mode 100644 index 0000000000..0f862e6ac3 --- /dev/null +++ b/apps/api/v2/src/lib/services/busy-times.service.ts @@ -0,0 +1,13 @@ +import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository"; +import { Injectable } from "@nestjs/common"; + +import { BusyTimesService as BaseBusyTimesService } from "@calcom/platform-libraries/slots"; + +@Injectable() +export class BusyTimesService extends BaseBusyTimesService { + constructor(bookingRepository: PrismaBookingRepository) { + super({ + bookingRepo: bookingRepository, + }); + } +} diff --git a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts index 91ed291aa8..f0c571a8dc 100644 --- a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts @@ -4,9 +4,9 @@ import dayjs from "@calcom/dayjs"; import type { Dayjs } from "@calcom/dayjs"; import { checkForConflicts } from "@calcom/features/bookings/lib/conflictChecker/checkForConflicts"; import { buildDateRanges } from "@calcom/lib/date-ranges"; -import { ErrorCode } from "@calcom/lib/errorCodes"; -import { getBusyTimesForLimitChecks } from "@calcom/lib/getBusyTimes"; +import { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability"; +import { ErrorCode } from "@calcom/lib/errorCodes"; import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits"; import { parseDurationLimit } from "@calcom/lib/intervalLimits/isDurationLimits"; import { getPiiFreeUser } from "@calcom/lib/piiFreeData"; @@ -62,7 +62,7 @@ const _ensureAvailableUsers = async ( shouldServeCache?: boolean // ReturnType hint of at least one IsFixedAwareUser, as it's made sure at least one entry exists ): Promise<[IsFixedAwareUser, ...IsFixedAwareUser[]]> => { - const userAvailabilityService = getUserAvailabilityService() + const userAvailabilityService = getUserAvailabilityService(); const availableUsers: IsFixedAwareUser[] = []; const startDateTimeUtc = getDateTimeInUtc(input.dateFrom, input.timeZone); @@ -74,9 +74,12 @@ const _ensureAvailableUsers = async ( const bookingLimits = parseBookingLimit(eventType?.bookingLimits); const durationLimits = parseDurationLimit(eventType?.durationLimits); - const busyTimesFromLimitsBookingsAllUsers: Awaited> = + const busyTimesService = getBusyTimesService(); + const busyTimesFromLimitsBookingsAllUsers: Awaited< + ReturnType + > = eventType && (bookingLimits || durationLimits) - ? await getBusyTimesForLimitChecks({ + ? await busyTimesService.getBusyTimesForLimitChecks({ userIds: eventType.users.map((u) => u.id), eventTypeId: eventType.id, startDate: startDateTimeUtc.format(), diff --git a/packages/lib/di/containers/available-slots.ts b/packages/lib/di/containers/available-slots.ts index 6dc67cb874..8ebdba18e6 100644 --- a/packages/lib/di/containers/available-slots.ts +++ b/packages/lib/di/containers/available-slots.ts @@ -7,17 +7,18 @@ import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/s import { availableSlotsModule } from "../modules/available-slots"; import { bookingRepositoryModule } from "../modules/booking"; +import { busyTimesModule } from "../modules/busy-times"; import { cacheModule } from "../modules/cache"; import { checkBookingLimitsModule } from "../modules/check-booking-limits"; import { eventTypeRepositoryModule } from "../modules/eventType"; import { featuresRepositoryModule } from "../modules/features"; +import { getUserAvailabilityModule } from "../modules/get-user-availability"; import { oooRepositoryModule } from "../modules/ooo"; import { routingFormResponseRepositoryModule } from "../modules/routingFormResponse"; import { scheduleRepositoryModule } from "../modules/schedule"; import { selectedSlotsRepositoryModule } from "../modules/selectedSlots"; import { teamRepositoryModule } from "../modules/team"; import { userRepositoryModule } from "../modules/user"; -import { getUserAvailabilityModule } from "../modules/get-user-availability"; const container = createContainer(); container.load(DI_TOKENS.REDIS_CLIENT, redisModule); @@ -34,7 +35,9 @@ container.load(DI_TOKENS.FEATURES_REPOSITORY_MODULE, featuresRepositoryModule); container.load(DI_TOKENS.CACHE_SERVICE_MODULE, cacheModule); container.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule); container.load(DI_TOKENS.AVAILABLE_SLOTS_SERVICE_MODULE, availableSlotsModule); -container.load(DI_TOKENS.GET_USER_AVAILABILITY_SERVICE_MODULE, getUserAvailabilityModule) +container.load(DI_TOKENS.GET_USER_AVAILABILITY_SERVICE_MODULE, getUserAvailabilityModule); +container.load(DI_TOKENS.BUSY_TIMES_SERVICE_MODULE, busyTimesModule); + export function getAvailableSlotsService() { return container.get(DI_TOKENS.AVAILABLE_SLOTS_SERVICE); } diff --git a/packages/lib/di/containers/busy-times.ts b/packages/lib/di/containers/busy-times.ts new file mode 100644 index 0000000000..11b659d5a9 --- /dev/null +++ b/packages/lib/di/containers/busy-times.ts @@ -0,0 +1,17 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { prismaModule } from "@calcom/prisma/prisma.module"; + +import type { BusyTimesService } from "../../getBusyTimes"; +import { bookingRepositoryModule } from "../modules/booking"; +import { busyTimesModule } from "../modules/busy-times"; + +const container = createContainer(); +container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); +container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); +container.load(DI_TOKENS.BUSY_TIMES_SERVICE_MODULE, busyTimesModule); + +export function getBusyTimesService() { + return container.get(DI_TOKENS.BUSY_TIMES_SERVICE); +} diff --git a/packages/lib/di/containers/get-user-availability.ts b/packages/lib/di/containers/get-user-availability.ts index 5faeed3699..29d1de1852 100644 --- a/packages/lib/di/containers/get-user-availability.ts +++ b/packages/lib/di/containers/get-user-availability.ts @@ -1,16 +1,15 @@ import { createContainer } from "@evyweb/ioctopus"; +import { redisModule } from "@calcom/features/redis/di/redisModule"; import { DI_TOKENS } from "@calcom/lib/di/tokens"; import { prismaModule } from "@calcom/prisma/prisma.module"; -import { getUserAvailabilityModule } from "../modules/get-user-availability"; +import type { UserAvailabilityService } from "../../getUserAvailability"; import { bookingRepositoryModule } from "../modules/booking"; - +import { busyTimesModule } from "../modules/busy-times"; import { eventTypeRepositoryModule } from "../modules/eventType"; -import { redisModule } from "@calcom/features/redis/di/redisModule"; - +import { getUserAvailabilityModule } from "../modules/get-user-availability"; import { oooRepositoryModule } from "../modules/ooo"; -import { UserAvailabilityService } from "../../getUserAvailability"; const container = createContainer(); container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); @@ -18,9 +17,9 @@ container.load(DI_TOKENS.OOO_REPOSITORY_MODULE, oooRepositoryModule); container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); container.load(DI_TOKENS.EVENT_TYPE_REPOSITORY_MODULE, eventTypeRepositoryModule); container.load(DI_TOKENS.GET_USER_AVAILABILITY_SERVICE_MODULE, getUserAvailabilityModule); +container.load(DI_TOKENS.BUSY_TIMES_SERVICE_MODULE, busyTimesModule); container.load(DI_TOKENS.REDIS_CLIENT, redisModule); - export function getUserAvailabilityService() { return container.get(DI_TOKENS.GET_USER_AVAILABILITY_SERVICE); } diff --git a/packages/lib/di/modules/available-slots.ts b/packages/lib/di/modules/available-slots.ts index 3c4a42a1ca..d7ffcb60a4 100644 --- a/packages/lib/di/modules/available-slots.ts +++ b/packages/lib/di/modules/available-slots.ts @@ -18,5 +18,6 @@ availableSlotsModule.bind(DI_TOKENS.AVAILABLE_SLOTS_SERVICE).toClass(AvailableSl redisClient: DI_TOKENS.REDIS_CLIENT, cacheService: DI_TOKENS.CACHE_SERVICE, checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE, - userAvailabilityService: DI_TOKENS.GET_USER_AVAILABILITY_SERVICE + userAvailabilityService: DI_TOKENS.GET_USER_AVAILABILITY_SERVICE, + busyTimesService: DI_TOKENS.BUSY_TIMES_SERVICE, } satisfies Record); diff --git a/packages/lib/di/modules/busy-times.ts b/packages/lib/di/modules/busy-times.ts new file mode 100644 index 0000000000..814d36e3cc --- /dev/null +++ b/packages/lib/di/modules/busy-times.ts @@ -0,0 +1,10 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { IBusyTimesService } from "../../getBusyTimes"; +import { BusyTimesService } from "../../getBusyTimes"; +import { DI_TOKENS } from "../tokens"; + +export const busyTimesModule = createModule(); +busyTimesModule.bind(DI_TOKENS.BUSY_TIMES_SERVICE).toClass(BusyTimesService, { + bookingRepo: DI_TOKENS.BOOKING_REPOSITORY, +} satisfies Record); diff --git a/packages/lib/di/modules/get-user-availability.ts b/packages/lib/di/modules/get-user-availability.ts index aac6e49987..5913cf2801 100644 --- a/packages/lib/di/modules/get-user-availability.ts +++ b/packages/lib/di/modules/get-user-availability.ts @@ -1,7 +1,8 @@ import { createModule } from "@evyweb/ioctopus"; +import type { IUserAvailabilityService } from "../../getUserAvailability"; +import { UserAvailabilityService } from "../../getUserAvailability"; import { DI_TOKENS } from "../tokens"; -import { IUserAvailabilityService, UserAvailabilityService } from "../../getUserAvailability"; export const getUserAvailabilityModule = createModule(); getUserAvailabilityModule.bind(DI_TOKENS.GET_USER_AVAILABILITY_SERVICE).toClass(UserAvailabilityService, { diff --git a/packages/lib/di/tokens.ts b/packages/lib/di/tokens.ts index 373aa3a4c7..2dc40117cc 100644 --- a/packages/lib/di/tokens.ts +++ b/packages/lib/di/tokens.ts @@ -35,4 +35,6 @@ export const DI_TOKENS = { CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE: Symbol("CheckBookingAndDurationLimitsServiceModule"), GET_USER_AVAILABILITY_SERVICE: Symbol("GetUserAvailabilityService"), GET_USER_AVAILABILITY_SERVICE_MODULE: Symbol("GetUserAvailabilityModule"), + BUSY_TIMES_SERVICE: Symbol("BusyTimesService"), + BUSY_TIMES_SERVICE_MODULE: Symbol("BusyTimesServiceModule"), }; diff --git a/packages/lib/getBusyTimes.test.ts b/packages/lib/getBusyTimes.test.ts index 0489e06359..bdb56290dd 100644 --- a/packages/lib/getBusyTimes.test.ts +++ b/packages/lib/getBusyTimes.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; -import { getBusyTimes } from "./getBusyTimes"; +import { getBusyTimesService } from "./di/containers/busy-times"; const startOfTomorrow = dayjs().add(1, "day").startOf("day"); const tomorrowDate = startOfTomorrow.format("YYYY-MM-DD"); @@ -58,7 +58,8 @@ const mockBookings = ({ describe("getBusyTimes", () => { it("blocks a regular time slot", async () => { - const busyTimes = await getBusyTimes({ + const busyTimesService = getBusyTimesService(); + const busyTimes = await busyTimesService.getBusyTimes({ credentials: [], userId: 1, userEmail: "exampleuser1@example.com", @@ -85,7 +86,8 @@ describe("getBusyTimes", () => { ]); }); it("should block before and after buffer times", async () => { - const busyTimes = await getBusyTimes({ + const busyTimesService = getBusyTimesService(); + const busyTimes = await busyTimesService.getBusyTimes({ credentials: [], userId: 1, userEmail: "exampleuser1@example.com", @@ -106,7 +108,8 @@ describe("getBusyTimes", () => { ]); }); it("should have busy times only if seated with remaining seats when buffers exist", async () => { - const busyTimes = await getBusyTimes({ + const busyTimesService = getBusyTimesService(); + const busyTimes = await busyTimesService.getBusyTimes({ credentials: [], userId: 1, eventTypeId: 1, diff --git a/packages/lib/getBusyTimes.ts b/packages/lib/getBusyTimes.ts index 2b316d51e5..2dbda02255 100644 --- a/packages/lib/getBusyTimes.ts +++ b/packages/lib/getBusyTimes.ts @@ -11,6 +11,7 @@ import logger from "@calcom/lib/logger"; import { getPiiFreeBooking } from "@calcom/lib/piiFreeData"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { performance } from "@calcom/lib/server/perfObserver"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; import prisma from "@calcom/prisma"; import type { SelectedCalendar } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -18,367 +19,374 @@ import type { EventBusyDetails } from "@calcom/types/Calendar"; import type { CredentialForCalendarService } from "@calcom/types/Credential"; import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes"; -import { BookingRepository } from "./server/repository/booking"; -const _getBusyTimes = async (params: { - credentials: CredentialForCalendarService[]; - userId: number; - userEmail: string; - username: string; - eventTypeId?: number; - startTime: string; - beforeEventBuffer?: number; - afterEventBuffer?: number; - endTime: string; - selectedCalendars: SelectedCalendar[]; - seatedEvent?: boolean; - rescheduleUid?: string | null; - duration?: number | null; - currentBookings?: - | (Pick & { - eventType: Pick< - EventType, - "id" | "beforeEventBuffer" | "afterEventBuffer" | "seatsPerTimeSlot" - > | null; - _count?: { - seatsReferences: number; - }; - })[] - | null; - bypassBusyCalendarTimes: boolean; - shouldServeCache?: boolean; -}) => { - const { - credentials, - userId, - userEmail, - username, - eventTypeId, - startTime, - endTime, - beforeEventBuffer, - afterEventBuffer, - selectedCalendars, - seatedEvent, - rescheduleUid, - duration, - bypassBusyCalendarTimes = false, - shouldServeCache, - } = params; +export interface IBusyTimesService { + bookingRepo: BookingRepository; +} - logger.silly( - `Checking Busy time from Cal Bookings in range ${startTime} to ${endTime} for input ${JSON.stringify({ - userId, - eventTypeId, - status: BookingStatus.ACCEPTED, - })}` - ); +export class BusyTimesService { + constructor(public readonly dependencies: IBusyTimesService) {} - /** - * A user is considered busy within a given time period if there - * is a booking they own OR attend. - * - * Performs a query for all bookings where: - * - The given booking is owned by this user, or.. - * - The current user has a different booking at this time he/she attends - * - * See further discussion within this GH issue: - * https://github.com/calcom/cal.com/issues/6374 - * - * NOTE: Changes here will likely require changes to some mocking - * logic within getSchedule.test.ts:addBookings - */ - performance.mark("prismaBookingGetStart"); - - const startTimeDate = - rescheduleUid && duration ? dayjs(startTime).subtract(duration, "minute").toDate() : new Date(startTime); - const endTimeDate = - rescheduleUid && duration ? dayjs(endTime).add(duration, "minute").toDate() : new Date(endTime); - - // to also get bookings that are outside of start and end time, but the buffer falls within the start and end time - const definedBufferTimes = getDefinedBufferTimes(); - const maxBuffer = definedBufferTimes[definedBufferTimes.length - 1]; - const startTimeAdjustedWithMaxBuffer = dayjs(startTimeDate).subtract(maxBuffer, "minute").toDate(); - const endTimeAdjustedWithMaxBuffer = dayjs(endTimeDate).add(maxBuffer, "minute").toDate(); - - // INFO: Refactored to allow this method to take in a list of current bookings for the user. - // Will keep support for retrieving a user's bookings if the caller does not already supply them. - // This function is called from multiple places but we aren't refactoring all of them at this moment - // to avoid potential side effects. - let bookings = params.currentBookings; - - if (!bookings) { - const bookingRepo = new BookingRepository(prisma); - bookings = await bookingRepo.findAllExistingBookingsForEventTypeBetween({ - userIdAndEmailMap: new Map([[userId, userEmail]]), - eventTypeId, - startDate: startTimeAdjustedWithMaxBuffer, - endDate: endTimeAdjustedWithMaxBuffer, - seatedEvent, - }); - } - - const bookingSeatCountMap: { [x: string]: number } = {}; - const busyTimes = bookings.reduce((aggregate: EventBusyDetails[], booking) => { - const { id, startTime, endTime, eventType, title, ...rest } = booking; - - const minutesToBlockBeforeEvent = (eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0); - const minutesToBlockAfterEvent = (eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0); - - if (rest._count?.seatsReferences) { - const bookedAt = `${dayjs(startTime).utc().format()}<>${dayjs(endTime).utc().format()}`; - bookingSeatCountMap[bookedAt] = bookingSeatCountMap[bookedAt] || 0; - bookingSeatCountMap[bookedAt]++; - // Seat references on the current event are non-blocking until the event is fully booked. - if ( - // there are still seats available. - bookingSeatCountMap[bookedAt] < (eventType?.seatsPerTimeSlot || 1) && - // and this is the seated event, other event types should be blocked. - eventTypeId === eventType?.id - ) { - // then we ONLY add the before/after buffer times as busy times. - if (minutesToBlockBeforeEvent) { - aggregate.push({ - start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(), - end: dayjs(startTime).toDate(), // The event starts after the buffer - }); - } - if (minutesToBlockAfterEvent) { - aggregate.push({ - start: dayjs(endTime).toDate(), // The event ends before the buffer - end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(), - }); - } - return aggregate; - } - // if it does get blocked at this point; we remove the bookingSeatCountMap entry - // doing this allows using the map later to remove the ranges from calendar busy times. - delete bookingSeatCountMap[bookedAt]; - } - // rescheduling the same booking to the same time should be possible. Why? - if (rest.uid === rescheduleUid) { - return aggregate; - } - aggregate.push({ - start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(), - end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(), - title, - source: `eventType-${eventType?.id}-booking-${id}`, - }); - return aggregate; - }, []); - - logger.debug( - `Busy Time from Cal Bookings ${JSON.stringify({ - busyTimes, - bookings: bookings?.map((booking) => getPiiFreeBooking(booking)), - numCredentials: credentials?.length, - })}` - ); - performance.mark("prismaBookingGetEnd"); - performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd"); - if (credentials?.length > 0 && !bypassBusyCalendarTimes) { - const startConnectedCalendarsGet = performance.now(); - - const calendarBusyTimesQuery = await getBusyCalendarTimes( + async _getBusyTimes(params: { + credentials: CredentialForCalendarService[]; + userId: number; + userEmail: string; + username: string; + eventTypeId?: number; + startTime: string; + beforeEventBuffer?: number; + afterEventBuffer?: number; + endTime: string; + selectedCalendars: SelectedCalendar[]; + seatedEvent?: boolean; + rescheduleUid?: string | null; + duration?: number | null; + currentBookings?: + | (Pick & { + eventType: Pick< + EventType, + "id" | "beforeEventBuffer" | "afterEventBuffer" | "seatsPerTimeSlot" + > | null; + _count?: { + seatsReferences: number; + }; + })[] + | null; + bypassBusyCalendarTimes: boolean; + shouldServeCache?: boolean; + }) { + const { credentials, + userId, + userEmail, + username, + eventTypeId, startTime, endTime, + beforeEventBuffer, + afterEventBuffer, selectedCalendars, - shouldServeCache - ); + seatedEvent, + rescheduleUid, + duration, + bypassBusyCalendarTimes = false, + shouldServeCache, + } = params; - if (!calendarBusyTimesQuery.success) { - throw new Error( - `Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map( - (calendar) => calendar.id - )}` - ); - } - - const calendarBusyTimes = calendarBusyTimesQuery.data; - const endConnectedCalendarsGet = performance.now(); - logger.debug( - `Connected Calendars get took ${ - endConnectedCalendarsGet - startConnectedCalendarsGet - } ms for user ${username}`, - JSON.stringify({ + logger.silly( + `Checking Busy time from Cal Bookings in range ${startTime} to ${endTime} for input ${JSON.stringify({ + userId, eventTypeId, - startTimeDate, - endTimeDate, - calendarBusyTimes, - }) + status: BookingStatus.ACCEPTED, + })}` ); - const openSeatsDateRanges = Object.keys(bookingSeatCountMap).map((key) => { - const [start, end] = key.split("<>"); - return { - start: dayjs(start), - end: dayjs(end), - }; - }); + /** + * A user is considered busy within a given time period if there + * is a booking they own OR attend. + * + * Performs a query for all bookings where: + * - The given booking is owned by this user, or.. + * - The current user has a different booking at this time he/she attends + * + * See further discussion within this GH issue: + * https://github.com/calcom/cal.com/issues/6374 + * + * NOTE: Changes here will likely require changes to some mocking + * logic within getSchedule.test.ts:addBookings + */ + performance.mark("prismaBookingGetStart"); - if (rescheduleUid) { - const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid); - // calendar busy time from original rescheduled booking should not be blocked - if (originalRescheduleBooking) { - openSeatsDateRanges.push({ - start: dayjs(originalRescheduleBooking.startTime), - end: dayjs(originalRescheduleBooking.endTime), - }); - } + const startTimeDate = + rescheduleUid && duration + ? dayjs(startTime).subtract(duration, "minute").toDate() + : new Date(startTime); + const endTimeDate = + rescheduleUid && duration ? dayjs(endTime).add(duration, "minute").toDate() : new Date(endTime); + + // to also get bookings that are outside of start and end time, but the buffer falls within the start and end time + const definedBufferTimes = getDefinedBufferTimes(); + const maxBuffer = definedBufferTimes[definedBufferTimes.length - 1]; + const startTimeAdjustedWithMaxBuffer = dayjs(startTimeDate).subtract(maxBuffer, "minute").toDate(); + const endTimeAdjustedWithMaxBuffer = dayjs(endTimeDate).add(maxBuffer, "minute").toDate(); + + // INFO: Refactored to allow this method to take in a list of current bookings for the user. + // Will keep support for retrieving a user's bookings if the caller does not already supply them. + // This function is called from multiple places but we aren't refactoring all of them at this moment + // to avoid potential side effects. + let bookings = params.currentBookings; + + if (!bookings) { + const bookingRepo = this.dependencies.bookingRepo; + bookings = await bookingRepo.findAllExistingBookingsForEventTypeBetween({ + userIdAndEmailMap: new Map([[userId, userEmail]]), + eventTypeId, + startDate: startTimeAdjustedWithMaxBuffer, + endDate: endTimeAdjustedWithMaxBuffer, + seatedEvent, + }); } - const result = subtract( - calendarBusyTimes.map((value) => ({ - ...value, - end: dayjs(value.end), - start: dayjs(value.start), - })), - openSeatsDateRanges - ); + const bookingSeatCountMap: { [x: string]: number } = {}; + const busyTimes = bookings.reduce((aggregate: EventBusyDetails[], booking) => { + const { id, startTime, endTime, eventType, title, ...rest } = booking; - busyTimes.push( - ...result.map((busyTime) => ({ - ...busyTime, - start: busyTime.start.subtract(afterEventBuffer || 0, "minute").toDate(), - end: busyTime.end.add(beforeEventBuffer || 0, "minute").toDate(), - })) - ); + const minutesToBlockBeforeEvent = (eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0); + const minutesToBlockAfterEvent = (eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0); - /* + if (rest._count?.seatsReferences) { + const bookedAt = `${dayjs(startTime).utc().format()}<>${dayjs(endTime).utc().format()}`; + bookingSeatCountMap[bookedAt] = bookingSeatCountMap[bookedAt] || 0; + bookingSeatCountMap[bookedAt]++; + // Seat references on the current event are non-blocking until the event is fully booked. + if ( + // there are still seats available. + bookingSeatCountMap[bookedAt] < (eventType?.seatsPerTimeSlot || 1) && + // and this is the seated event, other event types should be blocked. + eventTypeId === eventType?.id + ) { + // then we ONLY add the before/after buffer times as busy times. + if (minutesToBlockBeforeEvent) { + aggregate.push({ + start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(), + end: dayjs(startTime).toDate(), // The event starts after the buffer + }); + } + if (minutesToBlockAfterEvent) { + aggregate.push({ + start: dayjs(endTime).toDate(), // The event ends before the buffer + end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(), + }); + } + return aggregate; + } + // if it does get blocked at this point; we remove the bookingSeatCountMap entry + // doing this allows using the map later to remove the ranges from calendar busy times. + delete bookingSeatCountMap[bookedAt]; + } + // rescheduling the same booking to the same time should be possible. Why? + if (rest.uid === rescheduleUid) { + return aggregate; + } + aggregate.push({ + start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(), + end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(), + title, + source: `eventType-${eventType?.id}-booking-${id}`, + }); + return aggregate; + }, []); + + logger.debug( + `Busy Time from Cal Bookings ${JSON.stringify({ + busyTimes, + bookings: bookings?.map((booking) => getPiiFreeBooking(booking)), + numCredentials: credentials?.length, + })}` + ); + performance.mark("prismaBookingGetEnd"); + performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd"); + if (credentials?.length > 0 && !bypassBusyCalendarTimes) { + const startConnectedCalendarsGet = performance.now(); + + const calendarBusyTimesQuery = await getBusyCalendarTimes( + credentials, + startTime, + endTime, + selectedCalendars, + shouldServeCache + ); + + if (!calendarBusyTimesQuery.success) { + throw new Error( + `Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map( + (calendar) => calendar.id + )}` + ); + } + + const calendarBusyTimes = calendarBusyTimesQuery.data; + const endConnectedCalendarsGet = performance.now(); + logger.debug( + `Connected Calendars get took ${ + endConnectedCalendarsGet - startConnectedCalendarsGet + } ms for user ${username}`, + JSON.stringify({ + eventTypeId, + startTimeDate, + endTimeDate, + calendarBusyTimes, + }) + ); + + const openSeatsDateRanges = Object.keys(bookingSeatCountMap).map((key) => { + const [start, end] = key.split("<>"); + return { + start: dayjs(start), + end: dayjs(end), + }; + }); + + if (rescheduleUid) { + const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid); + // calendar busy time from original rescheduled booking should not be blocked + if (originalRescheduleBooking) { + openSeatsDateRanges.push({ + start: dayjs(originalRescheduleBooking.startTime), + end: dayjs(originalRescheduleBooking.endTime), + }); + } + } + + const result = subtract( + calendarBusyTimes.map((value) => ({ + ...value, + end: dayjs(value.end), + start: dayjs(value.start), + })), + openSeatsDateRanges + ); + + busyTimes.push( + ...result.map((busyTime) => ({ + ...busyTime, + start: busyTime.start.subtract(afterEventBuffer || 0, "minute").toDate(), + end: busyTime.end.add(beforeEventBuffer || 0, "minute").toDate(), + })) + ); + + /* // TODO: Disabled until we can filter Zoom events by date. Also this is adding too much latency. const videoBusyTimes = (await getBusyVideoTimes(credentials)).filter(notEmpty); console.log("videoBusyTimes", videoBusyTimes); busyTimes.push(...videoBusyTimes); */ - } else { - logger.warn(`No credentials found for user ${userId}`, { - selectedCalendarIds: selectedCalendars.map((calendar) => calendar.id), - }); - } - logger.debug( - "getBusyTimes:", - JSON.stringify({ - allBusyTimes: busyTimes, - }) - ); - return busyTimes; -}; - -export const getBusyTimes = withReporting(_getBusyTimes, "getBusyTimes"); - -export function getStartEndDateforLimitCheck( - startDate: string, - endDate: string, - bookingLimits?: IntervalLimit | null, - durationLimits?: IntervalLimit | null -) { - const startTimeAsDayJs = stringToDayjs(startDate); - const endTimeAsDayJs = stringToDayjs(endDate); - - let limitDateFrom = stringToDayjs(startDate); - let limitDateTo = stringToDayjs(endDate); - - // expand date ranges by absolute minimum required to apply limits - // (yearly limits are handled separately for performance) - for (const key of ["PER_MONTH", "PER_WEEK", "PER_DAY"] as Exclude[]) { - if (bookingLimits?.[key] || durationLimits?.[key]) { - const unit = intervalLimitKeyToUnit(key); - limitDateFrom = dayjs.min(limitDateFrom, startTimeAsDayJs.startOf(unit)); - limitDateTo = dayjs.max(limitDateTo, endTimeAsDayJs.endOf(unit)); + } else { + logger.warn(`No credentials found for user ${userId}`, { + selectedCalendarIds: selectedCalendars.map((calendar) => calendar.id), + }); } - } - - return { limitDateFrom, limitDateTo }; -} - -export async function getBusyTimesForLimitChecks(params: { - userIds: number[]; - eventTypeId: number; - startDate: string; - endDate: string; - rescheduleUid?: string | null; - bookingLimits?: IntervalLimit | null; - durationLimits?: IntervalLimit | null; -}) { - const { userIds, eventTypeId, startDate, endDate, rescheduleUid, bookingLimits, durationLimits } = params; - - performance.mark("getBusyTimesForLimitChecksStart"); - - let busyTimes: EventBusyDetails[] = []; - - if (!bookingLimits && !durationLimits) { + logger.debug( + "getBusyTimes:", + JSON.stringify({ + allBusyTimes: busyTimes, + }) + ); return busyTimes; } - const { limitDateFrom, limitDateTo } = getStartEndDateforLimitCheck( - startDate, - endDate, - bookingLimits, - durationLimits - ); + getBusyTimes = withReporting(this._getBusyTimes.bind(this), "getBusyTimes"); - logger.silly( - `Fetch limit checks bookings in range ${limitDateFrom} to ${limitDateTo} for input ${JSON.stringify({ - eventTypeId, - status: BookingStatus.ACCEPTED, - })}` - ); + getStartEndDateforLimitCheck( + startDate: string, + endDate: string, + bookingLimits?: IntervalLimit | null, + durationLimits?: IntervalLimit | null + ) { + const startTimeAsDayJs = stringToDayjs(startDate); + const endTimeAsDayJs = stringToDayjs(endDate); - const where: Prisma.BookingWhereInput = { - userId: { - in: userIds, - }, - eventTypeId, - status: BookingStatus.ACCEPTED, - // FIXME: bookings that overlap on one side will never be counted - startTime: { - gte: limitDateFrom.toDate(), - }, - endTime: { - lte: limitDateTo.toDate(), - }, - }; + let limitDateFrom = stringToDayjs(startDate); + let limitDateTo = stringToDayjs(endDate); - if (rescheduleUid) { - where.NOT = { - uid: rescheduleUid, - }; + // expand date ranges by absolute minimum required to apply limits + // (yearly limits are handled separately for performance) + for (const key of ["PER_MONTH", "PER_WEEK", "PER_DAY"] as Exclude[]) { + if (bookingLimits?.[key] || durationLimits?.[key]) { + const unit = intervalLimitKeyToUnit(key); + limitDateFrom = dayjs.min(limitDateFrom, startTimeAsDayJs.startOf(unit)); + limitDateTo = dayjs.max(limitDateTo, endTimeAsDayJs.endOf(unit)); + } + } + + return { limitDateFrom, limitDateTo }; } - const bookings = await prisma.booking.findMany({ - where, - select: { - id: true, - startTime: true, - endTime: true, - eventType: { - select: { - id: true, - }, + async getBusyTimesForLimitChecks(params: { + userIds: number[]; + eventTypeId: number; + startDate: string; + endDate: string; + rescheduleUid?: string | null; + bookingLimits?: IntervalLimit | null; + durationLimits?: IntervalLimit | null; + }) { + const { userIds, eventTypeId, startDate, endDate, rescheduleUid, bookingLimits, durationLimits } = params; + + performance.mark("getBusyTimesForLimitChecksStart"); + + let busyTimes: EventBusyDetails[] = []; + + if (!bookingLimits && !durationLimits) { + return busyTimes; + } + + const { limitDateFrom, limitDateTo } = this.getStartEndDateforLimitCheck( + startDate, + endDate, + bookingLimits, + durationLimits + ); + + logger.silly( + `Fetch limit checks bookings in range ${limitDateFrom} to ${limitDateTo} for input ${JSON.stringify({ + eventTypeId, + status: BookingStatus.ACCEPTED, + })}` + ); + + const where: Prisma.BookingWhereInput = { + userId: { + in: userIds, }, - title: true, - userId: true, - }, - }); + eventTypeId, + status: BookingStatus.ACCEPTED, + // FIXME: bookings that overlap on one side will never be counted + startTime: { + gte: limitDateFrom.toDate(), + }, + endTime: { + lte: limitDateTo.toDate(), + }, + }; - busyTimes = bookings.map(({ id, startTime, endTime, eventType, title, userId }) => ({ - start: dayjs(startTime).toDate(), - end: dayjs(endTime).toDate(), - title, - source: `eventType-${eventType?.id}-booking-${id}`, - userId, - })); + if (rescheduleUid) { + where.NOT = { + uid: rescheduleUid, + }; + } - logger.silly(`Fetch limit checks bookings for eventId: ${eventTypeId} ${JSON.stringify(busyTimes)}`); - performance.mark("getBusyTimesForLimitChecksEnd"); - performance.measure( - `prisma booking get for limits took $1'`, - "getBusyTimesForLimitChecksStart", - "getBusyTimesForLimitChecksEnd" - ); - return busyTimes; + const bookings = await prisma.booking.findMany({ + where, + select: { + id: true, + startTime: true, + endTime: true, + eventType: { + select: { + id: true, + }, + }, + title: true, + userId: true, + }, + }); + + busyTimes = bookings.map(({ id, startTime, endTime, eventType, title, userId }) => ({ + start: dayjs(startTime).toDate(), + end: dayjs(endTime).toDate(), + title, + source: `eventType-${eventType?.id}-booking-${id}`, + userId, + })); + + logger.silly(`Fetch limit checks bookings for eventId: ${eventTypeId} ${JSON.stringify(busyTimes)}`); + performance.mark("getBusyTimesForLimitChecksEnd"); + performance.measure( + `prisma booking get for limits took $1'`, + "getBusyTimesForLimitChecksStart", + "getBusyTimesForLimitChecksEnd" + ); + return busyTimes; + } } - -export default withReporting(_getBusyTimes, "getBusyTimes"); diff --git a/packages/lib/getUserAvailability.ts b/packages/lib/getUserAvailability.ts index a1cec196c1..25335eeb4b 100644 --- a/packages/lib/getUserAvailability.ts +++ b/packages/lib/getUserAvailability.ts @@ -36,7 +36,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { EventBusyDetails, IntervalLimitUnit } from "@calcom/types/Calendar"; import type { TimeRange } from "@calcom/types/schedule"; -import { getBusyTimes } from "./getBusyTimes"; +import { getBusyTimesService } from "./di/containers/busy-times"; import { getPeriodStartDatesBetween as getPeriodStartDatesBetweenUtil } from "./intervalLimits/utils/getPeriodStartDatesBetween"; import { withReporting } from "./sentryWrapper"; @@ -440,7 +440,8 @@ export class UserAvailabilityService { let busyTimes = []; try { - busyTimes = await getBusyTimes({ + const busyTimesService = getBusyTimesService(); + busyTimes = await busyTimesService.getBusyTimes({ credentials: user.credentials, startTime: getBusyTimesStart, endTime: getBusyTimesEnd, diff --git a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts index 82fd720d38..6f385e71db 100644 --- a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts +++ b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts @@ -1,7 +1,7 @@ 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 { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; import type { EventType } from "@calcom/lib/getUserAvailability"; import { getPeriodStartDatesBetween } from "@calcom/lib/intervalLimits/utils/getPeriodStartDatesBetween"; import { withReporting } from "@calcom/lib/sentryWrapper"; @@ -229,7 +229,8 @@ const _getBusyTimesFromTeamLimits = async ( timeZone: string, rescheduleUid?: string ) => { - const { limitDateFrom, limitDateTo } = getStartEndDateforLimitCheck( + const busyTimesService = getBusyTimesService(); + const { limitDateFrom, limitDateTo } = busyTimesService.getStartEndDateforLimitCheck( dateFrom.toISOString(), dateTo.toISOString(), bookingLimits diff --git a/packages/platform/libraries/slots.ts b/packages/platform/libraries/slots.ts index 420fc83b5b..782927228e 100644 --- a/packages/platform/libraries/slots.ts +++ b/packages/platform/libraries/slots.ts @@ -1,3 +1,6 @@ +import { BusyTimesService } from "@calcom/lib/getBusyTimes"; import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; export { AvailableSlotsService }; + +export { BusyTimesService }; diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index e14b6771b2..2c260668e8 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -15,8 +15,9 @@ import { RESERVED_SUBDOMAINS } from "@calcom/lib/constants"; import { buildDateRanges } from "@calcom/lib/date-ranges"; import { getUTCOffsetByTimezone } from "@calcom/lib/dayjs"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; +import type { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; import { getAggregatedAvailability } from "@calcom/lib/getAggregatedAvailability"; -import { getBusyTimesForLimitChecks, getStartEndDateforLimitCheck } from "@calcom/lib/getBusyTimes"; +import type { BusyTimesService } from "@calcom/lib/getBusyTimes"; import type { CurrentSeats, EventType, @@ -103,6 +104,7 @@ export interface IAvailableSlotsService { cacheService: CacheService; checkBookingLimitsService: CheckBookingLimitsService; userAvailabilityService: UserAvailabilityService; + busyTimesService: BusyTimesService; redisClient: IRedisService; } @@ -373,13 +375,13 @@ export class AvailableSlotsService { return userBusyTimesMap; } - const { limitDateFrom, limitDateTo } = getStartEndDateforLimitCheck( + const { limitDateFrom, limitDateTo } = this.dependencies.busyTimesService.getStartEndDateforLimitCheck( dateFrom.toISOString(), dateTo.toISOString(), bookingLimits || durationLimits ); - const busyTimesFromLimitsBookings = await getBusyTimesForLimitChecks({ + const busyTimesFromLimitsBookings = await this.dependencies.busyTimesService.getBusyTimesForLimitChecks({ userIds: users.map((user) => user.id), eventTypeId: eventType.id, startDate: limitDateFrom.format(), @@ -566,7 +568,7 @@ export class AvailableSlotsService { timeZone: string, rescheduleUid?: string ) { - const { limitDateFrom, limitDateTo } = getStartEndDateforLimitCheck( + const { limitDateFrom, limitDateTo } = this.dependencies.busyTimesService.getStartEndDateforLimitCheck( dateFrom.toISOString(), dateTo.toISOString(), bookingLimits @@ -797,19 +799,21 @@ export class AvailableSlotsService { ? parseDurationLimit(eventType?.durationLimits) : null; - let busyTimesFromLimitsBookingsAllUsers: Awaited> = []; + let busyTimesFromLimitsBookingsAllUsers: Awaited< + ReturnType + > = []; if (eventType && (bookingLimits || durationLimits)) { - // TODO: DI getBusyTimesForLimitChecks - busyTimesFromLimitsBookingsAllUsers = await getBusyTimesForLimitChecks({ - userIds: allUserIds, - eventTypeId: eventType.id, - startDate: startTime.format(), - endDate: endTime.format(), - rescheduleUid: input.rescheduleUid, - bookingLimits, - durationLimits, - }); + busyTimesFromLimitsBookingsAllUsers = + await this.dependencies.busyTimesService.getBusyTimesForLimitChecks({ + userIds: allUserIds, + eventTypeId: eventType.id, + startDate: startTime.format(), + endDate: endTime.format(), + rescheduleUid: input.rescheduleUid, + bookingLimits, + durationLimits, + }); } let busyTimesFromLimitsMap: Map | undefined = undefined; @@ -868,7 +872,6 @@ export class AvailableSlotsService { const enrichUsersWithData = withReporting(_enrichUsersWithData.bind(this), "enrichUsersWithData"); const users = enrichUsersWithData(); - // TODO: DI getUsersAvailability const premappedUsersAvailability = await this.dependencies.userAvailabilityService.getUsersAvailability({ users, query: { diff --git a/yarn.lock b/yarn.lock index de84adeab1..e8a90751b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2500,7 +2500,7 @@ __metadata: "@axiomhq/winston": ^1.2.0 "@calcom/platform-constants": "*" "@calcom/platform-enums": "*" - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.288" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.291" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -2531,6 +2531,8 @@ __metadata: "@types/luxon": ^3.3.7 "@types/passport-jwt": ^3.0.13 "@types/supertest": ^2.0.12 + "@typescript-eslint/eslint-plugin": ^6 + "@typescript-eslint/parser": ^6 body-parser: ^1.20.2 bull: ^4.12.4 class-transformer: ^0.5.1 @@ -3556,13 +3558,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.288": - version: 0.0.288 - resolution: "@calcom/platform-libraries@npm:0.0.288" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.291": + version: 0.0.291 + resolution: "@calcom/platform-libraries@npm:0.0.291" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 9f44ba9e258a94c9b8bb5388c400696f6e6acd20dbf73865d62739b1aad212626bb181d88b38b30d08659c8b9183d27aba94bdd52b36e6c6c3cc7594d1af29d7 + checksum: 09cb5db584698acccd4447b4007df2fe2d66352c6ba823ef8c354fd3458e59414a9502f6950438e1260df71613b95f1e5a14bb54e2c7ffe6cc1dafe8b643fb9d languageName: node linkType: hard @@ -5715,7 +5717,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.5.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: 0d628680e204bc316d545b4993d3658427ca404ae646ce541fcc65306b8c712c340e5e573e30fb9f85f4855c0c5f6dca9868931f2fcced06417fbe1a0c6cd2d6 @@ -17629,6 +17631,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.12": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 97ed0cb44d4070aecea772b7b2e2ed971e10c81ec87dd4ecc160322ffa55ff330dace1793489540e3e318d90942064bb697cc0f8989391797792d919737b3b98 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -18156,6 +18165,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.5.0": + version: 7.7.0 + resolution: "@types/semver@npm:7.7.0" + checksum: d488eaeddb23879a0a8a759bed667e1a76cb0dd4d23e3255538e24c189db387357953ca9e7a3bda2bb7f95e84cac8fe0db4fbe6b3456e893043337732d1d23cc + languageName: node + linkType: hard + "@types/send@npm:*": version: 0.17.4 resolution: "@types/send@npm:0.17.4" @@ -18462,21 +18478,28 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0": +"@typescript-eslint/eslint-plugin@npm:^6": version: 6.21.0 - resolution: "@typescript-eslint/parser@npm:6.21.0" + resolution: "@typescript-eslint/eslint-plugin@npm:6.21.0" dependencies: + "@eslint-community/regexpp": ^4.5.1 "@typescript-eslint/scope-manager": 6.21.0 - "@typescript-eslint/types": 6.21.0 - "@typescript-eslint/typescript-estree": 6.21.0 + "@typescript-eslint/type-utils": 6.21.0 + "@typescript-eslint/utils": 6.21.0 "@typescript-eslint/visitor-keys": 6.21.0 debug: ^4.3.4 + graphemer: ^1.4.0 + ignore: ^5.2.4 + natural-compare: ^1.4.0 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 peerDependencies: + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 162fe3a867eeeffda7328bce32dae45b52283c68c8cb23258fb9f44971f761991af61f71b8c9fe1aa389e93dfe6386f8509c1273d870736c507d76dd40647b68 + checksum: 5ef2c502255e643e98051e87eb682c2a257e87afd8ec3b9f6274277615e1c2caf3131b352244cfb1987b8b2c415645eeacb9113fa841fc4c9b2ac46e8aed6efd languageName: node linkType: hard @@ -18496,6 +18519,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0, @typescript-eslint/parser@npm:^6": + version: 6.21.0 + resolution: "@typescript-eslint/parser@npm:6.21.0" + dependencies: + "@typescript-eslint/scope-manager": 6.21.0 + "@typescript-eslint/types": 6.21.0 + "@typescript-eslint/typescript-estree": 6.21.0 + "@typescript-eslint/visitor-keys": 6.21.0 + debug: ^4.3.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 162fe3a867eeeffda7328bce32dae45b52283c68c8cb23258fb9f44971f761991af61f71b8c9fe1aa389e93dfe6386f8509c1273d870736c507d76dd40647b68 + languageName: node + linkType: hard + "@typescript-eslint/parser@npm:^5.52.0": version: 5.52.0 resolution: "@typescript-eslint/parser@npm:5.52.0" @@ -18560,6 +18601,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/type-utils@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/type-utils@npm:6.21.0" + dependencies: + "@typescript-eslint/typescript-estree": 6.21.0 + "@typescript-eslint/utils": 6.21.0 + debug: ^4.3.4 + ts-api-utils: ^1.0.1 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 77025473f4d80acf1fafcce99c5c283e557686a61861febeba9c9913331f8a41e930bf5cd8b7a54db502a57b6eb8ea6d155cbd4f41349ed00e3d7aeb1f477ddc + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.28.0": version: 8.28.0 resolution: "@typescript-eslint/type-utils@npm:8.28.0" @@ -18694,6 +18752,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/utils@npm:6.21.0" + dependencies: + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.21.0 + "@typescript-eslint/types": 6.21.0 + "@typescript-eslint/typescript-estree": 6.21.0 + semver: ^7.5.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: b129b3a4aebec8468259f4589985cb59ea808afbfdb9c54f02fad11e17d185e2bf72bb332f7c36ec3c09b31f18fc41368678b076323e6e019d06f74ee93f7bf2 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:8.28.0": version: 8.28.0 resolution: "@typescript-eslint/utils@npm:8.28.0" @@ -29699,7 +29774,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.3.1": +"ignore@npm:^5.2.4, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 2acfd32a573260ea522ea0bfeff880af426d68f6831f973129e2ba7363f422923cf53aab62f8369cbf4667c7b25b6f8a3761b34ecdb284ea18e87a5262a865be