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 <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 <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 <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot]
2025-08-07 10:53:18 +03:00
committed by GitHub
co-authored by morgan@cal.com <morgan@cal.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> morgan@cal.com <morgan@cal.com> Morgan
parent 895ed48bd6
commit cfda4757e4
8 changed files with 106 additions and 76 deletions
+1 -1
View File
@@ -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": "*",
+1 -1
View File
@@ -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"
);
@@ -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,
@@ -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(),
@@ -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;
};
+62 -18
View File
@@ -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;
}
}
@@ -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;
+5 -5
View File
@@ -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