From 758143ef7019bbddfe229e8dc88a8bccb81d1aa7 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:39:22 +0300 Subject: [PATCH] refactor: convert EventTypeRepository to use dependency injection pattern (#22397) - Add constructor with PrismaClient dependency injection - Convert all 14 static methods to instance methods using this.prismaClient - Update all usage sites to use two-step instantiation pattern: const eventTypeRepo = new EventTypeRepository(prisma); eventTypeRepo.method(...) - Keep getSelectedCalendarsFromUser as static utility method - Follow same pattern as UserRepository, TeamRepository, SelectedSlotsRepository, and BookingRepository - Maintain all existing method signatures and functionality Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com --- .../web/lib/video/[uid]/getServerSideProps.ts | 3 +- .../routerGetCrmContactOwnerEmail.ts | 4 +- .../credentials/deleteCredential.test.ts | 3 +- packages/lib/event-types/getEventTypeById.ts | 3 +- .../lib/event-types/getEventTypesByViewer.ts | 3 +- packages/lib/server/repository/eventType.ts | 79 +++++++++---------- .../viewer/eventTypes/create.handler.ts | 3 +- .../viewer/eventTypes/duplicate.handler.ts | 3 +- .../getEventTypesFromGroup.handler.ts | 9 ++- .../getTeamAndEventTypeOptions.handler.ts | 3 +- .../server/routers/viewer/eventTypes/util.ts | 2 +- ...rsMatchingAttributeLogicOfRoute.handler.ts | 3 +- .../viewer/slots/isAvailable.handler.ts | 3 +- .../trpc/server/routers/viewer/slots/util.ts | 6 +- 14 files changed, 68 insertions(+), 59 deletions(-) diff --git a/apps/web/lib/video/[uid]/getServerSideProps.ts b/apps/web/lib/video/[uid]/getServerSideProps.ts index ff71b8cf3e..c54e6e8b0b 100644 --- a/apps/web/lib/video/[uid]/getServerSideProps.ts +++ b/apps/web/lib/video/[uid]/getServerSideProps.ts @@ -94,7 +94,8 @@ const checkIfUserIsHost = async ({ return booking.user?.id === sessionUserId; } - const eventType = await EventTypeRepository.findByIdWithUserAccess({ + const eventTypeRepo = new EventTypeRepository(prisma); + const eventType = await eventTypeRepo.findByIdWithUserAccess({ id: booking.eventTypeId, userId: sessionUserId, }); diff --git a/packages/app-store/routing-forms/lib/crmRouting/routerGetCrmContactOwnerEmail.ts b/packages/app-store/routing-forms/lib/crmRouting/routerGetCrmContactOwnerEmail.ts index 9d2ddd8003..dea8504f99 100644 --- a/packages/app-store/routing-forms/lib/crmRouting/routerGetCrmContactOwnerEmail.ts +++ b/packages/app-store/routing-forms/lib/crmRouting/routerGetCrmContactOwnerEmail.ts @@ -1,5 +1,6 @@ import { getCRMContactOwnerForRRLeadSkip } from "@calcom/app-store/_utils/CRMRoundRobinSkip"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; +import { prisma } from "@calcom/prisma"; import { SchedulingType } from "@calcom/prisma/enums"; import type { LocalRoute } from "../../types/types"; @@ -32,7 +33,8 @@ export default async function routerGetCrmContactOwnerEmail({ // Determine if the action is an event type redirect if (action.type !== "eventTypeRedirectUrl" || !action.eventTypeId) return null; - const eventType = await EventTypeRepository.findByIdIncludeHostsAndTeam({ id: action.eventTypeId }); + const eventTypeRepo = new EventTypeRepository(prisma); + const eventType = await eventTypeRepo.findByIdIncludeHostsAndTeam({ id: action.eventTypeId }); if (!eventType || eventType.schedulingType !== SchedulingType.ROUND_ROBIN) return null; const eventTypeMetadata = eventType.metadata; diff --git a/packages/features/credentials/deleteCredential.test.ts b/packages/features/credentials/deleteCredential.test.ts index dde8e204a9..eb060bacd4 100644 --- a/packages/features/credentials/deleteCredential.test.ts +++ b/packages/features/credentials/deleteCredential.test.ts @@ -61,7 +61,8 @@ describe("deleteCredential", () => { await setupCredential({ userId: user.id, type: "zoom_video", appId: "zoom" }); await handleDeleteCredential({ userId: user.id, userMetadata: user.metadata, credentialId: 123 }); - const eventTypeQuery = await EventTypeRepository.findAllByUserId({ userId: user.id }); + const eventTypeRepo = new EventTypeRepository(prisma); + const eventTypeQuery = await eventTypeRepo.findAllByUserId({ userId: user.id }); // Ensure that the event type with the deleted app was converted back to daily const changedEventType = eventTypeQuery.find((eventType) => eventType.id === 1)?.locations; diff --git a/packages/lib/event-types/getEventTypeById.ts b/packages/lib/event-types/getEventTypeById.ts index b0fce94a0a..886fa85dc3 100644 --- a/packages/lib/event-types/getEventTypeById.ts +++ b/packages/lib/event-types/getEventTypeById.ts @@ -52,7 +52,8 @@ export const getEventTypeById = async ({ isPlatformManaged: true, } satisfies Prisma.UserSelect; - const rawEventType = await EventTypeRepository.findById({ id: eventTypeId, userId }); + const eventTypeRepo = new EventTypeRepository(prisma); + const rawEventType = await eventTypeRepo.findById({ id: eventTypeId, userId }); if (!rawEventType) { if (isTrpcCall) { diff --git a/packages/lib/event-types/getEventTypesByViewer.ts b/packages/lib/event-types/getEventTypesByViewer.ts index 3115eb7407..cfbbc2c4a8 100644 --- a/packages/lib/event-types/getEventTypesByViewer.ts +++ b/packages/lib/event-types/getEventTypesByViewer.ts @@ -51,6 +51,7 @@ export const getEventTypesByViewer = async (user: User, filters?: Filters, forRo if (isFilterSet && filters?.upIds && !isUpIdInFilter) { shouldListUserEvents = true; } + const eventTypeRepo = new EventTypeRepository(prisma); const [profileMemberships, profileEventTypes] = await Promise.all([ MembershipRepository.findAllByUpIdIncludeTeamWithMembersAndEventTypes( { @@ -63,7 +64,7 @@ export const getEventTypesByViewer = async (user: User, filters?: Filters, forRo } ), shouldListUserEvents - ? EventTypeRepository.findAllByUpId( + ? eventTypeRepo.findAllByUpId( { upId: userProfile.upId, userId: user.id, diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index f4b9fe33ee..b725b6b0fc 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -1,8 +1,9 @@ import type { EventType as PrismaEventType } from "@prisma/client"; -import type { Prisma } from "@prisma/client"; import logger from "@calcom/lib/logger"; +import type { PrismaClient } from "@calcom/prisma"; import { prisma, availabilityUserSelect } from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import { EventTypeMetaDataSchema, rrSegmentQueryValueSchema } from "@calcom/prisma/zod-utils"; @@ -68,7 +69,9 @@ function usersWithSelectedCalendars< } export class EventTypeRepository { - private static generateCreateEventTypeData = (eventTypeCreateData: IEventType) => { + constructor(private prismaClient: PrismaClient) {} + + private generateCreateEventTypeData = (eventTypeCreateData: IEventType) => { const { userId, profileId, @@ -122,8 +125,8 @@ export class EventTypeRepository { }; }; - static async create(data: IEventType) { - return await prisma.eventType.create({ + async create(data: IEventType) { + return await this.prismaClient.eventType.create({ data: this.generateCreateEventTypeData(data), include: { calVideoSettings: true, @@ -131,13 +134,13 @@ export class EventTypeRepository { }); } - static async createMany(data: IEventType[]) { - return await prisma.eventType.createMany({ + async createMany(data: IEventType[]) { + return await this.prismaClient.eventType.createMany({ data: data.map((d) => this.generateCreateEventTypeData(d)), }); } - static async findAllByUpId( + async findAllByUpId( { upId, userId }: { upId: string; userId: number }, { orderBy, @@ -184,7 +187,7 @@ export class EventTypeRepository { if (!profileId) { // Lookup is by userId - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { userId: lookupTarget.id, ...where, @@ -200,7 +203,7 @@ export class EventTypeRepository { if (profile?.movedFromUser) { // Because the user has been moved to this profile, we need to get all user events except those that belong to some other profile // This is because those event-types that are created after moving to profile would have profileId but existing event-types would have profileId set to null - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { OR: [ // Existing events @@ -228,7 +231,7 @@ export class EventTypeRepository { orderBy, }); } else { - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { OR: [ { @@ -252,7 +255,7 @@ export class EventTypeRepository { } } - static async findAllByUpIdWithMinimalData( + async findAllByUpIdWithMinimalData( { upId, userId }: { upId: string; userId: number }, { orderBy, @@ -288,7 +291,7 @@ export class EventTypeRepository { if (!profileId) { // Lookup is by userId - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { userId: lookupTarget.id, ...where, @@ -304,7 +307,7 @@ export class EventTypeRepository { if (profile?.movedFromUser) { // Because the user has been moved to this profile, we need to get all user events except those that belong to some other profile // This is because those event-types that are created after moving to profile would have profileId but existing event-types would have profileId set to null - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { OR: [ // Existing events @@ -332,7 +335,7 @@ export class EventTypeRepository { orderBy, }); } else { - return await prisma.eventType.findMany({ + return await this.prismaClient.eventType.findMany({ where: { OR: [ { @@ -356,7 +359,7 @@ export class EventTypeRepository { } } - static async findTeamEventTypes({ + async findTeamEventTypes({ teamId, parentId, userId, @@ -397,7 +400,7 @@ export class EventTypeRepository { }, }; - const teamMembership = await prisma.membership.findFirst({ + const teamMembership = await this.prismaClient.membership.findFirst({ where: { OR: [ { @@ -437,16 +440,16 @@ export class EventTypeRepository { }); } - static async findAllByUserId({ userId }: { userId: number }) { - return await prisma.eventType.findMany({ + async findAllByUserId({ userId }: { userId: number }) { + return await this.prismaClient.eventType.findMany({ where: { userId, }, }); } - static async findTitleById({ id }: { id: number }) { - return await prisma.eventType.findUnique({ + async findTitleById({ id }: { id: number }) { + return await this.prismaClient.eventType.findUnique({ where: { id, }, @@ -456,8 +459,8 @@ export class EventTypeRepository { }); } - static async findByIdWithUserAccess({ id, userId }: { id: number; userId: number }) { - return await prisma.eventType.findUnique({ + async findByIdWithUserAccess({ id, userId }: { id: number; userId: number }) { + return await this.prismaClient.eventType.findUnique({ where: { id, OR: [{ userId }, { hosts: { some: { userId } } }, { users: { some: { id: userId } } }], @@ -465,7 +468,7 @@ export class EventTypeRepository { }); } - static async findById({ id, userId }: { id: number; userId: number }) { + async findById({ id, userId }: { id: number; userId: number }) { const userSelect = { name: true, avatarUrl: true, @@ -716,7 +719,7 @@ export class EventTypeRepository { // This is more efficient than using a complex join with team.members in the query const userTeamIds = await MembershipRepository.findUserTeamIds({ userId }); - return await prisma.eventType.findFirst({ + return await this.prismaClient.eventType.findFirst({ where: { AND: [ { @@ -745,24 +748,16 @@ export class EventTypeRepository { }); } - static async findByIdMinimal({ id }: { id: number }) { - return await prisma.eventType.findUnique({ + async findByIdMinimal({ id }: { id: number }) { + return await this.prismaClient.eventType.findUnique({ where: { id, }, }); } - static async findFirstEventTypeId({ - slug, - teamId, - userId, - }: { - slug: string; - teamId?: number; - userId?: number; - }) { - return prisma.eventType.findFirst({ + async findFirstEventTypeId({ slug, teamId, userId }: { slug: string; teamId?: number; userId?: number }) { + return this.prismaClient.eventType.findFirst({ where: { slug, ...(teamId ? { teamId } : {}), @@ -774,8 +769,8 @@ export class EventTypeRepository { }); } - static async findByIdIncludeHostsAndTeam({ id }: { id: number }) { - const eventType = await prisma.eventType.findUnique({ + async findByIdIncludeHostsAndTeam({ id }: { id: number }) { + const eventType = await this.prismaClient.eventType.findUnique({ where: { id, }, @@ -818,8 +813,8 @@ export class EventTypeRepository { }; } - static async findAllByTeamIdIncludeManagedEventTypes({ teamId }: { teamId?: number }) { - return await prisma.eventType.findMany({ + async findAllByTeamIdIncludeManagedEventTypes({ teamId }: { teamId?: number }) { + return await this.prismaClient.eventType.findMany({ where: { OR: [ { @@ -835,8 +830,8 @@ export class EventTypeRepository { }); } - static async findForSlots({ id }: { id: number }) { - const eventType = await prisma.eventType.findUnique({ + async findForSlots({ id }: { id: number }) { + const eventType = await this.prismaClient.eventType.findUnique({ where: { id, }, diff --git a/packages/trpc/server/routers/viewer/eventTypes/create.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/create.handler.ts index 0f22089f0b..c96103b0c8 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/create.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/create.handler.ts @@ -126,7 +126,8 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => { const profile = ctx.user.profile; try { - const eventType = await EventTypeRepository.create({ + const eventTypeRepo = new EventTypeRepository(ctx.prisma); + const eventType = await eventTypeRepo.create({ ...data, profileId: profile.id, }); diff --git a/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts index 86a2bd19d5..56dc2dfecf 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts @@ -151,7 +151,8 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => { } } - const newEventType = await EventTypeRepository.create(data); + const eventTypeRepo = new EventTypeRepository(prisma); + const newEventType = await eventTypeRepo.create(data); // Create custom inputs if (customInputs) { diff --git a/packages/trpc/server/routers/viewer/eventTypes/getEventTypesFromGroup.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getEventTypesFromGroup.handler.ts index 2d8a561c98..d24d7232d9 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getEventTypesFromGroup.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getEventTypesFromGroup.handler.ts @@ -21,7 +21,7 @@ type GetByViewerOptions = { input: TGetEventTypesFromGroupSchema; }; -type EventType = Awaited>[number]; +type EventType = Awaited>[number]; type MappedEventType = Awaited>; export const getEventTypesFromGroup = async ({ @@ -47,6 +47,7 @@ export const getEventTypesFromGroup = async ({ !isFilterSet || isUpIdInFilter || (isFilterSet && filters?.upIds && !isUpIdInFilter); const eventTypes: EventType[] = []; + const eventTypeRepo = new EventTypeRepository(ctx.prisma); if (shouldListUserEvents || !teamId) { const baseQueryConditions = { @@ -56,7 +57,7 @@ export const getEventTypesFromGroup = async ({ }; const [nonChildEventTypes, childEventTypes] = await Promise.all([ - EventTypeRepository.findAllByUpId( + eventTypeRepo.findAllByUpId( { upId: userProfile.upId, userId: ctx.user.id, @@ -78,7 +79,7 @@ export const getEventTypesFromGroup = async ({ cursor, } ), - EventTypeRepository.findAllByUpId( + eventTypeRepo.findAllByUpId( { upId: userProfile.upId, userId: ctx.user.id, @@ -117,7 +118,7 @@ export const getEventTypesFromGroup = async ({ if (teamId) { const teamEventTypes = - (await EventTypeRepository.findTeamEventTypes({ + (await eventTypeRepo.findTeamEventTypes({ teamId, parentId, userId: ctx.user.id, diff --git a/packages/trpc/server/routers/viewer/eventTypes/getTeamAndEventTypeOptions.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getTeamAndEventTypeOptions.handler.ts index 984b59d2e3..2448e0fd7d 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getTeamAndEventTypeOptions.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getTeamAndEventTypeOptions.handler.ts @@ -53,6 +53,7 @@ export const getTeamAndEventTypeOptions = async ({ ctx, input }: GetTeamAndEvent const parentOrgHasLockedEventTypes = profile?.organization?.organizationSettings?.lockEventTypeCreationForUsers; + const eventTypeRepo = new EventTypeRepository(ctx.prisma); const [profileMemberships, profileEventTypes] = await Promise.all([ MembershipRepository.findAllByUpIdIncludeMinimalEventTypes( { @@ -67,7 +68,7 @@ export const getTeamAndEventTypeOptions = async ({ ctx, input }: GetTeamAndEvent ), teamId ? [] - : EventTypeRepository.findAllByUpIdWithMinimalData( + : eventTypeRepo.findAllByUpIdWithMinimalData( { upId: userProfile.upId, userId: user.id, diff --git a/packages/trpc/server/routers/viewer/eventTypes/util.ts b/packages/trpc/server/routers/viewer/eventTypes/util.ts index dee4917996..0a9977ad29 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/util.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/util.ts @@ -14,7 +14,7 @@ import { TRPCError } from "@trpc/server"; import authedProcedure from "../../../procedures/authedProcedure"; import type { TUpdateInputSchema } from "./types"; -type EventType = Awaited>[number]; +type EventType = Awaited>[number]; export const eventOwnerProcedure = authedProcedure .input( diff --git a/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts b/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts index 1e14d039f5..0b1f363abf 100644 --- a/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts +++ b/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts @@ -177,7 +177,8 @@ export const findTeamMembersMatchingAttributeLogicOfRouteHandler = async ({ }); } - const eventType = await EventTypeRepository.findByIdIncludeHostsAndTeam({ id: eventTypeId }); + const eventTypeRepo = new EventTypeRepository(prisma); + const eventType = await eventTypeRepo.findByIdIncludeHostsAndTeam({ id: eventTypeId }); if (!eventType) { throw new TRPCError({ diff --git a/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts b/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts index 1884af8390..8e86b47810 100644 --- a/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/isAvailable.handler.ts @@ -32,7 +32,8 @@ export const isAvailableHandler = async ({ const { slots, eventTypeId } = input; // Get event type details for time bounds validation - const eventType = await EventTypeRepository.findByIdMinimal({ id: eventTypeId }); + const eventTypeRepo = new EventTypeRepository(ctx.prisma); + const eventType = await eventTypeRepo.findByIdMinimal({ id: eventTypeId }); if (!eventType) { throw new HttpError({ statusCode: 404, message: "Event type not found" }); diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index d5be18837f..87be8eebc7 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -211,7 +211,8 @@ export class AvailableSlotsService { return null; } - return await EventTypeRepository.findForSlots({ id: eventTypeId }); + const eventTypeRepo = new EventTypeRepository(prisma); + return await eventTypeRepo.findForSlots({ id: eventTypeId }); } private getEventType = withReporting(this._getEventType.bind(this), "getEventType"); @@ -281,7 +282,8 @@ export class AvailableSlotsService { organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false } ); } - const eventType = await EventTypeRepository.findFirstEventTypeId({ slug: eventTypeSlug, teamId, userId }); + const eventTypeRepo = new EventTypeRepository(prisma); + const eventType = await eventTypeRepo.findFirstEventTypeId({ slug: eventTypeSlug, teamId, userId }); if (!eventType) { throw new TRPCError({ code: "NOT_FOUND" }); }