From 80ac1182422f306644285faf19f5f491c3e0a024 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:14:26 +0000 Subject: [PATCH] refactor: convert SelectedSlotsRepository to use dependency injection pattern (#22387) * refactor: convert TeamRepository to use dependency injection pattern - Add constructor injection for PrismaClient in TeamRepository - Convert all static methods to instance methods using this.prismaClient - Update all usage sites to use two-step pattern: const teamRepo = new TeamRepository(prisma); teamRepo.method(...) - Optimize instance reuse within same function scopes where possible - Update test files to work with new instance pattern - Preserve all existing functionality and method signatures Follows the same dependency injection pattern as UserRepository refactoring Co-Authored-By: morgan@cal.com * refactor: convert SelectedSlotsRepository to use dependency injection pattern - Add constructor injection for PrismaClient/PrismaTransaction - Convert all static methods to instance methods using this.prismaClient - Update usage sites to use two-step instantiation pattern: const slotsRepo = new SelectedSlotsRepository(prisma); slotsRepo.method(...) - Optimize instance reuse in util.ts for multiple method calls - Update test mocks to handle new constructor-based pattern - Add explicit type annotations to resolve TypeScript inference issues Follows same dependency injection pattern as UserRepository and TeamRepository refactorings. Co-Authored-By: morgan@cal.com * chore: bump platform libs * fix: devin mistake prisma client * fix: devin typing mistakes * chore: bump platform libs --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> --- apps/api/v2/package.json | 2 +- .../lib/server/repository/selectedSlots.ts | 24 ++++++++++--------- .../viewer/slots/isAvailable.handler.ts | 5 ++-- .../viewer/slots/reserveSlot.handler.test.ts | 6 ++--- .../viewer/slots/reserveSlot.handler.ts | 3 ++- .../trpc/server/routers/viewer/slots/util.ts | 5 ++-- yarn.lock | 10 ++++---- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index e96aeca30e..8e6fd564f8 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.253", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.255", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/packages/lib/server/repository/selectedSlots.ts b/packages/lib/server/repository/selectedSlots.ts index c99d85a890..f86873f48d 100644 --- a/packages/lib/server/repository/selectedSlots.ts +++ b/packages/lib/server/repository/selectedSlots.ts @@ -1,6 +1,6 @@ import type { Prisma } from "@prisma/client"; -import { prisma } from "@calcom/prisma"; +import type { PrismaClient } from "@calcom/prisma"; type WhereCondition = { slotUtcStartDate?: Date | string; @@ -23,17 +23,19 @@ export type TimeSlot = { }; export class SelectedSlotsRepository { - static async findMany({ where, select }: FindManyArgs) { - return await prisma.selectedSlots.findMany({ where, select }); + constructor(private prismaClient: PrismaClient) {} + + async findMany({ where, select }: FindManyArgs) { + return await this.prismaClient.selectedSlots.findMany({ where, select }); } - static async findFirst({ where }: { where: Prisma.SelectedSlotsWhereInput }) { - return await prisma.selectedSlots.findFirst({ + async findFirst({ where }: { where: Prisma.SelectedSlotsWhereInput }) { + return await this.prismaClient.selectedSlots.findFirst({ where, }); } - static async findReservedByOthers({ + async findReservedByOthers({ slot, eventTypeId, uid, @@ -53,7 +55,7 @@ export class SelectedSlotsRepository { }); } - static async findManyReservedByOthers(slots: TimeSlot[], eventTypeId: number, uid: string) { + async findManyReservedByOthers(slots: TimeSlot[], eventTypeId: number, uid: string) { return await this.findMany({ where: { OR: slots.map((slot) => ({ @@ -71,14 +73,14 @@ export class SelectedSlotsRepository { }); } - static async findManyUnexpiredSlots({ + async findManyUnexpiredSlots({ userIds, currentTimeInUtc, }: { userIds: number[]; currentTimeInUtc: string; }) { - return prisma.selectedSlots.findMany({ + return this.prismaClient.selectedSlots.findMany({ where: { userId: { in: userIds }, releaseAt: { gt: currentTimeInUtc }, @@ -95,14 +97,14 @@ export class SelectedSlotsRepository { }); } - static async deleteManyExpiredSlots({ + async deleteManyExpiredSlots({ eventTypeId, currentTimeInUtc, }: { eventTypeId: number; currentTimeInUtc: string; }) { - return prisma.selectedSlots.deleteMany({ + return this.prismaClient.selectedSlots.deleteMany({ where: { eventTypeId: { equals: eventTypeId }, releaseAt: { lt: currentTimeInUtc }, diff --git a/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts b/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts index 130f5c4989..1884af8390 100644 --- a/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts @@ -40,9 +40,8 @@ export const isAvailableHandler = async ({ // Check each slot's availability // Without uid, we must not check for reserved slots because if uuid isn't set in cookie yet, but it is going to be through reserveSlot request soon, we could consider the slot as reserved accidentally. - const reservedSlots = uid - ? await SelectedSlotsRepository.findManyReservedByOthers(slots, eventTypeId, uid) - : []; + const slotsRepo = new SelectedSlotsRepository(ctx.prisma); + const reservedSlots = uid ? await slotsRepo.findManyReservedByOthers(slots, eventTypeId, uid) : []; // Map all slots to their availability status const slotsWithStatus: TIsAvailableOutputSchema["slots"] = slots.map((slot) => { diff --git a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts index 01df877a0c..b2a006f97a 100644 --- a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts +++ b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts @@ -10,12 +10,12 @@ import { vi, describe, it, expect, afterEach } from "vitest"; // We alias the module path once we know WEBAPP_URL has been configured. const dynamicImportHandler = async () => await import("./reserveSlot.handler"); -// The repository static method is used to check for an existing reservation by someone else. +// The repository instance method is used to check for an existing reservation by someone else. // To keep this unit test isolated from the database layer, we stub this to always resolve falsey. vi.mock("@calcom/lib/server/repository/selectedSlots", () => ({ - SelectedSlotsRepository: { + SelectedSlotsRepository: vi.fn().mockImplementation(() => ({ findReservedByOthers: vi.fn().mockResolvedValue(null), - }, + })), })); // A tiny helper to build a canned handler context with stubbed Prisma methods. diff --git a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts index 22f27f1392..7b9c4d8d4e 100644 --- a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts @@ -64,7 +64,8 @@ export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) => } // Check for existing reservations for the same slot - const reservedBySomeoneElse = await SelectedSlotsRepository.findReservedByOthers({ + const slotsRepo = new SelectedSlotsRepository(prisma); + const reservedBySomeoneElse = await slotsRepo.findReservedByOthers({ slot: { utcStartIso: slotUtcStartDate, utcEndIso: slotUtcEndDate, diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 4c58e327a9..492891a482 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -107,9 +107,10 @@ export class AvailableSlotsService { eventTypeId: number; }) { const currentTimeInUtc = dayjs.utc().format(); + const slotsRepo = new SelectedSlotsRepository(prisma); const unexpiredSelectedSlots = - (await SelectedSlotsRepository.findManyUnexpiredSlots({ + (await slotsRepo.findManyUnexpiredSlots({ userIds: usersWithCredentials.map((user) => user.id), currentTimeInUtc, })) || []; @@ -123,7 +124,7 @@ export class AvailableSlotsService { return reservedSlots; async function _cleanupExpiredSlots({ eventTypeId }: { eventTypeId: number }) { - await SelectedSlotsRepository.deleteManyExpiredSlots({ eventTypeId, currentTimeInUtc }); + await slotsRepo.deleteManyExpiredSlots({ eventTypeId, currentTimeInUtc }); } } diff --git a/yarn.lock b/yarn.lock index 5c6a40b307..191cf46544 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.253" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.255" "@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.253": - version: 0.0.253 - resolution: "@calcom/platform-libraries@npm:0.0.253" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.255": + version: 0.0.255 + resolution: "@calcom/platform-libraries@npm:0.0.255" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 797435f9a72750dc7f8bc6d3b52b1b1fa93701451907f4ae29028403619365649bdc7c5632ac1db326bd6ed1b90e27544d14ae108e042fe18eaeac4ca11e8606 + checksum: 50ec08f31a1a30fde270335f93065554ff93d74ab96d059262cec44f9efcae2036c2a7c26177cb3a1a2f708dabea4736b26110629ab08691e97a62f9d3464cd9 languageName: node linkType: hard