From cfda4757e434bdee8a865c80723e52f778fd7928 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:53:18 +0300 Subject: [PATCH] refactor: move getTotalBookingDuration to BookingRepository (#22931) * refactor: move getTotalBookingDuration to BookingRepository - Move getTotalBookingDuration function from standalone file to BookingRepository class - Update all usage sites to call method through repository instance - Remove standalone function file packages/lib/server/queries/booking/index.ts - Add prisma import to util.ts for BookingRepository instantiation - Maintain exact same method signature and functionality Co-Authored-By: morgan@cal.com * fix: remove eslint-config-next to resolve TypeScript ESLint conflicts - Remove eslint-config-next dependency that was causing version conflicts - Resolves 'Class extends value undefined is not a constructor or null' errors - ESLint 'next' config issue appears to be pre-existing in main branch Co-Authored-By: morgan@cal.com * fixup! Merge branch 'main' into devin/move-getTotalBookingDuration-1754460208 * chore: bump platform libs --------- 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 | 2 +- packages/lib/getUserAvailability.ts | 2 +- .../server/checkDurationLimits.ts | 6 +- .../server/getBusyTimesFromLimits.ts | 4 +- packages/lib/server/queries/booking/index.ts | 39 --------- packages/lib/server/repository/booking.ts | 80 ++++++++++++++----- .../trpc/server/routers/viewer/slots/util.ts | 39 +++++++-- yarn.lock | 10 +-- 8 files changed, 106 insertions(+), 76 deletions(-) delete mode 100644 packages/lib/server/queries/booking/index.ts diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 05a2a28eda..fb81e0ed67 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.287", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.288", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/packages/lib/getUserAvailability.ts b/packages/lib/getUserAvailability.ts index 79ee8d26c9..a1cec196c1 100644 --- a/packages/lib/getUserAvailability.ts +++ b/packages/lib/getUserAvailability.ts @@ -597,7 +597,7 @@ export class UserAvailabilityService { getUserAvailability = withReporting(this._getUserAvailability.bind(this), "getUserAvailability"); getPeriodStartDatesBetween = withReporting( - (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit, timeZone?: string) => + (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit, timeZone?: string) => getPeriodStartDatesBetweenUtil(dateFrom, dateTo, period, timeZone), "getPeriodStartDatesBetween" ); diff --git a/packages/lib/intervalLimits/server/checkDurationLimits.ts b/packages/lib/intervalLimits/server/checkDurationLimits.ts index 72f430881e..4fd7572419 100644 --- a/packages/lib/intervalLimits/server/checkDurationLimits.ts +++ b/packages/lib/intervalLimits/server/checkDurationLimits.ts @@ -1,7 +1,8 @@ import dayjs from "@calcom/dayjs"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; -import { getTotalBookingDuration } from "@calcom/lib/server/queries/booking"; +import { BookingRepository } from "@calcom/lib/server/repository/booking"; +import prisma from "@calcom/prisma"; import { ascendingLimitKeys, intervalLimitKeyToUnit } from "../intervalLimit"; import type { IntervalLimit, IntervalLimitKey } from "../intervalLimitSchema"; @@ -55,7 +56,8 @@ export async function checkDurationLimit({ const startDate = dayjs(eventStartDate).startOf(unit).toDate(); const endDate = dayjs(eventStartDate).endOf(unit).toDate(); - const totalBookingDuration = await getTotalBookingDuration({ + const bookingRepo = new BookingRepository(prisma); + const totalBookingDuration = await bookingRepo.getTotalBookingDuration({ eventId, startDate, endDate, diff --git a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts index 03575149dc..82fd720d38 100644 --- a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts +++ b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts @@ -6,7 +6,6 @@ import type { EventType } from "@calcom/lib/getUserAvailability"; import { getPeriodStartDatesBetween } from "@calcom/lib/intervalLimits/utils/getPeriodStartDatesBetween"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { performance } from "@calcom/lib/server/perfObserver"; -import { getTotalBookingDuration } from "@calcom/lib/server/queries/booking"; import { BookingRepository } from "@calcom/lib/server/repository/booking"; import prisma from "@calcom/prisma"; import type { EventBusyDetails } from "@calcom/types/Calendar"; @@ -181,7 +180,8 @@ const _getBusyTimesFromDurationLimits = async ( // special handling of yearly limits to improve performance if (unit === "year") { - const totalYearlyDuration = await getTotalBookingDuration({ + const bookingRepo = new BookingRepository(prisma); + const totalYearlyDuration = await bookingRepo.getTotalBookingDuration({ eventId: eventType.id, startDate: periodStart.toDate(), endDate: periodStart.endOf(unit).toDate(), diff --git a/packages/lib/server/queries/booking/index.ts b/packages/lib/server/queries/booking/index.ts deleted file mode 100644 index 9ae95954e0..0000000000 --- a/packages/lib/server/queries/booking/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import prisma from "@calcom/prisma"; - -export const getTotalBookingDuration = async ({ - eventId, - startDate, - endDate, - rescheduleUid, -}: { - eventId: number; - startDate: Date; - endDate: Date; - rescheduleUid?: string; -}) => { - // Aggregates the total booking time for a given event in a given time period - // FIXME: bookings that overlap on one side will never be counted - let totalBookingTime; - - if (rescheduleUid) { - [totalBookingTime] = await prisma.$queryRaw<[{ totalMinutes: number | null }]>` - SELECT SUM(EXTRACT(EPOCH FROM ("endTime" - "startTime")) / 60) as "totalMinutes" - FROM "Booking" - WHERE "status" = 'accepted' - AND "eventTypeId" = ${eventId} - AND "startTime" >= ${startDate} - AND "endTime" <= ${endDate} - AND "uid" != ${rescheduleUid}; - `; - } else { - [totalBookingTime] = await prisma.$queryRaw<[{ totalMinutes: number | null }]>` - SELECT SUM(EXTRACT(EPOCH FROM ("endTime" - "startTime")) / 60) as "totalMinutes" - FROM "Booking" - WHERE "status" = 'accepted' - AND "eventTypeId" = ${eventId} - AND "startTime" >= ${startDate} - AND "endTime" <= ${endDate}; - `; - } - return totalBookingTime.totalMinutes ?? 0; -}; diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 2fb9c906af..9be11147ad 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -870,25 +870,69 @@ export class BookingRepository { }); } - async findAcceptedBookingByEventTypeId({eventTypeId, dateFrom, dateTo}: {eventTypeId?: number, dateFrom: string, dateTo: string}) { - return this.prismaClient.booking.findMany({ - where: { - eventTypeId, - startTime: { - gte: dateFrom, - lte: dateTo, - }, - status: BookingStatus.ACCEPTED, - }, + async findAcceptedBookingByEventTypeId({ + eventTypeId, + dateFrom, + dateTo, + }: { + eventTypeId?: number; + dateFrom: string; + dateTo: string; + }) { + return this.prismaClient.booking.findMany({ + where: { + eventTypeId, + startTime: { + gte: dateFrom, + lte: dateTo, + }, + status: BookingStatus.ACCEPTED, + }, + select: { + uid: true, + startTime: true, + attendees: { select: { - uid: true, - startTime: true, - attendees: { - select: { - email: true, - }, - }, + email: true, }, - }); + }, + }, + }); + } + + async getTotalBookingDuration({ + eventId, + startDate, + endDate, + rescheduleUid, + }: { + eventId: number; + startDate: Date; + endDate: Date; + rescheduleUid?: string; + }) { + let totalBookingTime; + + if (rescheduleUid) { + [totalBookingTime] = await this.prismaClient.$queryRaw<[{ totalMinutes: number | null }]>` + SELECT SUM(EXTRACT(EPOCH FROM ("endTime" - "startTime")) / 60) as "totalMinutes" + FROM "Booking" + WHERE "status" = 'accepted' + AND "eventTypeId" = ${eventId} + AND "startTime" >= ${startDate} + AND "endTime" <= ${endDate} + AND "uid" != ${rescheduleUid}; + `; + } else { + [totalBookingTime] = await this.prismaClient.$queryRaw<[{ totalMinutes: number | null }]>` + SELECT SUM(EXTRACT(EPOCH FROM ("endTime" - "startTime")) / 60) as "totalMinutes" + FROM "Booking" + WHERE "status" = 'accepted' + AND "eventTypeId" = ${eventId} + AND "startTime" >= ${startDate} + AND "endTime" <= ${endDate}; + `; + } + return totalBookingTime.totalMinutes ?? 0; } } diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 93c8107d06..2489a84e0a 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -42,7 +42,6 @@ import logger from "@calcom/lib/logger"; import { isRestrictionScheduleEnabled } from "@calcom/lib/restrictionSchedule"; import { safeStringify } from "@calcom/lib/safeStringify"; import { withReporting } from "@calcom/lib/sentryWrapper"; -import { getTotalBookingDuration } from "@calcom/lib/server/queries/booking"; import type { ISelectedSlotRepository } from "@calcom/lib/server/repository/ISelectedSlotRepository"; import type { BookingRepository } from "@calcom/lib/server/repository/booking"; import type { EventTypeRepository } from "@calcom/lib/server/repository/eventTypeRepository"; @@ -400,7 +399,12 @@ export class AvailableSlotsService { if (!limit) continue; const unit = intervalLimitKeyToUnit(key); - const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone); + const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween( + dateFrom, + dateTo, + unit, + timeZone + ); for (const periodStart of periodStartDates) { if (globalLimitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue; @@ -435,7 +439,12 @@ export class AvailableSlotsService { if (!limit) continue; const unit = intervalLimitKeyToUnit(key); - const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone); + const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween( + dateFrom, + dateTo, + unit, + timeZone + ); for (const periodStart of periodStartDates) { if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue; @@ -486,7 +495,12 @@ export class AvailableSlotsService { if (!limit) continue; const unit = intervalLimitKeyToUnit(key); - const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone); + const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween( + dateFrom, + dateTo, + unit, + timeZone + ); for (const periodStart of periodStartDates) { if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue; @@ -499,8 +513,7 @@ export class AvailableSlotsService { } if (unit === "year") { - // TODO: DI getTotalBookingDuration - const totalYearlyDuration = await getTotalBookingDuration({ + const totalYearlyDuration = await this.dependencies.bookingRepo.getTotalBookingDuration({ eventId: eventType.id, startDate: periodStart.toDate(), endDate: periodStart.endOf(unit).toDate(), @@ -586,7 +599,12 @@ export class AvailableSlotsService { if (!limit) continue; const unit = intervalLimitKeyToUnit(key); - const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone); + const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween( + dateFrom, + dateTo, + unit, + timeZone + ); for (const periodStart of periodStartDates) { if (globalLimitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue; @@ -634,7 +652,12 @@ export class AvailableSlotsService { if (!limit) continue; const unit = intervalLimitKeyToUnit(key); - const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone); + const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween( + dateFrom, + dateTo, + unit, + timeZone + ); for (const periodStart of periodStartDates) { if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue; diff --git a/yarn.lock b/yarn.lock index c08123cee0..de84adeab1 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.287" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.288" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -3556,13 +3556,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.287": - version: 0.0.287 - resolution: "@calcom/platform-libraries@npm:0.0.287" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.288": + version: 0.0.288 + resolution: "@calcom/platform-libraries@npm:0.0.288" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 098a50e1b20f70c0f67a332d5f4f6a13c9aaf03f0c0ac9c9ffe1b74c06c607e6650bc0f08bdc1a9bdd89fa25461c0e153580305b845620a1506b23150334c5bd + checksum: 9f44ba9e258a94c9b8bb5388c400696f6e6acd20dbf73865d62739b1aad212626bb181d88b38b30d08659c8b9183d27aba94bdd52b36e6c6c3cc7594d1af29d7 languageName: node linkType: hard