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 <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 <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 <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2025-07-10 15:14:26 +00:00
1 parent efd15c5a3e
commit 80ac118242
7 files changed
+29 -26

No files matched your search

+1 -1
View File
@@ -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": "*",
+13 -11
View File
@@ -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 },
@@ -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) => {
@@ -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.
@@ -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,
@@ -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 });
}
}
+5 -5
View File
@@ -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