diff --git a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md index d20c8a12e758af..70f045797b3b8a 100644 --- a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md +++ b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md @@ -103,7 +103,7 @@ export default function InsightsPage() { ## Step 4: Create tRPC Handler -Add the tRPC endpoint in the insights router using the `createInsightsBookingService()` helper: +Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function: ```typescript // packages/features/insights/server/trpc-router.ts @@ -118,6 +118,7 @@ export const insightsRouter = router({ myNewChartData: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { + // `createInsightsBookingService` is defined at the root level in this file const insightsBookingService = createInsightsBookingService(ctx, input); try { @@ -129,13 +130,13 @@ export const insightsRouter = router({ }); ``` -## Step 5: Add Service Method to InsightsBookingService +## Step 5: Add Service Method to InsightsBookingBaseService -Add your new method to the `InsightsBookingService` class: +Add your new method to the `InsightsBookingBaseService` class: ```typescript -// packages/lib/server/service/insightsBooking.ts -export class InsightsBookingService { +// packages/lib/server/service/InsightsBookingBaseService.ts +export class InsightsBookingBaseService { // ... existing methods async getMyNewChartData() { @@ -168,7 +169,7 @@ export class InsightsBookingService { ## Best Practices -1. **Use `createInsightsBookingService()`**: Always use the helper function for consistent service creation +1. **Use `getInsightsBookingService()`**: Always use the DI container function for consistent service creation 2. **Raw SQL for Performance**: Use `$queryRaw` for complex aggregations and better performance 3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions 4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError` diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index aef25e394e1b3e..cf858336f8100b 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -9,8 +9,8 @@ import { routingRepositoryBaseInputSchema, bookingRepositoryBaseInputSchema, } from "@calcom/features/insights/server/raw-data.schema"; +import { getInsightsBookingService } from "@calcom/lib/di/containers/insights-booking"; import { getInsightsRoutingService } from "@calcom/lib/di/containers/insights-routing"; -import { InsightsBookingService } from "@calcom/lib/server/service/insightsBooking"; import type { readonlyPrisma } from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure"; @@ -316,14 +316,13 @@ const BATCH_SIZE = 1000; // Adjust based on your needs * Helper function to create InsightsBookingService with standardized parameters */ function createInsightsBookingService( - ctx: { insightsDb: typeof readonlyPrisma; user: { id: number; organizationId: number | null } }, + ctx: { user: { id: number; organizationId: number | null } }, input: z.infer, dateTarget: "createdAt" | "startTime" = "createdAt" ) { const { scope, selectedTeamId, startDate, endDate, columnFilters } = input; - return new InsightsBookingService({ - prisma: ctx.insightsDb, + return getInsightsBookingService({ options: { scope, userId: ctx.user.id, diff --git a/packages/lib/di/containers/insights-booking.ts b/packages/lib/di/containers/insights-booking.ts new file mode 100644 index 00000000000000..20b9e640ffa6a7 --- /dev/null +++ b/packages/lib/di/containers/insights-booking.ts @@ -0,0 +1,27 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import type { + InsightsBookingServicePublicOptions, + InsightsBookingServiceFilterOptions, + InsightsBookingBaseService, +} from "@calcom/lib/server/service/InsightsBookingBaseService"; +import type { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; +import { prismaModule } from "@calcom/prisma/prisma.module"; + +import { insightsBookingModule } from "../modules/insights-booking"; + +export function getInsightsBookingService({ + options, + filters, +}: { + options: InsightsBookingServicePublicOptions; + filters?: InsightsBookingServiceFilterOptions; +}): InsightsBookingBaseService { + const container = createContainer(); + container.load(DI_TOKENS.READ_ONLY_PRISMA_CLIENT, prismaModule); + container.load(DI_TOKENS.INSIGHTS_BOOKING_SERVICE_MODULE, insightsBookingModule); + + const diService = container.get(DI_TOKENS.INSIGHTS_BOOKING_SERVICE); + return diService.create({ options, filters }); +} diff --git a/packages/lib/di/modules/insights-booking.ts b/packages/lib/di/modules/insights-booking.ts new file mode 100644 index 00000000000000..2d431c4a718dc3 --- /dev/null +++ b/packages/lib/di/modules/insights-booking.ts @@ -0,0 +1,11 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { IInsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; +import { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; + +import { DI_TOKENS } from "../tokens"; + +export const insightsBookingModule = createModule(); +insightsBookingModule.bind(DI_TOKENS.INSIGHTS_BOOKING_SERVICE).toClass(InsightsBookingService, { + prisma: DI_TOKENS.READ_ONLY_PRISMA_CLIENT, +} satisfies Record); diff --git a/packages/lib/di/tokens.ts b/packages/lib/di/tokens.ts index 58df8998069784..0f56cb9e5d7144 100644 --- a/packages/lib/di/tokens.ts +++ b/packages/lib/di/tokens.ts @@ -22,6 +22,8 @@ export const DI_TOKENS = { AVAILABLE_SLOTS_SERVICE_MODULE: Symbol("AvailableSlotsModule"), INSIGHTS_ROUTING_SERVICE: Symbol("InsightsRoutingService"), INSIGHTS_ROUTING_SERVICE_MODULE: Symbol("InsightsRoutingServiceModule"), + INSIGHTS_BOOKING_SERVICE: Symbol("InsightsBookingService"), + INSIGHTS_BOOKING_SERVICE_MODULE: Symbol("InsightsBookingServiceModule"), FEATURES_REPOSITORY: Symbol("FeaturesRepository"), FEATURES_REPOSITORY_MODULE: Symbol("FeaturesRepositoryModule"), CACHE_SERVICE: Symbol("CacheService"), diff --git a/packages/lib/server/service/insightsBooking.ts b/packages/lib/server/service/InsightsBookingBaseService.ts similarity index 99% rename from packages/lib/server/service/insightsBooking.ts rename to packages/lib/server/service/InsightsBookingBaseService.ts index 14fb22840a9618..180c19950dea56 100644 --- a/packages/lib/server/service/insightsBooking.ts +++ b/packages/lib/server/service/InsightsBookingBaseService.ts @@ -135,7 +135,7 @@ const NOTHING_CONDITION = Prisma.sql`1=0`; const bookingDataKeys = new Set(Object.keys(bookingDataSchema.shape)); -export class InsightsBookingService { +export class InsightsBookingBaseService { private prisma: typeof readonlyPrisma; private options: InsightsBookingServiceOptions | null; private filters: InsightsBookingServiceFilterOptions | null; diff --git a/packages/lib/server/service/InsightsBookingDIService.ts b/packages/lib/server/service/InsightsBookingDIService.ts new file mode 100644 index 00000000000000..df052520f0a652 --- /dev/null +++ b/packages/lib/server/service/InsightsBookingDIService.ts @@ -0,0 +1,29 @@ +import type { readonlyPrisma } from "@calcom/prisma"; + +import { + InsightsBookingBaseService, + type InsightsBookingServicePublicOptions, + type InsightsBookingServiceFilterOptions, +} from "./InsightsBookingBaseService"; + +export interface IInsightsBookingService { + prisma: typeof readonlyPrisma; +} + +export class InsightsBookingService { + constructor(private readonly dependencies: IInsightsBookingService) {} + + create({ + options, + filters, + }: { + options: InsightsBookingServicePublicOptions; + filters?: InsightsBookingServiceFilterOptions; + }): InsightsBookingBaseService { + return new InsightsBookingBaseService({ + prisma: this.dependencies.prisma, + options, + filters, + }); + } +} diff --git a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts b/packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts similarity index 99% rename from packages/lib/server/service/__tests__/insightsBooking.integration-test.ts rename to packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts index 1d9940b3602452..8c45f0d927c29b 100644 --- a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts +++ b/packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts @@ -6,7 +6,7 @@ import { ColumnFilterType } from "@calcom/features/data-table/lib/types"; import prisma from "@calcom/prisma"; import { BookingStatus, MembershipRole } from "@calcom/prisma/enums"; -import { InsightsBookingService } from "../../service/insightsBooking"; +import { InsightsBookingBaseService as InsightsBookingService } from "../InsightsBookingBaseService"; const NOTHING_CONDITION = Prisma.sql`1=0`; diff --git a/packages/lib/server/service/__tests__/insightsRouting.integration-test.ts b/packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts similarity index 100% rename from packages/lib/server/service/__tests__/insightsRouting.integration-test.ts rename to packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts