From 5c508bce6de3c2257c38086ab02752bc10a0eb35 Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Wed, 9 Jul 2025 22:55:55 +0300 Subject: [PATCH] chore: DI repositories in AvailableSlotsService class (#22356) * chore: DI available slots service repositories * remove configService from worker module * register scheduleRepo in availableSlotsModule * chore: bump platform libs * remove useless comment from prisma module * fix: availableSlotsModule to class deps * fix: repositoriesModule deps * chore: container pattern * refactor: move modules in DI folder * fixup! refactor: move modules in DI folder --- apps/api/v1/pages/api/slots/_get.ts | 6 +- apps/api/v2/package.json | 2 +- .../lib/repositories/PrismaOOORepository.ts | 12 +++ .../repositories/PrismaScheduleRepository.ts | 12 +++ .../api/v2/src/lib/services/AvailableSlots.ts | 15 ++++ .../controllers/slots.controller.ts | 7 +- .../slots/slots-2024-04-15/slots.module.ts | 6 ++ .../workers/slots.worker.module.ts | 24 ++++++ .../slots-2024-04-15/workers/slots.worker.ts | 74 +++++++++++++++---- .../services/slots.service.ts | 11 +-- .../slots/slots-2024-09-04/slots.module.ts | 6 ++ apps/api/v2/tsconfig.json | 3 +- .../api/get-inbound-dynamic-variables.ts | 4 +- apps/web/test/lib/getSchedule.test.ts | 4 +- .../lib/getSchedule/calendarEvents.test.ts | 4 +- .../getSchedule/delegation-credential.test.ts | 5 +- .../getSchedule/futureLimit.timezone.test.ts | 4 +- .../getSchedule/restrictionSchedule.test.ts | 4 +- .../lib/getSchedule/selectedSlots.test.ts | 4 +- packages/lib/di/containers/available-slots.ts | 19 +++++ packages/lib/di/modules/available-slots.ts | 12 +++ packages/lib/di/modules/ooo.ts | 7 ++ packages/lib/di/modules/schedule.ts | 9 +++ packages/lib/di/tokens.ts | 11 +++ packages/platform/libraries/package.json | 8 ++ packages/platform/libraries/repositories.ts | 2 + packages/platform/libraries/vite.config.js | 1 + packages/prisma/prisma.module.ts | 11 +++ .../highPerf/getTeamSchedule.handler.ts | 5 +- .../viewer/slots/getSchedule.handler.ts | 5 +- .../trpc/server/routers/viewer/slots/util.ts | 18 +++-- yarn.lock | 10 +-- 32 files changed, 266 insertions(+), 59 deletions(-) create mode 100644 apps/api/v2/src/lib/repositories/PrismaOOORepository.ts create mode 100644 apps/api/v2/src/lib/repositories/PrismaScheduleRepository.ts create mode 100644 apps/api/v2/src/lib/services/AvailableSlots.ts create mode 100644 apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.module.ts create mode 100644 packages/lib/di/containers/available-slots.ts create mode 100644 packages/lib/di/modules/available-slots.ts create mode 100644 packages/lib/di/modules/ooo.ts create mode 100644 packages/lib/di/modules/schedule.ts create mode 100644 packages/lib/di/tokens.ts create mode 100644 packages/platform/libraries/repositories.ts create mode 100644 packages/prisma/prisma.module.ts diff --git a/apps/api/v1/pages/api/slots/_get.ts b/apps/api/v1/pages/api/slots/_get.ts index 6c402cd9fa..f47731f2e7 100644 --- a/apps/api/v1/pages/api/slots/_get.ts +++ b/apps/api/v1/pages/api/slots/_get.ts @@ -4,11 +4,11 @@ import type { NextApiRequest, NextApiResponse } from "next"; import dayjs from "@calcom/dayjs"; import { isSupportedTimeZone } from "@calcom/lib/dayjs"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { createContext } from "@calcom/trpc/server/createContext"; import { getScheduleSchema } from "@calcom/trpc/server/routers/viewer/slots/types"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { TRPCError } from "@trpc/server"; import { getHTTPStatusCodeFromError } from "@trpc/server/http"; @@ -27,11 +27,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } const input = getScheduleSchema.parse({ usernameList: slugs, isTeamEvent: parsedIsTeamEvent, ...rest }); const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false; - const availableSlotsService = new AvailableSlotsService(); + + const availableSlotsService = getAvailableSlotsService(); const availableSlots = await availableSlotsService.getAvailableSlots({ ctx: await createContext({ req, res }), input, }); + const slotsInProvidedTimeZone = timeZoneSupported ? Object.keys(availableSlots.slots).reduce( (acc: Record, date) => { diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 8fcc5fd92f..d3ac20104e 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.243", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.249", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/api/v2/src/lib/repositories/PrismaOOORepository.ts b/apps/api/v2/src/lib/repositories/PrismaOOORepository.ts new file mode 100644 index 0000000000..ebc51e51b2 --- /dev/null +++ b/apps/api/v2/src/lib/repositories/PrismaOOORepository.ts @@ -0,0 +1,12 @@ +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +import { PrismaOOORepository } from "@calcom/platform-libraries/repositories"; +import { PrismaClient } from "@calcom/prisma"; + +@Injectable() +export class OOORepository extends PrismaOOORepository { + constructor(private readonly dbWrite: PrismaWriteService) { + super(dbWrite.prisma as unknown as PrismaClient); + } +} diff --git a/apps/api/v2/src/lib/repositories/PrismaScheduleRepository.ts b/apps/api/v2/src/lib/repositories/PrismaScheduleRepository.ts new file mode 100644 index 0000000000..7fdb6ededa --- /dev/null +++ b/apps/api/v2/src/lib/repositories/PrismaScheduleRepository.ts @@ -0,0 +1,12 @@ +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +import { PrismaScheduleRepository } from "@calcom/platform-libraries/repositories"; +import { PrismaClient } from "@calcom/prisma"; + +@Injectable() +export class ScheduleRepository extends PrismaScheduleRepository { + constructor(private readonly dbWrite: PrismaWriteService) { + super(dbWrite.prisma as unknown as PrismaClient); + } +} diff --git a/apps/api/v2/src/lib/services/AvailableSlots.ts b/apps/api/v2/src/lib/services/AvailableSlots.ts new file mode 100644 index 0000000000..8788b9d1da --- /dev/null +++ b/apps/api/v2/src/lib/services/AvailableSlots.ts @@ -0,0 +1,15 @@ +import { OOORepository } from "@/lib/repositories/PrismaOOORepository"; +import { ScheduleRepository } from "@/lib/repositories/PrismaScheduleRepository"; +import { Injectable } from "@nestjs/common"; + +import { AvailableSlotsService as BaseAvailableSlotsService } from "@calcom/platform-libraries/slots"; + +@Injectable() +export class AvailableSlotsService extends BaseAvailableSlotsService { + constructor( + private readonly oooRepoDependency: OOORepository, + private readonly scheduleRepoDependency: ScheduleRepository + ) { + super({ oooRepo: oooRepoDependency, scheduleRepo: scheduleRepoDependency }); + } +} diff --git a/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts b/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts index 2c4c1f0f12..ae21b457bb 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts @@ -1,4 +1,5 @@ import { TRPC_ERROR_CODE, TRPC_ERROR_MAP, TRPCErrorCode } from "@/filters/trpc-exception.filter"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; import { SlotsOutputService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-output.service"; import type { RangeSlots, TimeSlots } from "@/modules/slots/slots-2024-04-15/services/slots-output.service"; import { SlotsWorkerService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-worker.service"; @@ -17,7 +18,6 @@ import { VERSION_2024_08_13, } from "@calcom/platform-constants"; import { TRPCError } from "@calcom/platform-libraries"; -import { AvailableSlotsService } from "@calcom/platform-libraries/slots"; import { RemoveSelectedSlotInput_2024_04_15, ReserveSlotInput_2024_04_15 } from "@calcom/platform-types"; import { ApiResponse, GetAvailableSlotsInput_2024_04_15 } from "@calcom/platform-types"; @@ -31,7 +31,8 @@ export class SlotsController_2024_04_15 { private readonly slotsService: SlotsService_2024_04_15, private readonly config: ConfigService, private readonly slotsOutputService: SlotsOutputService_2024_04_15, - private readonly slotsWorkerService: SlotsWorkerService_2024_04_15 + private readonly slotsWorkerService: SlotsWorkerService_2024_04_15, + private readonly availableSlotsService: AvailableSlotsService ) {} @Post("/reserve") @@ -170,7 +171,7 @@ export class SlotsController_2024_04_15 { : query.isTeamEvent; // Do not use workers in E2E, not supported by TS-JEST - const availableSlotsService = this.config.get("e2e") ? new AvailableSlotsService() : null; + const availableSlotsService = this.config.get("e2e") ? this.availableSlotsService : null; const availableSlots = availableSlotsService ? await availableSlotsService.getAvailableSlots({ diff --git a/apps/api/v2/src/modules/slots/slots-2024-04-15/slots.module.ts b/apps/api/v2/src/modules/slots/slots-2024-04-15/slots.module.ts index 06ccc0988a..44cda28df2 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-04-15/slots.module.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-04-15/slots.module.ts @@ -1,4 +1,7 @@ import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module"; +import { OOORepository as PrismaOOORepository } from "@/lib/repositories/PrismaOOORepository"; +import { ScheduleRepository as PrismaScheduleRepository } from "@/lib/repositories/PrismaScheduleRepository"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { SlotsController_2024_04_15 } from "@/modules/slots/slots-2024-04-15/controllers/slots.controller"; import { SlotsOutputService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-output.service"; @@ -14,6 +17,9 @@ import { Module } from "@nestjs/common"; SlotsService_2024_04_15, SlotsOutputService_2024_04_15, SlotsWorkerService_2024_04_15, + PrismaOOORepository, + PrismaScheduleRepository, + AvailableSlotsService, ], controllers: [SlotsController_2024_04_15], exports: [SlotsService_2024_04_15], diff --git a/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.module.ts b/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.module.ts new file mode 100644 index 0000000000..4dc9af9571 --- /dev/null +++ b/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.module.ts @@ -0,0 +1,24 @@ +// apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots-worker.module.ts +// Needed if ConfigService is used anywhere +import appConfig from "@/config/app"; +import { OOORepository as PrismaOOORepository } from "@/lib/repositories/PrismaOOORepository"; +import { ScheduleRepository as PrismaScheduleRepository } from "@/lib/repositories/PrismaScheduleRepository"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { Module, Logger } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { SentryModule } from "@sentry/nestjs/setup"; + +@Module({ + imports: [ + SentryModule.forRoot(), + ConfigModule.forRoot({ + ignoreEnvFile: true, + isGlobal: true, + load: [appConfig], + }), + PrismaModule, + ], + providers: [Logger, PrismaOOORepository, PrismaScheduleRepository, AvailableSlotsService], +}) +export class SlotsWorkerModule {} diff --git a/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.ts b/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.ts index 818aa1fcd3..c0416df4ec 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-04-15/workers/slots.worker.ts @@ -1,19 +1,65 @@ -import { parentPort } from "worker_threads"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; +import { Logger } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { IncomingMessage } from "http"; +import { parentPort, isMainThread } from "worker_threads"; -import { AvailableSlotsService } from "@calcom/platform-libraries/slots"; +import { SlotsWorkerModule } from "./slots.worker.module"; -const availableSlotsService = new AvailableSlotsService(); +interface ContextForGetSchedule extends Record { + req?: (IncomingMessage & { cookies: Partial<{ [key: string]: string }> }) | undefined; +} -parentPort?.on("message", async (data) => { - try { - const { input, ctx } = data; +interface WorkerMessage { + input: any; // Use a more specific type if available, e.g., TGetScheduleInputSchema + ctx?: ContextForGetSchedule; +} - const result = await availableSlotsService.getAvailableSlots({ input, ctx }); - parentPort?.postMessage({ success: true, data: result }); - } catch (error) { - parentPort?.postMessage({ - success: false, - error: error instanceof Error ? error : new Error("Unknown error"), - }); +interface WorkerResult { + success: boolean; + data?: any; // Use TimeSlots + error?: { + message: string; + code?: string; + stack?: string; + }; +} + +async function bootstrapWorkerApp() { + if (isMainThread) { + console.error("slots-worker-main.ts should only be run in a worker thread."); + process.exit(1); } -}); + + const app = await NestFactory.createApplicationContext(SlotsWorkerModule); + const logger = app.get(Logger); // Get the NestJS Logger instance + logger.log("NestJS worker application bootstrapped successfully."); + + const availableSlotsService = app.get(AvailableSlotsService); + + parentPort?.on("message", async (data: WorkerMessage) => { + try { + const { input, ctx } = data; + + const result = await availableSlotsService.getAvailableSlots({ input, ctx }); + parentPort?.postMessage({ success: true, data: result } as WorkerResult); + } catch (error: any) { + parentPort?.postMessage({ + success: false, + error: error instanceof Error ? error : new Error("Unknown error"), + }); + } + }); + + parentPort?.on("close", async () => { + logger.log("Worker port closed. Shutting down NestJS worker application."); + await app.close(); + }); + + parentPort?.on("disconnect", async () => { + logger.log("Worker port disconnected. Shutting down NestJS worker application."); + await app.close(); + }); +} + +bootstrapWorkerApp(); diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts index 80e45c1bb2..2d5eb87267 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts @@ -1,4 +1,5 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; import { MembershipsRepository } from "@/modules/memberships/memberships.repository"; import { MembershipsService } from "@/modules/memberships/services/memberships.service"; import { TimeSlots } from "@/modules/slots/slots-2024-04-15/services/slots-output.service"; @@ -17,7 +18,6 @@ import { import { DateTime } from "luxon"; import { z } from "zod"; -import { AvailableSlotsService } from "@calcom/platform-libraries/slots"; import { GetSlotsInput_2024_09_04, ReserveSlotInput_2024_09_04 } from "@calcom/platform-types"; import { Booking, EventType } from "@calcom/prisma/client"; @@ -31,8 +31,6 @@ const DEFAULT_RESERVATION_DURATION = 5; @Injectable() export class SlotsService_2024_09_04 { - private readonly availableSlotsService: AvailableSlotsService; - constructor( private readonly eventTypeRepository: EventTypesRepository_2024_06_14, private readonly slotsRepository: SlotsRepository_2024_09_04, @@ -40,10 +38,9 @@ export class SlotsService_2024_09_04 { private readonly slotsInputService: SlotsInputService_2024_09_04, private readonly membershipsService: MembershipsService, private readonly membershipsRepository: MembershipsRepository, - private readonly teamsRepository: TeamsRepository - ) { - this.availableSlotsService = new AvailableSlotsService(); - } + private readonly teamsRepository: TeamsRepository, + private readonly availableSlotsService: AvailableSlotsService + ) {} async getAvailableSlots(query: GetSlotsInput_2024_09_04) { try { diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/slots.module.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/slots.module.ts index 214bd44e67..dbee58ecb6 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-09-04/slots.module.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/slots.module.ts @@ -1,4 +1,7 @@ import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; +import { OOORepository as PrismaOOORepository } from "@/lib/repositories/PrismaOOORepository"; +import { ScheduleRepository as PrismaScheduleRepository } from "@/lib/repositories/PrismaScheduleRepository"; +import { AvailableSlotsService } from "@/lib/services/AvailableSlots"; import { MembershipsModule } from "@/modules/memberships/memberships.module"; import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository"; import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository"; @@ -33,6 +36,9 @@ import { Module } from "@nestjs/common"; OrganizationsUsersRepository, OrganizationsRepository, OrganizationsTeamsRepository, + PrismaOOORepository, + PrismaScheduleRepository, + AvailableSlotsService, ], controllers: [SlotsController_2024_09_04], exports: [SlotsService_2024_09_04], diff --git a/apps/api/v2/tsconfig.json b/apps/api/v2/tsconfig.json index 2c8cc688d5..b4eb8fc148 100644 --- a/apps/api/v2/tsconfig.json +++ b/apps/api/v2/tsconfig.json @@ -25,7 +25,8 @@ "@calcom/platform-libraries/schedules": ["../../../packages/platform/libraries/schedules.ts"], "@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/conferencing": ["../../../packages/platform/libraries/conferencing.ts"], + "@calcom/platform-libraries/repositories": ["../../../packages/platform/libraries/repositories.ts"] }, "incremental": true, "skipLibCheck": true, diff --git a/apps/web/pages/api/get-inbound-dynamic-variables.ts b/apps/web/pages/api/get-inbound-dynamic-variables.ts index ba9fb5b210..b9b3a9bfec 100644 --- a/apps/web/pages/api/get-inbound-dynamic-variables.ts +++ b/apps/web/pages/api/get-inbound-dynamic-variables.ts @@ -5,10 +5,10 @@ import { z } from "zod"; import dayjs from "@calcom/dayjs"; import { ZGetRetellLLMSchema } from "@calcom/features/ee/cal-ai-phone/zod-utils"; import type { TGetRetellLLMSchema } from "@calcom/features/ee/cal-ai-phone/zod-utils"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { fetcher } from "@calcom/lib/retellAIFetcher"; import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import prisma from "@calcom/prisma"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; dayjs.extend(advancedFormat); @@ -86,7 +86,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const startTime = now.startOf("month").toISOString(); const endTime = now.add(2, "month").endOf("month").toISOString(); const orgSlug = eventType?.team?.parent?.slug ?? null; - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); const availableSlots = await availableSlotsService.getAvailableSlots({ input: { diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index a6360d8684..a26d26e72a 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -16,8 +16,8 @@ import { import { describe, vi, test } from "vitest"; import dayjs from "@calcom/dayjs"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { SchedulingType, type BookingStatus } from "@calcom/prisma/enums"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { expect, expectedSlotsForSchedule } from "./getSchedule/expects"; import { setupAndTeardown } from "./getSchedule/setupAndTeardown"; @@ -31,7 +31,7 @@ vi.mock("@calcom/lib/constants", () => ({ })); describe("getSchedule", () => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); // TODO: Move these inside describe('Team Event') diff --git a/apps/web/test/lib/getSchedule/calendarEvents.test.ts b/apps/web/test/lib/getSchedule/calendarEvents.test.ts index 179bc4b3ea..7eb99a49f2 100644 --- a/apps/web/test/lib/getSchedule/calendarEvents.test.ts +++ b/apps/web/test/lib/getSchedule/calendarEvents.test.ts @@ -9,13 +9,13 @@ import { import { describe, test } from "vitest"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { expect, expectedSlotsForSchedule } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; describe("getSchedule", () => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); describe("Calendar event", () => { test("correctly identifies unavailable slots from selected calendars at user level", async () => { diff --git a/apps/web/test/lib/getSchedule/delegation-credential.test.ts b/apps/web/test/lib/getSchedule/delegation-credential.test.ts index ed8997547e..f2f1e8f5f2 100644 --- a/apps/web/test/lib/getSchedule/delegation-credential.test.ts +++ b/apps/web/test/lib/getSchedule/delegation-credential.test.ts @@ -12,13 +12,14 @@ import { expectNoAttemptToGetAvailability } from "../../utils/bookingScenario/ex import { describe, test } from "vitest"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { MembershipRole } from "@calcom/prisma/enums"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { expect, expectedSlotsForSchedule } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; describe("getSchedule", () => { + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); describe("Delegation Credential", () => { @@ -90,7 +91,6 @@ describe("getSchedule", () => { }; await createBookingScenario(scenarioData); - const availableSlotsService = new AvailableSlotsService(); const scheduleForDayWithAGoogleCalendarBooking = await availableSlotsService.getAvailableSlots({ input: { eventTypeId: 1, @@ -182,7 +182,6 @@ describe("getSchedule", () => { }; await createBookingScenario(scenarioData); - const availableSlotsService = new AvailableSlotsService(); const scheduleForDayWithAGoogleCalendarBooking = await availableSlotsService.getAvailableSlots({ input: { eventTypeId: 1, diff --git a/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts b/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts index 8036cd00a9..b424590700 100644 --- a/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts +++ b/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts @@ -8,8 +8,8 @@ import type { ScenarioData } from "../../utils/bookingScenario/bookingScenario"; import { describe, expect, vi, test } from "vitest"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { PeriodType } from "@calcom/prisma/enums"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { expectedSlotsForSchedule } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; @@ -70,7 +70,7 @@ vi.mock("@calcom/lib/constants", () => ({ })); describe("getSchedule", () => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); describe("Future Limits", () => { describe("PeriodType=ROLLING", () => { diff --git a/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts b/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts index 7a9c2a62ae..cccb2655c7 100644 --- a/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts +++ b/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts @@ -10,8 +10,8 @@ import { import { describe, test, vi } from "vitest"; import type { z } from "zod"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import type { getScheduleSchema } from "@calcom/trpc/server/routers/viewer/slots/types"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { expect } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; @@ -89,7 +89,7 @@ async function setupTeamAndFeatures() { } describe("getSchedule", () => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); describe("Restriction Schedule", () => { diff --git a/apps/web/test/lib/getSchedule/selectedSlots.test.ts b/apps/web/test/lib/getSchedule/selectedSlots.test.ts index ef53a7c5cb..edeb3c7108 100644 --- a/apps/web/test/lib/getSchedule/selectedSlots.test.ts +++ b/apps/web/test/lib/getSchedule/selectedSlots.test.ts @@ -10,9 +10,9 @@ import type { IncomingMessage } from "http"; import { describe, test, beforeEach, vi } from "vitest"; import type { z } from "zod"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; import { prisma } from "@calcom/prisma"; import type { getScheduleSchema, GetScheduleOptions } from "@calcom/trpc/server/routers/viewer/slots/types"; -import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; import { expect } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; @@ -62,7 +62,7 @@ const getBaseScenarioData = (): ScheduleScenario => ({ }); describe("getSchedule", () => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); setupAndTeardown(); describe("Reserved Slots", () => { diff --git a/packages/lib/di/containers/available-slots.ts b/packages/lib/di/containers/available-slots.ts new file mode 100644 index 0000000000..f5d81b0296 --- /dev/null +++ b/packages/lib/di/containers/available-slots.ts @@ -0,0 +1,19 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { prismaModule } from "@calcom/prisma/prisma.module"; +import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; + +import { availableSlotsModule } from "../modules/available-slots"; +import { oooRepositoryModule } from "../modules/ooo"; +import { scheduleRepositoryModule } from "../modules/schedule"; + +const container = createContainer(); +container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); +container.load(DI_TOKENS.OOO_REPOSITORY_MODULE, oooRepositoryModule); +container.load(DI_TOKENS.SCHEDULE_REPOSITORY_MODULE, scheduleRepositoryModule); +container.load(DI_TOKENS.AVAILABLE_SLOTS_SERVICE_MODULE, availableSlotsModule); + +export function getAvailableSlotsService() { + return container.get(DI_TOKENS.AVAILABLE_SLOTS_SERVICE); +} diff --git a/packages/lib/di/modules/available-slots.ts b/packages/lib/di/modules/available-slots.ts new file mode 100644 index 0000000000..5e02adc73e --- /dev/null +++ b/packages/lib/di/modules/available-slots.ts @@ -0,0 +1,12 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { IAvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; +import { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; + +import { DI_TOKENS } from "../tokens"; + +export const availableSlotsModule = createModule(); +availableSlotsModule.bind(DI_TOKENS.AVAILABLE_SLOTS_SERVICE).toClass(AvailableSlotsService, { + oooRepo: DI_TOKENS.OOO_REPOSITORY, + scheduleRepo: DI_TOKENS.SCHEDULE_REPOSITORY, +} satisfies Record); diff --git a/packages/lib/di/modules/ooo.ts b/packages/lib/di/modules/ooo.ts new file mode 100644 index 0000000000..5df7c37b55 --- /dev/null +++ b/packages/lib/di/modules/ooo.ts @@ -0,0 +1,7 @@ +import { createModule } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { PrismaOOORepository } from "@calcom/lib/server/repository/ooo"; + +export const oooRepositoryModule = createModule(); +oooRepositoryModule.bind(DI_TOKENS.OOO_REPOSITORY).toClass(PrismaOOORepository, [DI_TOKENS.PRISMA_CLIENT]); diff --git a/packages/lib/di/modules/schedule.ts b/packages/lib/di/modules/schedule.ts new file mode 100644 index 0000000000..3c4f353bf9 --- /dev/null +++ b/packages/lib/di/modules/schedule.ts @@ -0,0 +1,9 @@ +import { createModule } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { ScheduleRepository } from "@calcom/lib/server/repository/schedule"; + +export const scheduleRepositoryModule = createModule(); +scheduleRepositoryModule + .bind(DI_TOKENS.SCHEDULE_REPOSITORY) + .toClass(ScheduleRepository, [DI_TOKENS.PRISMA_CLIENT]); // Maps 'prismaClient' param to PRISMA_CLIENT token diff --git a/packages/lib/di/tokens.ts b/packages/lib/di/tokens.ts new file mode 100644 index 0000000000..157a7c595f --- /dev/null +++ b/packages/lib/di/tokens.ts @@ -0,0 +1,11 @@ +export const DI_TOKENS = { + PRISMA_CLIENT: Symbol("PrismaClient"), + READ_ONLY_PRISMA_CLIENT: Symbol("ReadOnlyPrismaClient"), + PRISMA_MODULE: Symbol("PrismaModule"), + OOO_REPOSITORY: Symbol("OOORepository"), + OOO_REPOSITORY_MODULE: Symbol("OOORepositoryModule"), + SCHEDULE_REPOSITORY: Symbol("ScheduleRepository"), + SCHEDULE_REPOSITORY_MODULE: Symbol("ScheduleRepositoryModule"), + AVAILABLE_SLOTS_SERVICE: Symbol("AvailableSlotsService"), + AVAILABLE_SLOTS_SERVICE_MODULE: Symbol("AvailableSlotsModule"), +}; diff --git a/packages/platform/libraries/package.json b/packages/platform/libraries/package.json index 174125c6d3..6bf5fac7b3 100644 --- a/packages/platform/libraries/package.json +++ b/packages/platform/libraries/package.json @@ -68,6 +68,11 @@ "import": "./dist/conferencing.js", "require": "./dist/conferencing.cjs", "types": "./dist/conferencing.d.ts" + }, + "./repositories": { + "import": "./dist/repositories.js", + "require": "./dist/repositories.cjs", + "types": "./dist/repositories.d.ts" } }, "typesVersions": { @@ -92,6 +97,9 @@ ], "conferencing": [ "dist/conferencing.d.ts" + ], + "repositories": [ + "dist/repositories.d.ts" ] } } diff --git a/packages/platform/libraries/repositories.ts b/packages/platform/libraries/repositories.ts new file mode 100644 index 0000000000..f49d993282 --- /dev/null +++ b/packages/platform/libraries/repositories.ts @@ -0,0 +1,2 @@ +export { PrismaOOORepository } from "@calcom/lib/server/repository/ooo"; +export { ScheduleRepository as PrismaScheduleRepository } from "@calcom/lib/server/repository/schedule"; diff --git a/packages/platform/libraries/vite.config.js b/packages/platform/libraries/vite.config.js index 96ceb96242..42093a2126 100644 --- a/packages/platform/libraries/vite.config.js +++ b/packages/platform/libraries/vite.config.js @@ -25,6 +25,7 @@ export default defineConfig({ workflows: resolve(__dirname, "./workflows.ts"), slots: resolve(__dirname, "./slots.ts"), conferencing: resolve(__dirname, "./conferencing.ts"), + repositories: resolve(__dirname, "./repositories.ts"), }, name: "calcom-lib", fileName: "calcom-lib", diff --git a/packages/prisma/prisma.module.ts b/packages/prisma/prisma.module.ts new file mode 100644 index 0000000000..a85634ada5 --- /dev/null +++ b/packages/prisma/prisma.module.ts @@ -0,0 +1,11 @@ +import { createModule } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; + +import { prisma, readonlyPrisma } from "./index"; + +export const prismaModule = createModule(); + +prismaModule.bind(DI_TOKENS.PRISMA_CLIENT).toFactory(() => prisma, "singleton"); + +prismaModule.bind(DI_TOKENS.READ_ONLY_PRISMA_CLIENT).toFactory(() => readonlyPrisma, "singleton"); diff --git a/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts b/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts index 3db504ae0e..755523ff5d 100644 --- a/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts +++ b/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts @@ -1,6 +1,7 @@ import type { IncomingMessage } from "http"; -import { AvailableSlotsService } from "../slots/util"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; + import type { TGetTeamScheduleInputSchema } from "./getTeamSchedule.schema"; export type GetTeamScheduleOptions = { @@ -13,6 +14,6 @@ interface ContextForGetSchedule extends Record { } export const getTeamScheduleHandler = async ({ ctx, input }: GetTeamScheduleOptions) => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); return await availableSlotsService.getAvailableSlots({ ctx, input }); }; diff --git a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts index 7898a0efbe..e2285bd7e7 100644 --- a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts @@ -1,7 +1,8 @@ +import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; + import type { GetScheduleOptions } from "./types"; -import { AvailableSlotsService } from "./util"; export const getScheduleHandler = async ({ ctx, input }: GetScheduleOptions) => { - const availableSlotsService = new AvailableSlotsService(); + const availableSlotsService = getAvailableSlotsService(); return await availableSlotsService.getAvailableSlots({ ctx, input }); }; diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index e410f3ad07..901c0a2be5 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -44,13 +44,12 @@ import { getTotalBookingDuration } from "@calcom/lib/server/queries/booking"; import { BookingRepository as BookingRepo } from "@calcom/lib/server/repository/booking"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; -import { PrismaOOORepository } from "@calcom/lib/server/repository/ooo"; -import { ScheduleRepository } from "@calcom/lib/server/repository/schedule"; +import type { PrismaOOORepository } from "@calcom/lib/server/repository/ooo"; +import type { ScheduleRepository } from "@calcom/lib/server/repository/schedule"; import { SelectedSlotsRepository } from "@calcom/lib/server/repository/selectedSlots"; import { TeamRepository } from "@calcom/lib/server/repository/team"; import { UserRepository, withSelectedCalendars } from "@calcom/lib/server/repository/user"; import getSlots from "@calcom/lib/slots"; -import prisma from "@calcom/prisma"; import { PeriodType } from "@calcom/prisma/client"; import { SchedulingType } from "@calcom/prisma/enums"; import type { EventBusyDate, EventBusyDetails } from "@calcom/types/Calendar"; @@ -89,7 +88,14 @@ export type GetAvailableSlotsResponse = Awaited< ReturnType<(typeof AvailableSlotsService)["prototype"]["_getAvailableSlots"]> >; +export interface IAvailableSlotsService { + oooRepo: PrismaOOORepository; + scheduleRepo: ScheduleRepository; +} + export class AvailableSlotsService { + constructor(private readonly dependencies: IAvailableSlotsService) {} + private async _getReservedSlotsAndCleanupExpired({ bookerClientUid, usersWithCredentials, @@ -626,8 +632,7 @@ export class AvailableSlotsService { ); private async _getOOODates(startTimeDate: Date, endTimeDate: Date, allUserIds: number[]) { - const oooRepo = new PrismaOOORepository(prisma); - return oooRepo.findManyOOO({ startTimeDate, endTimeDate, allUserIds }); + return this.dependencies.oooRepo.findManyOOO({ startTimeDate, endTimeDate, allUserIds }); } private getOOODates = withReporting(this._getOOODates.bind(this), "getOOODates"); @@ -1054,8 +1059,7 @@ export class AvailableSlotsService { // TODO: DI isRestrictionScheduleEnabled const isRestrictionScheduleFeatureEnabled = await isRestrictionScheduleEnabled(eventType.team?.id); if (eventType.restrictionScheduleId && isRestrictionScheduleFeatureEnabled) { - const scheduleRepo = new ScheduleRepository(prisma); - const restrictionSchedule = await scheduleRepo.findScheduleByIdForBuildDateRanges({ + const restrictionSchedule = await this.dependencies.scheduleRepo.findScheduleByIdForBuildDateRanges({ scheduleId: eventType.restrictionScheduleId, }); if (restrictionSchedule) { diff --git a/yarn.lock b/yarn.lock index a83ee2ac9c..ad53c21f61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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.243" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.249" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -3565,13 +3565,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.243": - version: 0.0.243 - resolution: "@calcom/platform-libraries@npm:0.0.243" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.249": + version: 0.0.249 + resolution: "@calcom/platform-libraries@npm:0.0.249" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: ce0a4778857a62a6df46b1c0b419fbaad1d874d7b0da92d43589348f6e07a7de912703692c9fbc1e21fb2e0bf4d323bcd7eae96b5198376216216c872374cc09 + checksum: 5ca9eec4c30ffa78b817979202dc33fa96f701b97f54ed946311b0f6a35d1a3c6f37947229cef3b3133438134bf82bbea15dfe47b32156f0da6b1f59c9ed9763 languageName: node linkType: hard