* 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>
112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
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 type { BookingRepository } from "@calcom/lib/server/repository/booking";
|
|
|
|
import { ascendingLimitKeys, intervalLimitKeyToUnit } from "../intervalLimit";
|
|
import type { IntervalLimit, IntervalLimitKey } from "../intervalLimitSchema";
|
|
import { parseBookingLimit } from "../isBookingLimits";
|
|
|
|
export interface ICheckBookingLimitsService {
|
|
bookingRepo: BookingRepository;
|
|
}
|
|
|
|
export class CheckBookingLimitsService {
|
|
constructor(private readonly dependencies: ICheckBookingLimitsService) {}
|
|
|
|
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;
|
|
|
|
const unit = intervalLimitKeyToUnit(key);
|
|
|
|
const startDate = dayjs(eventDateInOrganizerTz).startOf(unit).toDate();
|
|
const endDate = dayjs(eventDateInOrganizerTz).endOf(unit).toDate();
|
|
|
|
let bookingsInPeriod;
|
|
|
|
if (teamId && user) {
|
|
bookingsInPeriod = await this.dependencies.bookingRepo.getAllAcceptedTeamBookingsOfUser({
|
|
user: { id: user.id, email: user.email },
|
|
teamId,
|
|
startDate: startDate,
|
|
endDate: endDate,
|
|
shouldReturnCount: true,
|
|
excludedUid: rescheduleUid,
|
|
includeManagedEvents,
|
|
});
|
|
} else {
|
|
bookingsInPeriod = await this.dependencies.bookingRepo.countBookingsByEventTypeAndDateRange({
|
|
eventTypeId: eventId!,
|
|
startDate,
|
|
endDate,
|
|
excludedUid: rescheduleUid,
|
|
});
|
|
}
|
|
|
|
if (bookingsInPeriod < limitingNumber) return;
|
|
|
|
throw new HttpError({
|
|
message: `booking_limit_reached`,
|
|
statusCode: 403,
|
|
});
|
|
}
|
|
|
|
checkBookingLimit = withReporting(this._checkBookingLimit.bind(this), "checkBookingLimit");
|
|
}
|