refactor: convert checkBookingLimits to class service with dependency injection (#22768)
* refactor: convert checkBookingLimits to class service with dependency injection - Create CheckBookingLimitsService class following AvailableSlotsService pattern - Add countBookingsByEventTypeAndDateRange method to BookingRepository - Move direct prisma calls from service to repository layer - Implement dependency injection with proper DI tokens and modules - Update all usage points to use the new service through DI - Maintain backward compatibility with error-throwing wrapper functions - Update tests to use the new service pattern - Resolve TODO comment in AvailableSlotsService for DI integration Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: DI CheckBookingLimitsService in v2 slots service * chore: bump libraries * chore: create getCheckBookingLimitsService * refactor: convert checkBookingAndDurationLimits to service class with DI - Create CheckBookingAndDurationLimitsService class following DI pattern - Add DI tokens and module for the new service - Update booking-limits container to provide the new service - Refactor handleNewBooking.ts to use service through DI - Maintain backward compatibility with deprecated function export - Preserve all existing functionality while improving code organization Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: CheckBookingAndDurationLimitsService * 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:
23 files changed
+260
-131
No files matched your search
@@ -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": "*",
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<CheckBookingLimitsService>(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE);
|
||||
}
|
||||
|
||||
export function getCheckBookingAndDurationLimitsService() {
|
||||
return container.get<CheckBookingAndDurationLimitsService>(
|
||||
DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE
|
||||
);
|
||||
}
|
||||
@@ -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<keyof IAvailableSlotsService, symbol>);
|
||||
@@ -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<keyof ICheckBookingAndDurationLimitsService, symbol>);
|
||||
@@ -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 });
|
||||
@@ -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"),
|
||||
};
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
|
||||
export { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits";
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user