From b76e3d1fde4150f95044ed4a2e5616f5ea62dd51 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 12:47:08 +0000 Subject: [PATCH 1/6] refactor: use dependency injection for InsightsBookingService - Add DI tokens for InsightsBookingService and module - Rename InsightsBookingService to InsightsBookingBaseService - Create InsightsBookingService DI interface with create method - Add DI module and container for InsightsBookingService - Update tRPC router to use getInsightsBookingService DI container - Update test file to use InsightsBookingBaseService - Update documentation to reflect new DI pattern Follows the same dependency injection pattern established for InsightsRoutingService in PR #22677 Co-Authored-By: eunjae@cal.com --- .../insights/HOW_TO_ADD_BOOKING_CHARTS.md | 30 +- .../features/insights/server/trpc-router.ts | 285 +++++++++++++++--- .../lib/di/containers/insights-booking.ts | 27 ++ packages/lib/di/modules/insights-booking.ts | 11 + packages/lib/di/tokens.ts | 2 + .../insightsBooking.integration-test.ts | 22 +- ...ightsBooking.ts => insightsBookingBase.ts} | 2 +- .../lib/server/service/insightsBookingDI.ts | 29 ++ 8 files changed, 342 insertions(+), 66 deletions(-) create mode 100644 packages/lib/di/containers/insights-booking.ts create mode 100644 packages/lib/di/modules/insights-booking.ts rename packages/lib/server/service/{insightsBooking.ts => insightsBookingBase.ts} (99%) create mode 100644 packages/lib/server/service/insightsBookingDI.ts diff --git a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md index d20c8a12e758af..ab69f9591e4baf 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: ```typescript // packages/features/insights/server/trpc-router.ts @@ -118,7 +118,23 @@ export const insightsRouter = router({ myNewChartData: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getMyNewChartData(); @@ -129,13 +145,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/insightsBookingBase.ts +export class InsightsBookingBaseService { // ... existing methods async getMyNewChartData() { @@ -168,7 +184,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 cd980ec852f4e4..79ad0ac4761ec6 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"; @@ -312,50 +312,49 @@ export interface IResultTeamList { 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 } }, - input: z.infer, - dateTarget: "createdAt" | "startTime" = "createdAt" -) { - const { scope, selectedTeamId, eventTypeId, memberUserId, startDate, endDate } = input; - - return new InsightsBookingService({ - prisma: ctx.insightsDb, - options: { - scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(selectedTeamId && { teamId: selectedTeamId }), - }, - filters: { - ...(eventTypeId && { eventTypeId }), - ...(memberUserId && { memberUserId }), - dateRange: { - target: dateTarget, - startDate, - endDate, - }, - }, - }); -} export const insightsRouter = router({ bookingKPIStats: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const currentPeriodService = createInsightsBookingService(ctx, input); + const currentPeriodService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); // Get current period stats const currentStats = await currentPeriodService.getBookingStats(); // Calculate previous period dates and create service for previous period const previousPeriodDates = currentPeriodService.calculatePreviousPeriodDates(); - const previousPeriodService = createInsightsBookingService(ctx, { - ...input, - startDate: previousPeriodDates.startDate, - endDate: previousPeriodDates.endDate, + const previousPeriodService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: previousPeriodDates.startDate, + endDate: previousPeriodDates.endDate, + }, + }, }); // Get previous period stats @@ -457,7 +456,23 @@ export const insightsRouter = router({ weekStart: ctx.user.weekStart, }); - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getEventTrendsStats({ timeZone, @@ -470,7 +485,23 @@ export const insightsRouter = router({ popularEvents: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getPopularEventsStats(); @@ -483,7 +514,23 @@ export const insightsRouter = router({ .query(async ({ ctx, input }) => { const { startDate, endDate, timeZone } = input; - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { const timeView = getTimeView(startDate, endDate); @@ -537,7 +584,23 @@ export const insightsRouter = router({ membersWithMostCancelledBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getMembersStatsWithCount("cancelled", "DESC"); @@ -548,7 +611,23 @@ export const insightsRouter = router({ membersWithMostBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getMembersStatsWithCount("all", "DESC"); @@ -559,7 +638,23 @@ export const insightsRouter = router({ membersWithLeastBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getMembersStatsWithCount("all", "ASC"); @@ -749,13 +844,45 @@ export const insightsRouter = router({ recentRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); return await insightsBookingService.getRecentRatingsStats(); }), membersWithMostNoShow: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getMembersStatsWithCount("noShow", "DESC"); @@ -766,13 +893,45 @@ export const insightsRouter = router({ membersWithHighestRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); return await insightsBookingService.getMembersRatingStats("DESC"); }), membersWithLowestRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); return await insightsBookingService.getMembersRatingStats("ASC"); }), rawData: userBelongsToTeamProcedure @@ -785,7 +944,23 @@ export const insightsRouter = router({ .query(async ({ ctx, input }) => { const { limit, offset } = input; - const insightsBookingService = createInsightsBookingService(ctx, input); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "createdAt", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getCsvData({ @@ -1025,7 +1200,23 @@ export const insightsRouter = router({ .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { const { timeZone } = input; - const insightsBookingService = createInsightsBookingService(ctx, input, "startTime"); + const insightsBookingService = getInsightsBookingService({ + options: { + scope: input.scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(input.selectedTeamId && { teamId: input.selectedTeamId }), + }, + filters: { + ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), + ...(input.memberUserId && { memberUserId: input.memberUserId }), + dateRange: { + target: "startTime", + startDate: input.startDate, + endDate: input.endDate, + }, + }, + }); try { return await insightsBookingService.getBookingsByHourStats({ diff --git a/packages/lib/di/containers/insights-booking.ts b/packages/lib/di/containers/insights-booking.ts new file mode 100644 index 00000000000000..3b8b4037245195 --- /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/insightsBookingBase"; +import type { InsightsBookingService } from "@calcom/lib/server/service/insightsBookingDI"; +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..c472c418b810ca --- /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/insightsBookingDI"; +import { InsightsBookingService } from "@calcom/lib/server/service/insightsBookingDI"; + +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 b3ba3d1d32c6fd..200697d9012dcc 100644 --- a/packages/lib/di/tokens.ts +++ b/packages/lib/di/tokens.ts @@ -22,4 +22,6 @@ 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"), }; diff --git a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts b/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts index fabd45af99b6be..507b944321b4d7 100644 --- a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts +++ b/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import prisma from "@calcom/prisma"; import { BookingStatus, MembershipRole } from "@calcom/prisma/enums"; -import { InsightsBookingService } from "../../service/insightsBooking"; +import { InsightsBookingBaseService } from "../insightsBookingBase"; const NOTHING_CONDITION = Prisma.sql`1=0`; @@ -201,7 +201,7 @@ async function createTestData({ describe("InsightsBookingService Integration Tests", () => { describe("Authorization Conditions", () => { it("should return NOTHING for invalid options", async () => { - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: null as any, }); @@ -232,7 +232,7 @@ describe("InsightsBookingService Integration Tests", () => { }, }); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "org", @@ -260,7 +260,7 @@ describe("InsightsBookingService Integration Tests", () => { orgRole: MembershipRole.OWNER, }); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", @@ -281,7 +281,7 @@ describe("InsightsBookingService Integration Tests", () => { orgRole: MembershipRole.OWNER, }); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "team", @@ -318,7 +318,7 @@ describe("InsightsBookingService Integration Tests", () => { const team2 = testData.additionalTeams[0]; // First additional team const team3 = testData.additionalTeams[1]; // Second additional team - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "org", @@ -350,7 +350,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should return null when no filters are provided", async () => { const testData = await createTestData(); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", @@ -368,7 +368,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should build eventTypeId filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", @@ -391,7 +391,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should build memberUserId filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", @@ -412,7 +412,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should combine multiple filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", @@ -465,7 +465,7 @@ describe("InsightsBookingService Integration Tests", () => { }, }); - const service = new InsightsBookingService({ + const service = new InsightsBookingBaseService({ prisma, options: { scope: "user", diff --git a/packages/lib/server/service/insightsBooking.ts b/packages/lib/server/service/insightsBookingBase.ts similarity index 99% rename from packages/lib/server/service/insightsBooking.ts rename to packages/lib/server/service/insightsBookingBase.ts index d76e02a3731ca2..8da8cf4cfc4f91 100644 --- a/packages/lib/server/service/insightsBooking.ts +++ b/packages/lib/server/service/insightsBookingBase.ts @@ -120,7 +120,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/insightsBookingDI.ts b/packages/lib/server/service/insightsBookingDI.ts new file mode 100644 index 00000000000000..02f08ff207589d --- /dev/null +++ b/packages/lib/server/service/insightsBookingDI.ts @@ -0,0 +1,29 @@ +import type { readonlyPrisma } from "@calcom/prisma"; + +import { + InsightsBookingBaseService, + type InsightsBookingServicePublicOptions, + type InsightsBookingServiceFilterOptions, +} from "./insightsBookingBase"; + +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, + }); + } +} From 8995227aaa63c80b925b60afd2d69ed74a0d467f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 12:53:28 +0000 Subject: [PATCH 2/6] refactor: restore createInsightsBookingService helper using DI internally - Keep createInsightsBookingService helper function for cleaner API - Use getInsightsBookingService DI container internally - Maintain same function signature and behavior - All existing calls continue to work unchanged Co-Authored-By: eunjae@cal.com --- .../features/insights/server/trpc-router.ts | 227 +++--------------- 1 file changed, 40 insertions(+), 187 deletions(-) diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index 79ad0ac4761ec6..a9a65b2396bc6f 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -312,6 +312,35 @@ export interface IResultTeamList { 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 } }, + input: z.infer, + dateTarget: "createdAt" | "startTime" = "createdAt" +) { + const { scope, selectedTeamId, eventTypeId, memberUserId, startDate, endDate } = input; + + return getInsightsBookingService({ + options: { + scope, + userId: ctx.user.id, + orgId: ctx.user.organizationId ?? 0, + ...(selectedTeamId && { teamId: selectedTeamId }), + }, + filters: { + ...(eventTypeId && { eventTypeId }), + ...(memberUserId && { memberUserId }), + dateRange: { + target: dateTarget, + startDate, + endDate, + }, + }, + }); +} + export const insightsRouter = router({ bookingKPIStats: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) @@ -456,23 +485,7 @@ export const insightsRouter = router({ weekStart: ctx.user.weekStart, }); - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getEventTrendsStats({ timeZone, @@ -485,23 +498,7 @@ export const insightsRouter = router({ popularEvents: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getPopularEventsStats(); @@ -514,23 +511,7 @@ export const insightsRouter = router({ .query(async ({ ctx, input }) => { const { startDate, endDate, timeZone } = input; - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { const timeView = getTimeView(startDate, endDate); @@ -584,23 +565,7 @@ export const insightsRouter = router({ membersWithMostCancelledBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getMembersStatsWithCount("cancelled", "DESC"); @@ -611,23 +576,7 @@ export const insightsRouter = router({ membersWithMostBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getMembersStatsWithCount("all", "DESC"); @@ -638,23 +587,7 @@ export const insightsRouter = router({ membersWithLeastBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getMembersStatsWithCount("all", "ASC"); @@ -844,45 +777,13 @@ export const insightsRouter = router({ recentRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); return await insightsBookingService.getRecentRatingsStats(); }), membersWithMostNoShow: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getMembersStatsWithCount("noShow", "DESC"); @@ -893,45 +794,13 @@ export const insightsRouter = router({ membersWithHighestRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); return await insightsBookingService.getMembersRatingStats("DESC"); }), membersWithLowestRatings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); return await insightsBookingService.getMembersRatingStats("ASC"); }), rawData: userBelongsToTeamProcedure @@ -944,23 +813,7 @@ export const insightsRouter = router({ .query(async ({ ctx, input }) => { const { limit, offset } = input; - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getCsvData({ From 76a9aebd779107a8ab158ce46177f1fab663d6ca Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:14:09 +0000 Subject: [PATCH 3/6] refactor: rename service files with proper capitalization - Rename insightsBookingBase.ts to InsightsBookingBaseService.ts - Rename insightsBookingDI.ts to InsightsBookingDIService.ts - Update all import statements to use new file names - Maintain existing handler functionality using createInsightsBookingService(ctx, input) Co-Authored-By: eunjae@cal.com --- packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md | 2 +- packages/lib/di/containers/insights-booking.ts | 4 ++-- packages/lib/di/modules/insights-booking.ts | 4 ++-- .../{insightsBookingBase.ts => InsightsBookingBaseService.ts} | 0 .../{insightsBookingDI.ts => InsightsBookingDIService.ts} | 2 +- .../service/__tests__/insightsBooking.integration-test.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename packages/lib/server/service/{insightsBookingBase.ts => InsightsBookingBaseService.ts} (100%) rename packages/lib/server/service/{insightsBookingDI.ts => InsightsBookingDIService.ts} (94%) diff --git a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md index ab69f9591e4baf..a3a2811c182ac4 100644 --- a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md +++ b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md @@ -150,7 +150,7 @@ export const insightsRouter = router({ Add your new method to the `InsightsBookingBaseService` class: ```typescript -// packages/lib/server/service/insightsBookingBase.ts +// packages/lib/server/service/InsightsBookingBaseService.ts export class InsightsBookingBaseService { // ... existing methods diff --git a/packages/lib/di/containers/insights-booking.ts b/packages/lib/di/containers/insights-booking.ts index 3b8b4037245195..20b9e640ffa6a7 100644 --- a/packages/lib/di/containers/insights-booking.ts +++ b/packages/lib/di/containers/insights-booking.ts @@ -5,8 +5,8 @@ import type { InsightsBookingServicePublicOptions, InsightsBookingServiceFilterOptions, InsightsBookingBaseService, -} from "@calcom/lib/server/service/insightsBookingBase"; -import type { InsightsBookingService } from "@calcom/lib/server/service/insightsBookingDI"; +} 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"; diff --git a/packages/lib/di/modules/insights-booking.ts b/packages/lib/di/modules/insights-booking.ts index c472c418b810ca..2d431c4a718dc3 100644 --- a/packages/lib/di/modules/insights-booking.ts +++ b/packages/lib/di/modules/insights-booking.ts @@ -1,7 +1,7 @@ import { createModule } from "@evyweb/ioctopus"; -import type { IInsightsBookingService } from "@calcom/lib/server/service/insightsBookingDI"; -import { InsightsBookingService } from "@calcom/lib/server/service/insightsBookingDI"; +import type { IInsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; +import { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; import { DI_TOKENS } from "../tokens"; diff --git a/packages/lib/server/service/insightsBookingBase.ts b/packages/lib/server/service/InsightsBookingBaseService.ts similarity index 100% rename from packages/lib/server/service/insightsBookingBase.ts rename to packages/lib/server/service/InsightsBookingBaseService.ts diff --git a/packages/lib/server/service/insightsBookingDI.ts b/packages/lib/server/service/InsightsBookingDIService.ts similarity index 94% rename from packages/lib/server/service/insightsBookingDI.ts rename to packages/lib/server/service/InsightsBookingDIService.ts index 02f08ff207589d..df052520f0a652 100644 --- a/packages/lib/server/service/insightsBookingDI.ts +++ b/packages/lib/server/service/InsightsBookingDIService.ts @@ -4,7 +4,7 @@ import { InsightsBookingBaseService, type InsightsBookingServicePublicOptions, type InsightsBookingServiceFilterOptions, -} from "./insightsBookingBase"; +} from "./InsightsBookingBaseService"; export interface IInsightsBookingService { prisma: typeof readonlyPrisma; diff --git a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts b/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts index 507b944321b4d7..a20e8e7a1cbfbb 100644 --- a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts +++ b/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import prisma from "@calcom/prisma"; import { BookingStatus, MembershipRole } from "@calcom/prisma/enums"; -import { InsightsBookingBaseService } from "../insightsBookingBase"; +import { InsightsBookingBaseService } from "../InsightsBookingBaseService"; const NOTHING_CONDITION = Prisma.sql`1=0`; From 5695cf93462f594c04ae49d16ce64fa0701bfe47 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 29 Jul 2025 16:12:28 +0200 Subject: [PATCH 4/6] revert some changes --- .../features/insights/server/trpc-router.ts | 58 +++---------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index a9a65b2396bc6f..48a82d17c5af7f 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -316,7 +316,7 @@ 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" ) { @@ -345,45 +345,17 @@ export const insightsRouter = router({ bookingKPIStats: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const currentPeriodService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const currentPeriodService = createInsightsBookingService(ctx, input); // Get current period stats const currentStats = await currentPeriodService.getBookingStats(); // Calculate previous period dates and create service for previous period const previousPeriodDates = currentPeriodService.calculatePreviousPeriodDates(); - const previousPeriodService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: previousPeriodDates.startDate, - endDate: previousPeriodDates.endDate, - }, - }, + const previousPeriodService = createInsightsBookingService(ctx, { + ...input, + startDate: previousPeriodDates.startDate, + endDate: previousPeriodDates.endDate, }); // Get previous period stats @@ -1053,23 +1025,7 @@ export const insightsRouter = router({ .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { const { timeZone } = input; - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "startTime", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + const insightsBookingService = createInsightsBookingService(ctx, input, "startTime"); try { return await insightsBookingService.getBookingsByHourStats({ From f988e0cc09c1303dbd4d3011920aad82f4a0ba28 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 29 Jul 2025 16:15:45 +0200 Subject: [PATCH 5/6] rename --- ...nsightsBookingService.integration-test.ts} | 22 +++++++++---------- ...nsightsRoutingService.integration-test.ts} | 0 2 files changed, 11 insertions(+), 11 deletions(-) rename packages/lib/server/service/__tests__/{insightsBooking.integration-test.ts => InsightsBookingService.integration-test.ts} (95%) rename packages/lib/server/service/__tests__/{insightsRouting.integration-test.ts => InsightsRoutingService.integration-test.ts} (100%) diff --git a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts b/packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts similarity index 95% rename from packages/lib/server/service/__tests__/insightsBooking.integration-test.ts rename to packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts index a20e8e7a1cbfbb..28f9954748dd6f 100644 --- a/packages/lib/server/service/__tests__/insightsBooking.integration-test.ts +++ b/packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import prisma from "@calcom/prisma"; import { BookingStatus, MembershipRole } from "@calcom/prisma/enums"; -import { InsightsBookingBaseService } from "../InsightsBookingBaseService"; +import { InsightsBookingBaseService as InsightsBookingService } from "../InsightsBookingBaseService"; const NOTHING_CONDITION = Prisma.sql`1=0`; @@ -201,7 +201,7 @@ async function createTestData({ describe("InsightsBookingService Integration Tests", () => { describe("Authorization Conditions", () => { it("should return NOTHING for invalid options", async () => { - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: null as any, }); @@ -232,7 +232,7 @@ describe("InsightsBookingService Integration Tests", () => { }, }); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "org", @@ -260,7 +260,7 @@ describe("InsightsBookingService Integration Tests", () => { orgRole: MembershipRole.OWNER, }); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", @@ -281,7 +281,7 @@ describe("InsightsBookingService Integration Tests", () => { orgRole: MembershipRole.OWNER, }); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "team", @@ -318,7 +318,7 @@ describe("InsightsBookingService Integration Tests", () => { const team2 = testData.additionalTeams[0]; // First additional team const team3 = testData.additionalTeams[1]; // Second additional team - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "org", @@ -350,7 +350,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should return null when no filters are provided", async () => { const testData = await createTestData(); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", @@ -368,7 +368,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should build eventTypeId filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", @@ -391,7 +391,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should build memberUserId filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", @@ -412,7 +412,7 @@ describe("InsightsBookingService Integration Tests", () => { it("should combine multiple filter conditions", async () => { const testData = await createTestData(); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", @@ -465,7 +465,7 @@ describe("InsightsBookingService Integration Tests", () => { }, }); - const service = new InsightsBookingBaseService({ + const service = new InsightsBookingService({ prisma, options: { scope: "user", 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 From d85c0b9784473d6ee070474346efdfe8ee1986c1 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 29 Jul 2025 16:20:32 +0200 Subject: [PATCH 6/6] update doc --- .../insights/HOW_TO_ADD_BOOKING_CHARTS.md | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md index a3a2811c182ac4..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 `getInsightsBookingService()` DI container: +Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function: ```typescript // packages/features/insights/server/trpc-router.ts @@ -118,23 +118,8 @@ export const insightsRouter = router({ myNewChartData: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ ctx, input }) => { - const insightsBookingService = getInsightsBookingService({ - options: { - scope: input.scope, - userId: ctx.user.id, - orgId: ctx.user.organizationId ?? 0, - ...(input.selectedTeamId && { teamId: input.selectedTeamId }), - }, - filters: { - ...(input.eventTypeId && { eventTypeId: input.eventTypeId }), - ...(input.memberUserId && { memberUserId: input.memberUserId }), - dateRange: { - target: "createdAt", - startDate: input.startDate, - endDate: input.endDate, - }, - }, - }); + // `createInsightsBookingService` is defined at the root level in this file + const insightsBookingService = createInsightsBookingService(ctx, input); try { return await insightsBookingService.getMyNewChartData();