From e4c47640fc4fdb9645c98859f8cd6f59aa1f1b4e 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 12:11:14 +0000 Subject: [PATCH] refactor: convert UserRepository to use dependency injection pattern (#22360) * refactor: convert UserRepository to use dependency injection pattern - Convert all static methods to public instance methods - Add constructor that takes PrismaClient parameter - Update all usage sites to use new instantiation pattern: new UserRepository(prisma).method() - Follow same pattern as PrismaOOORepository for consistency - Maintain all existing method logic and signatures unchanged - Update 125+ files across the codebase to adapt to new pattern Co-Authored-By: morgan@cal.com * optimize: reuse UserRepository instances within same function scope - Create single UserRepository instance per function scope - Reuse instance for multiple method calls within same function - Reduces object instantiation overhead and improves performance - Apply optimization pattern consistently across codebase Co-Authored-By: morgan@cal.com * fix: repository * fixup! fix: repository * fixup! fixup! fix: repository * fixup! fixup! fixup! fix: repository * fix: update test mocking strategies for UserRepository dependency injection - Convert static method mocks to instance method mocks in userCreationService.test.ts - Update vi.spyOn calls to work with constructor injection pattern in getAllCredentials.test.ts - Fix UserRepository mocking in getRoutedUrl.test.ts to use constructor injection - Ensure consistent mocking approach across all test files - Fix 'UserRepository is not a constructor' errors in tests Co-Authored-By: morgan@cal.com * feat: optimize UserRepository instance reuse and add SessionUser type - Reuse UserRepository instance in OrganizationRepository.createWithNonExistentOwner - Add comprehensive SessionUser type definition for type safety - Improve type constraints in enrichUserWithTheProfile and enrichUserWithItsProfile - Ensure proper return types with profile information Co-Authored-By: morgan@cal.com * fix: make UserRepository mocking strategy more robust for CI environments - Add defensive checks for vi.mocked() to handle CI environment differences - Ensure mockImplementation is available before calling it - Maintain consistent mocking pattern across all test files - Fix 'Cannot read properties of undefined' error in CI Co-Authored-By: morgan@cal.com * fixup! fix: make UserRepository mocking strategy more robust for CI environments * refactor: convert direct UserRepository instantiations to two-step pattern - Change await new UserRepository(prisma).method(...) to const userRepo = new UserRepository(prisma); await userRepo.method(...) - Optimize instance reuse within same function scopes - Apply pattern consistently across all modified files in PR - Fix type errors in organization.ts and sessionMiddleware.ts Co-Authored-By: morgan@cal.com * refactor: complete two-step UserRepository pattern for remaining files - Apply two-step instantiation pattern to all remaining modified files in PR - Ensure consistent UserRepository usage across entire codebase - Maintain instance reuse optimization within function scopes Co-Authored-By: morgan@cal.com * 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> --- .../v1/pages/api/connected-calendars/_get.ts | 4 +- apps/api/v2/package.json | 2 +- .../apps/(homepage)/page.tsx | 4 +- .../admin/users/[id]/edit/page.tsx | 7 +- .../app/api/availability/calendar/route.ts | 6 +- apps/web/app/api/customer-card/route.ts | 6 +- .../[[...step]]/getServerSideProps.ts | 3 +- .../d/[link]/[slug]/getServerSideProps.tsx | 3 +- .../[[...step]]/getServerSideProps.tsx | 4 +- .../reschedule/[uid]/getServerSideProps.ts | 3 +- .../web/lib/video/[uid]/getServerSideProps.ts | 3 +- .../lib/[user]/[type]/getServerSideProps.ts | 3 +- .../server/lib/[user]/getServerSideProps.ts | 7 +- .../_utils/oauth/updateProfilePhotoGoogle.ts | 7 +- .../_utils/throwIfNotHaveAdminAccessToTeam.ts | 4 +- .../getServerSidePropsSingleForm.ts | 3 +- .../pages/routing-link/getServerSideProps.ts | 3 +- .../trpc/getResponseWithFormFields.handler.ts | 3 +- .../features/auth/lib/getServerSession.ts | 3 +- .../features/auth/lib/next-auth-options.ts | 13 +- .../getAllCredentials.test.ts | 84 +++++++- .../getAllCredentials.ts | 2 +- .../lib/handleNewBooking/loadUsers.ts | 2 +- .../credentials/deleteCredential.test.ts | 5 +- .../features/ee/dsync/lib/handleUserEvents.ts | 2 +- .../lib/OrganizationPaymentService.ts | 2 +- .../createOrganizationFromOnboarding.ts | 2 +- .../lib/server/orgCreationUtils.ts | 2 +- .../features/eventtypes/lib/getPublicEvent.ts | 8 +- .../lib/createAProfileForAnExistingUser.ts | 3 +- packages/lib/delegationCredential/server.ts | 3 +- packages/lib/event-types/getEventTypeById.ts | 7 +- .../lib/event-types/getEventTypesByViewer.ts | 56 ++--- packages/lib/server/getRoutedUrl.test.ts | 20 +- packages/lib/server/getRoutedUrl.ts | 4 +- packages/lib/server/queries/teams/index.ts | 5 +- packages/lib/server/repository/booking.ts | 3 +- .../lib/server/repository/organization.ts | 3 +- packages/lib/server/repository/user.test.ts | 6 +- packages/lib/server/repository/user.ts | 192 +++++++++++------- .../service/userCreationService.test.ts | 56 ++++- .../lib/server/service/userCreationService.ts | 4 +- .../server/middlewares/sessionMiddleware.ts | 8 +- .../admin/whitelistUserWorkflows.handler.ts | 3 +- .../apps/appCredentialsByType.handler.ts | 2 +- ...amMembersMatchingAttributeLogic.handler.ts | 2 +- .../team/listTeamAvailability.handler.ts | 3 +- .../viewer/bookings/editLocation.handler.ts | 2 +- .../server/routers/viewer/eventTypes/util.ts | 5 +- .../server/routers/viewer/me/get.handler.ts | 2 +- .../routers/viewer/me/myStats.handler.ts | 3 +- .../viewer/organizations/create.handler.ts | 4 +- .../organizations/createTeams.handler.ts | 2 +- .../organizations/listMembers.handler.ts | 2 +- .../listOtherTeamMembers.handler.ts | 2 +- ...rsMatchingAttributeLogicOfRoute.handler.ts | 3 +- .../trpc/server/routers/viewer/slots/util.ts | 5 +- .../inviteMember/inviteMember.handler.ts | 6 +- .../viewer/teams/inviteMember/utils.ts | 4 +- .../viewer/teams/legacyListMembers.handler.ts | 2 +- .../viewer/teams/listMembers.handler.ts | 2 +- yarn.lock | 10 +- 62 files changed, 425 insertions(+), 209 deletions(-) diff --git a/apps/api/v1/pages/api/connected-calendars/_get.ts b/apps/api/v1/pages/api/connected-calendars/_get.ts index ae37cb28c5..d8cb6835a5 100644 --- a/apps/api/v1/pages/api/connected-calendars/_get.ts +++ b/apps/api/v1/pages/api/connected-calendars/_get.ts @@ -105,7 +105,9 @@ async function getHandler(req: NextApiRequest) { const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId]; - const usersWithCalendars = await UserRepository.findManyByIdsIncludeDestinationAndSelectedCalendars({ + const usersWithCalendars = await new UserRepository( + prisma + ).findManyByIdsIncludeDestinationAndSelectedCalendars({ ids: userIds, }); diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index d3ac20104e..16ba151377 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.249", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.252", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/web/app/(use-page-wrapper)/apps/(homepage)/page.tsx b/apps/web/app/(use-page-wrapper)/apps/(homepage)/page.tsx index a6293ffbd3..bd18bbbdbb 100644 --- a/apps/web/app/(use-page-wrapper)/apps/(homepage)/page.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/(homepage)/page.tsx @@ -4,6 +4,7 @@ import { cookies, headers } from "next/headers"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import type { AppCategories } from "@calcom/prisma/enums"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; @@ -25,7 +26,8 @@ const ServerPage = async () => { const session = await getServerSession({ req }); let appStore, userAdminTeamsIds: number[]; if (session?.user?.id) { - const userAdminTeams = await UserRepository.getUserAdminTeams(session.user.id); + const userRepo = new UserRepository(prisma); + const userAdminTeams = await userRepo.getUserAdminTeams({ userId: session.user.id }); userAdminTeamsIds = userAdminTeams?.teams?.map(({ team }) => team.id) ?? []; appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeamsIds); } else { diff --git a/apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/users/[id]/edit/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/users/[id]/edit/page.tsx index be1e7dc61c..4114e904cf 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/users/[id]/edit/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/users/[id]/edit/page.tsx @@ -6,6 +6,7 @@ import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequir import { UsersEditView } from "@calcom/features/ee/users/pages/users-edit-view"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; const userIdSchema = z.object({ id: z.coerce.number() }); @@ -21,7 +22,8 @@ export const generateMetadata = async ({ params }: { params: Params }) => { ); } - const user = await UserRepository.adminFindById(input.data.id); + const userRepo = new UserRepository(prisma); + const user = await userRepo.adminFindById(input.data.id); return await _generateMetadata( (t) => `${t("editing_user")}: ${user.username}`, @@ -37,7 +39,8 @@ const Page = async ({ params }: { params: Params }) => { if (!input.success) throw new Error("Invalid access"); - const user = await UserRepository.adminFindById(input.data.id); + const userRepo = new UserRepository(prisma); + const user = await userRepo.adminFindById(input.data.id); const t = await getTranslate(); return ( diff --git a/apps/web/app/api/availability/calendar/route.ts b/apps/web/app/api/availability/calendar/route.ts index 23bd9840bd..b84dab2446 100644 --- a/apps/web/app/api/availability/calendar/route.ts +++ b/apps/web/app/api/availability/calendar/route.ts @@ -11,6 +11,7 @@ import { HttpError } from "@calcom/lib/http-error"; import notEmpty from "@calcom/lib/notEmpty"; import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; @@ -29,7 +30,10 @@ async function authMiddleware() { throw new HttpError({ statusCode: 401, message: "Not authenticated" }); } - const userWithCredentials = await UserRepository.findUserWithCredentials({ id: session.user.id }); + const userRepo = new UserRepository(prisma); + const userWithCredentials = await userRepo.findUserWithCredentials({ + id: session.user.id, + }); if (!userWithCredentials) { throw new HttpError({ statusCode: 401, message: "Not authenticated" }); diff --git a/apps/web/app/api/customer-card/route.ts b/apps/web/app/api/customer-card/route.ts index 9dcdaef88b..64a6f484f7 100644 --- a/apps/web/app/api/customer-card/route.ts +++ b/apps/web/app/api/customer-card/route.ts @@ -8,6 +8,7 @@ import { z } from "zod"; import dayjs from "@calcom/dayjs"; import { timeZoneSchema } from "@calcom/lib/dayjs/timeZone.schema"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { userMetadata } from "@calcom/prisma/zod-utils"; import { CardComponent } from "@lib/plain/card-components"; @@ -474,7 +475,8 @@ async function handler(request: NextRequest) { // Validate request body const { cardKeys, customer } = inputSchema.parse(requestBody); - const user = await UserRepository.findByEmail({ email: customer.email }); + const userRepo = new UserRepository(prisma); + const user = await userRepo.findByEmail({ email: customer.email }); if (!user) { return NextResponse.json({ @@ -502,7 +504,7 @@ async function handler(request: NextRequest) { } // Fetch team details including userId and team name - const teamMemberships = await UserRepository.findTeamsByUserId({ userId: user.id }); + const teamMemberships = await userRepo.findTeamsByUserId({ userId: user.id }); const firstTeam = teamMemberships.teams[0] ?? null; // Parse user metadata diff --git a/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts b/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts index 7617fb33c9..594c61b4ef 100644 --- a/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts +++ b/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts @@ -18,7 +18,8 @@ import { STEPS } from "~/apps/installation/[[...step]]/constants"; import type { OnboardingPageProps, TEventTypeGroup } from "~/apps/installation/[[...step]]/step-view"; const getUser = async (userId: number) => { - const userAdminTeams = await UserRepository.getUserAdminTeams(userId); + const userRepo = new UserRepository(prisma); + const userAdminTeams = await userRepo.getUserAdminTeams({ userId }); if (!userAdminTeams?.id) { return null; diff --git a/apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx b/apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx index 9d0d01a2cb..b4dfcd5bbb 100644 --- a/apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx +++ b/apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx @@ -101,7 +101,8 @@ async function getUserPageProps(context: GetServerSidePropsContext) { name = profileUsername || username; - const [user] = await UserRepository.findUsersByUsername({ + const userRepo = new UserRepository(prisma); + const [user] = await userRepo.findUsersByUsername({ usernameList: [name], orgSlug: org, }); diff --git a/apps/web/lib/getting-started/[[...step]]/getServerSideProps.tsx b/apps/web/lib/getting-started/[[...step]]/getServerSideProps.tsx index a65419b9fe..a21142b937 100644 --- a/apps/web/lib/getting-started/[[...step]]/getServerSideProps.tsx +++ b/apps/web/lib/getting-started/[[...step]]/getServerSideProps.tsx @@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; export const getServerSideProps = async (context: GetServerSidePropsContext) => { const { req } = context; @@ -12,7 +13,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => return { redirect: { permanent: false, destination: "/auth/login" } }; } - const user = await UserRepository.findUserTeams({ + const userRepo = new UserRepository(prisma); + const user = await userRepo.findUserTeams({ id: session.user.id, }); diff --git a/apps/web/lib/reschedule/[uid]/getServerSideProps.ts b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts index 4267076416..f18ea866df 100644 --- a/apps/web/lib/reschedule/[uid]/getServerSideProps.ts +++ b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts @@ -101,8 +101,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const eventType = booking.eventType ? booking.eventType : getDefaultEvent(dynamicEventSlugRef); + const userRepo = new UserRepository(prisma); const enrichedBookingUser = booking.user - ? await UserRepository.enrichUserWithItsProfile({ user: booking.user }) + ? await userRepo.enrichUserWithItsProfile({ user: booking.user }) : null; const eventUrl = await buildEventUrlFromBooking({ diff --git a/apps/web/lib/video/[uid]/getServerSideProps.ts b/apps/web/lib/video/[uid]/getServerSideProps.ts index 17f18a315b..e86d2a5205 100644 --- a/apps/web/lib/video/[uid]/getServerSideProps.ts +++ b/apps/web/lib/video/[uid]/getServerSideProps.ts @@ -140,9 +140,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }) : false; + const userRepo = new UserRepository(prisma); const profile = booking.user ? ( - await UserRepository.enrichUserWithItsProfile({ + await userRepo.enrichUserWithItsProfile({ user: booking.user, }) ).profile diff --git a/apps/web/server/lib/[user]/[type]/getServerSideProps.ts b/apps/web/server/lib/[user]/[type]/getServerSideProps.ts index 12400d547d..5ce7977b43 100644 --- a/apps/web/server/lib/[user]/[type]/getServerSideProps.ts +++ b/apps/web/server/lib/[user]/[type]/getServerSideProps.ts @@ -135,7 +135,8 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { } } - const usersInOrgContext = await UserRepository.findUsersByUsername({ + const userRepo = new UserRepository(prisma); + const usersInOrgContext = await userRepo.findUsersByUsername({ usernameList: usernames, orgSlug: isValidOrgDomain ? currentOrgDomain : null, }); diff --git a/apps/web/server/lib/[user]/getServerSideProps.ts b/apps/web/server/lib/[user]/getServerSideProps.ts index 9b39ecdf69..26e46ef97f 100644 --- a/apps/web/server/lib/[user]/getServerSideProps.ts +++ b/apps/web/server/lib/[user]/getServerSideProps.ts @@ -13,6 +13,7 @@ import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { safeStringify } from "@calcom/lib/safeStringify"; import { UserRepository } from "@calcom/lib/server/repository/user"; import { stripMarkdown } from "@calcom/lib/stripMarkdown"; +import prisma from "@calcom/prisma"; import { RedirectType, type EventType, type User } from "@calcom/prisma/client"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { UserProfile } from "@calcom/types/UserProfile"; @@ -199,7 +200,9 @@ export const getServerSideProps: GetServerSideProps = async (cont }; export async function getUsersInOrgContext(usernameList: string[], orgSlug: string | null) { - const usersInOrgContext = await UserRepository.findUsersByUsername({ + const userRepo = new UserRepository(prisma); + + const usersInOrgContext = await userRepo.findUsersByUsername({ usernameList, orgSlug, }); @@ -212,7 +215,7 @@ export async function getUsersInOrgContext(usernameList: string[], orgSlug: stri // the platform organization does not have a domain. In this case there is no org domain but also platform member // "User.organization" is not null so "UserRepository.findUsersByUsername" returns empty array and we do this as a last resort // call to find platform member. - return await UserRepository.findPlatformMembersByUsernames({ + return await userRepo.findPlatformMembersByUsernames({ usernameList, }); } diff --git a/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts b/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts index 40b81b597e..5526343d87 100644 --- a/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts +++ b/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts @@ -5,6 +5,7 @@ import logger from "@calcom/lib/logger"; import { uploadAvatar } from "@calcom/lib/server/avatar"; import { UserRepository } from "@calcom/lib/server/repository/user"; import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; +import prisma from "@calcom/prisma"; export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userId: number) { try { @@ -25,11 +26,13 @@ export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userI avatar: await resizeBase64Image(avatarUrl), userId, }); - await UserRepository.updateAvatar({ id: userId, avatarUrl: resizedAvatarUrl }); + const userRepo = new UserRepository(prisma); + await userRepo.updateAvatar({ id: userId, avatarUrl: resizedAvatarUrl }); return; } - await UserRepository.updateAvatar({ id: userId, avatarUrl }); + const userRepo = new UserRepository(prisma); + await userRepo.updateAvatar({ id: userId, avatarUrl }); } catch (error) { logger.error("Error updating avatarUrl from google calendar connect", error); } diff --git a/packages/app-store/_utils/throwIfNotHaveAdminAccessToTeam.ts b/packages/app-store/_utils/throwIfNotHaveAdminAccessToTeam.ts index 744cc99b82..b70e005023 100644 --- a/packages/app-store/_utils/throwIfNotHaveAdminAccessToTeam.ts +++ b/packages/app-store/_utils/throwIfNotHaveAdminAccessToTeam.ts @@ -1,5 +1,6 @@ import { HttpError } from "@calcom/lib/http-error"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; export const throwIfNotHaveAdminAccessToTeam = async ({ teamId, @@ -11,7 +12,8 @@ export const throwIfNotHaveAdminAccessToTeam = async ({ if (!teamId) { return; } - const userAdminTeams = await UserRepository.getUserAdminTeams(userId); + const userRepo = new UserRepository(prisma); + const userAdminTeams = await userRepo.getUserAdminTeams({ userId }); const teamsUserHasAdminAccessFor = userAdminTeams?.teams?.map(({ team }) => team.id) ?? []; const hasAdminAccessToTeam = teamsUserHasAdminAccessFor.some((id) => id === teamId); diff --git a/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts b/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts index 1b90a4f4de..7c8b0cd888 100644 --- a/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts +++ b/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts @@ -99,9 +99,10 @@ export const getServerSidePropsForSingleFormView = async function getServerSideP const { UserRepository } = await import("@calcom/lib/server/repository/user"); + const userRepo = new UserRepository(prisma); const formWithUserInfoProfile = { ...form, - user: await UserRepository.enrichUserWithItsProfile({ user: form.user }), + user: await userRepo.enrichUserWithItsProfile({ user: form.user }), }; return { diff --git a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts b/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts index 5fd8c8f10f..6a1b71f3b4 100644 --- a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts +++ b/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts @@ -67,9 +67,10 @@ export const getServerSideProps = async function getServerSideProps( } const { UserRepository } = await import("@calcom/lib/server/repository/user"); + const userRepo = new UserRepository(prisma); const formWithUserProfile = { ...form, - user: await UserRepository.enrichUserWithItsProfile({ user: form.user }), + user: await userRepo.enrichUserWithItsProfile({ user: form.user }), }; if ( diff --git a/packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts b/packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts index 5d2bfeed98..307b868687 100644 --- a/packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts +++ b/packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts @@ -83,9 +83,10 @@ async function getResponseWithFormFieldsHandler({ ctx, input }: GetResponseWithF } const { UserRepository } = await import("@calcom/lib/server/repository/user"); + const userRepo = new UserRepository(prisma); const formWithUserProfile = { ...form, - user: await UserRepository.enrichUserWithItsProfile({ user: form.user }), + user: await userRepo.enrichUserWithItsProfile({ user: form.user }), }; return { diff --git a/packages/features/auth/lib/getServerSession.ts b/packages/features/auth/lib/getServerSession.ts index 2e71cc27e4..dde207f1e0 100644 --- a/packages/features/auth/lib/getServerSession.ts +++ b/packages/features/auth/lib/getServerSession.ts @@ -80,7 +80,8 @@ export async function getServerSession(options: { return null; } - const user = await UserRepository.enrichUserWithTheProfile({ + const userRepository = new UserRepository(prisma); + const user = await userRepository.enrichUserWithTheProfile({ user: userFromDb, upId, }); diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 43de83ae20..a083b62c78 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -116,7 +116,8 @@ const providers: Provider[] = [ throw new Error(ErrorCode.InternalServerError); } - const user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({ + const userRepo = new UserRepository(prisma); + const user = await userRepo.findByEmailAndIncludeProfilesAndPassword({ email: credentials.email, }); // Don't leak information about it being username or password that is invalid @@ -291,7 +292,8 @@ if (isSAMLLoginEnabled) { locale?: string; }) => { log.debug("BoxyHQ:profile", safeStringify({ profile })); - const user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({ + const userRepo = new UserRepository(prisma); + const user = await userRepo.findByEmailAndIncludeProfilesAndPassword({ email: profile.email || "", }); return { @@ -355,9 +357,8 @@ if (isSAMLLoginEnabled) { const { id, firstName, lastName } = userInfo; const email = userInfo.email.toLowerCase(); - let user = !email - ? undefined - : await UserRepository.findByEmailAndIncludeProfilesAndPassword({ email }); + const userRepo = new UserRepository(prisma); + let user = !email ? undefined : await userRepo.findByEmailAndIncludeProfilesAndPassword({ email }); if (!user) { const hostedCal = Boolean(HOSTED_CAL_FEATURES); if (hostedCal && email) { @@ -373,7 +374,7 @@ if (isSAMLLoginEnabled) { createUsersAndConnectToOrgProps, org, }); - user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({ + user = await userRepo.findByEmailAndIncludeProfilesAndPassword({ email: email, }); } diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.test.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.test.ts index eae3f0c4e1..c9c60997d8 100644 --- a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.test.ts +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.test.ts @@ -9,18 +9,30 @@ import { describe, test, expect, vi } from "vitest"; import { UserRepository } from "@calcom/lib/server/repository/user"; -// vi.mock("@calcom/lib/server/repository/user", () => { -// return { -// enrichUserWithItsProfile -// } -// }) +vi.mock("@calcom/lib/server/repository/user", () => { + return { + UserRepository: vi.fn().mockImplementation(() => ({ + enrichUserWithItsProfile: vi.fn(), + })), + }; +}); describe("getAllCredentialsIncludeServiceAccountKey", () => { test("Get an individual's credentials", async () => { - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: null, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; @@ -62,10 +74,20 @@ describe("getAllCredentialsIncludeServiceAccountKey", () => { describe("If CRM is enabled on the event type", () => { describe("With _crm credentials", () => { test("For users", async () => { - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: null, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; @@ -136,10 +158,20 @@ describe("getAllCredentialsIncludeServiceAccountKey", () => { expect(credentials).toContainEqual(expect.objectContaining({ userId: 1, type: "salesforce_crm" })); }); test("For teams", async () => { - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: null, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; @@ -203,10 +235,20 @@ describe("getAllCredentialsIncludeServiceAccountKey", () => { expect(credentials).toContainEqual(expect.objectContaining({ teamId: 1, type: "salesforce_crm" })); }); test("For child of managed event type", async () => { - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: null, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; @@ -298,10 +340,20 @@ describe("getAllCredentialsIncludeServiceAccountKey", () => { const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; const orgId = 3; - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: { organizationId: orgId }, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const crmCredential = { id: 1, type: "salesforce_crm", @@ -611,10 +663,20 @@ describe("getAllCredentialsIncludeServiceAccountKey", () => { const getAllCredentialsIncludeServiceAccountKey = (await import("./getAllCredentials")) .getAllCredentialsIncludeServiceAccountKey; const orgId = 3; - vi.spyOn(UserRepository, "enrichUserWithItsProfile").mockReturnValue({ + const mockEnrichUserWithItsProfile = vi.fn().mockReturnValue({ profile: { organizationId: orgId }, }); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } + const crmCredential = { id: 1, type: "salesforce_crm", diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts index 75d7f870a0..46843b6fbd 100644 --- a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts @@ -57,7 +57,7 @@ export const getAllCredentialsIncludeServiceAccountKey = async ( } } - const { profile } = await UserRepository.enrichUserWithItsProfile({ + const { profile } = await new UserRepository(prisma).enrichUserWithItsProfile({ user: user, }); diff --git a/packages/features/bookings/lib/handleNewBooking/loadUsers.ts b/packages/features/bookings/lib/handleNewBooking/loadUsers.ts index ac53ed2807..110a01656c 100644 --- a/packages/features/bookings/lib/handleNewBooking/loadUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/loadUsers.ts @@ -116,7 +116,7 @@ export const findUsersByUsername = async ({ usernameList: string[]; }) => { log.debug("findUsersByUsername", { usernameList, orgSlug }); - const { where, profiles } = await UserRepository._getWhereClauseForFindingUsersByUsername({ + const { where, profiles } = await new UserRepository(prisma)._getWhereClauseForFindingUsersByUsername({ orgSlug, usernameList, }); diff --git a/packages/features/credentials/deleteCredential.test.ts b/packages/features/credentials/deleteCredential.test.ts index 4d1d0b304c..dde8e204a9 100644 --- a/packages/features/credentials/deleteCredential.test.ts +++ b/packages/features/credentials/deleteCredential.test.ts @@ -10,6 +10,7 @@ import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import { DestinationCalendarRepository } from "@calcom/lib/server/repository/destinationCalendar"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; const testUser = { email: "test@test.com", @@ -38,7 +39,7 @@ describe("deleteCredential", () => { test("Delete video credential", async () => { const handleDeleteCredential = (await import("./handleDeleteCredential")).default; - const user = await UserRepository.create({ + const user = await new UserRepository(prisma).create({ ...testUser, }); @@ -74,7 +75,7 @@ describe("deleteCredential", () => { test("Delete calendar credential", async () => { const handleDeleteCredential = (await import("./handleDeleteCredential")).default; - const user = await UserRepository.create({ + const user = await new UserRepository(prisma).create({ ...testUser, }); diff --git a/packages/features/ee/dsync/lib/handleUserEvents.ts b/packages/features/ee/dsync/lib/handleUserEvents.ts index 152e3d9c13..6aad0be17b 100644 --- a/packages/features/ee/dsync/lib/handleUserEvents.ts +++ b/packages/features/ee/dsync/lib/handleUserEvents.ts @@ -79,7 +79,7 @@ const handleUserEvents = async (event: DirectorySyncEvent, organizationId: numbe if (user) { if (eventData.active) { - if (UserRepository.isAMemberOfOrganization({ user, organizationId })) { + if (await new UserRepository(prisma).isAMemberOfOrganization({ user, organizationId })) { await syncCustomAttributesToUser({ event, userEmail, diff --git a/packages/features/ee/organizations/lib/OrganizationPaymentService.ts b/packages/features/ee/organizations/lib/OrganizationPaymentService.ts index 51d6e20e52..b9e230fef1 100644 --- a/packages/features/ee/organizations/lib/OrganizationPaymentService.ts +++ b/packages/features/ee/organizations/lib/OrganizationPaymentService.ts @@ -105,7 +105,7 @@ export class OrganizationPaymentService { const stripeCustomerId = customer.stripeCustomerId; if (existingCustomer && parsedMetadata) { - await UserRepository.updateStripeCustomerId({ + await new UserRepository(prisma).updateStripeCustomerId({ id: existingCustomer.id, stripeCustomerId, existingMetadata: parsedMetadata, diff --git a/packages/features/ee/organizations/lib/server/createOrganizationFromOnboarding.ts b/packages/features/ee/organizations/lib/server/createOrganizationFromOnboarding.ts index e697fcc24c..5708d581df 100644 --- a/packages/features/ee/organizations/lib/server/createOrganizationFromOnboarding.ts +++ b/packages/features/ee/organizations/lib/server/createOrganizationFromOnboarding.ts @@ -317,7 +317,7 @@ async function ensureStripeCustomerIdIsUpdated({ }) { const parsedMetadata = userMetadata.parse(owner.metadata); - await UserRepository.updateStripeCustomerId({ + await new UserRepository(prisma).updateStripeCustomerId({ id: owner.id, stripeCustomerId: stripeCustomerId, existingMetadata: parsedMetadata, diff --git a/packages/features/ee/organizations/lib/server/orgCreationUtils.ts b/packages/features/ee/organizations/lib/server/orgCreationUtils.ts index 2044ced60e..525a2e8255 100644 --- a/packages/features/ee/organizations/lib/server/orgCreationUtils.ts +++ b/packages/features/ee/organizations/lib/server/orgCreationUtils.ts @@ -279,7 +279,7 @@ export const findUserToBeOrgOwner = async (email: string) => { return null; } - return await UserRepository.enrichUserWithItsProfile({ + return await new UserRepository(prisma).enrichUserWithItsProfile({ user, }); }; diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index 6f22d8c922..abf5879ae2 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -237,7 +237,7 @@ export const getPublicEvent = async ( const orgQuery = org ? getSlugOrRequestedSlug(org) : null; // In case of dynamic group event, we fetch user's data and use the default event. if (usernameList.length > 1) { - const usersInOrgContext = await UserRepository.findUsersByUsername({ + const usersInOrgContext = await new UserRepository(prisma).findUsersByUsername({ usernameList, orgSlug: org, }); @@ -397,7 +397,7 @@ export const getPublicEvent = async ( const usersAsHosts = event.hosts.map((host) => host.user); // Enrich users in a single batch call - const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(usersAsHosts); + const enrichedUsers = await new UserRepository(prisma).enrichUsersWithTheirProfiles(usersAsHosts); // Map enriched users back to the hosts const hosts = event.hosts.map((host, index) => ({ @@ -408,7 +408,7 @@ export const getPublicEvent = async ( const eventWithUserProfiles = { ...event, owner: event.owner - ? await UserRepository.enrichUserWithItsProfile({ + ? await new UserRepository(prisma).enrichUserWithItsProfile({ user: event.owner, }) : null, @@ -640,7 +640,7 @@ async function getOwnerFromUsersArray(prisma: PrismaClient, eventTypeId: number) if (!users.length) return null; // Batch enrich users in a single call - const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(users); + const enrichedUsers = await new UserRepository(prisma).enrichUsersWithTheirProfiles(users); // Map the enriched users back to include the organization info const usersWithUserProfile = enrichedUsers.map((user) => ({ diff --git a/packages/lib/createAProfileForAnExistingUser.ts b/packages/lib/createAProfileForAnExistingUser.ts index 5a3b87dc37..ddbbbffd0a 100644 --- a/packages/lib/createAProfileForAnExistingUser.ts +++ b/packages/lib/createAProfileForAnExistingUser.ts @@ -47,7 +47,8 @@ export const createAProfileForAnExistingUser = async ({ movedFromUserId: user.id, }); - await UserRepository.updateWhereId({ + const userRepo = new UserRepository(prisma); + await userRepo.updateWhereId({ whereId: user.id, data: { movedToProfileId: profile.id, diff --git a/packages/lib/delegationCredential/server.ts b/packages/lib/delegationCredential/server.ts index b766c6a5af..4abe342628 100644 --- a/packages/lib/delegationCredential/server.ts +++ b/packages/lib/delegationCredential/server.ts @@ -8,6 +8,7 @@ import { safeStringify } from "@calcom/lib/safeStringify"; import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import type { ServiceAccountKey } from "@calcom/lib/server/repository/delegationCredential"; import { DelegationCredentialRepository } from "@calcom/lib/server/repository/delegationCredential"; +import prisma from "@calcom/prisma"; import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential"; import { UserRepository } from "../server/repository/user"; @@ -593,7 +594,7 @@ export async function findUniqueDelegationCalendarCredential({ }) { const [delegationCredential, user] = await Promise.all([ DelegationCredentialRepository.findByIdIncludeSensitiveServiceAccountKey({ id: delegationCredentialId }), - UserRepository.findById({ id: userId }), + new UserRepository(prisma).findById({ id: userId }), ]); if (!delegationCredential) { diff --git a/packages/lib/event-types/getEventTypeById.ts b/packages/lib/event-types/getEventTypeById.ts index e1e4f7de66..b0fce94a0a 100644 --- a/packages/lib/event-types/getEventTypeById.ts +++ b/packages/lib/event-types/getEventTypeById.ts @@ -66,11 +66,12 @@ export const getEventTypeById = async ({ const newMetadata = eventTypeMetaDataSchemaWithTypedApps.parse(metadata || {}) || {}; const apps = newMetadata?.apps || {}; const eventTypeWithParsedMetadata = { ...rawEventType, metadata: newMetadata }; + const userRepo = new UserRepository(prisma); const eventTeamMembershipsWithUserProfile = []; for (const eventTeamMembership of rawEventType.team?.members || []) { eventTeamMembershipsWithUserProfile.push({ ...eventTeamMembership, - user: await UserRepository.enrichUserWithItsProfile({ + user: await userRepo.enrichUserWithItsProfile({ user: eventTeamMembership.user, }), }); @@ -81,7 +82,7 @@ export const getEventTypeById = async ({ childrenWithUserProfile.push({ ...child, owner: child.owner - ? await UserRepository.enrichUserWithItsProfile({ + ? await userRepo.enrichUserWithItsProfile({ user: child.owner, }) : null, @@ -91,7 +92,7 @@ export const getEventTypeById = async ({ const eventTypeUsersWithUserProfile = []; for (const eventTypeUser of rawEventType.users) { eventTypeUsersWithUserProfile.push( - await UserRepository.enrichUserWithItsProfile({ + await userRepo.enrichUserWithItsProfile({ user: eventTypeUser, }) ); diff --git a/packages/lib/event-types/getEventTypesByViewer.ts b/packages/lib/event-types/getEventTypesByViewer.ts index 0e0d44fb60..3115eb7407 100644 --- a/packages/lib/event-types/getEventTypesByViewer.ts +++ b/packages/lib/event-types/getEventTypesByViewer.ts @@ -13,6 +13,7 @@ import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { MembershipRepository } from "@calcom/lib/server/repository/membership"; import { ProfileRepository } from "@calcom/lib/server/repository/profile"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { eventTypeMetaDataSchemaWithUntypedApps } from "@calcom/prisma/zod-utils"; @@ -105,32 +106,35 @@ export const getEventTypesByViewer = async (user: User, filters?: Filters, forRo type UserEventTypes = (typeof profileEventTypes)[number]; - const mapEventType = async (eventType: UserEventTypes) => ({ - ...eventType, - safeDescription: eventType?.description ? markdownToSafeHTML(eventType.description) : undefined, - users: await Promise.all( - (!!eventType?.hosts?.length ? eventType?.hosts.map((host) => host.user) : eventType.users).map( - async (u) => - await UserRepository.enrichUserWithItsProfile({ - user: u, - }) - ) - ), - metadata: eventType.metadata ? eventTypeMetaDataSchemaWithUntypedApps.parse(eventType.metadata) : null, - children: await Promise.all( - (eventType.children || []).map(async (c) => ({ - ...c, - users: await Promise.all( - c.users.map( - async (u) => - await UserRepository.enrichUserWithItsProfile({ - user: u, - }) - ) - ), - })) - ), - }); + const mapEventType = async (eventType: UserEventTypes) => { + const userRepo = new UserRepository(prisma); + return { + ...eventType, + safeDescription: eventType?.description ? markdownToSafeHTML(eventType.description) : undefined, + users: await Promise.all( + (!!eventType?.hosts?.length ? eventType?.hosts.map((host) => host.user) : eventType.users).map( + async (u) => + await userRepo.enrichUserWithItsProfile({ + user: u, + }) + ) + ), + metadata: eventType.metadata ? eventTypeMetaDataSchemaWithUntypedApps.parse(eventType.metadata) : null, + children: await Promise.all( + (eventType.children || []).map(async (c) => ({ + ...c, + users: await Promise.all( + c.users.map( + async (u) => + await userRepo.enrichUserWithItsProfile({ + user: u, + }) + ) + ), + })) + ), + }; + }; const userEventTypes = (await Promise.all(profileEventTypes.map(mapEventType))).filter((eventType) => { const isAChildEvent = eventType.parentId; diff --git a/packages/lib/server/getRoutedUrl.test.ts b/packages/lib/server/getRoutedUrl.test.ts index f595b0f512..cce4e7650b 100644 --- a/packages/lib/server/getRoutedUrl.test.ts +++ b/packages/lib/server/getRoutedUrl.test.ts @@ -23,7 +23,13 @@ import { getRoutedUrl } from "./getRoutedUrl"; vi.mock("@calcom/lib/checkRateLimitAndThrowError"); vi.mock("@calcom/app-store/routing-forms/lib/handleResponse"); vi.mock("@calcom/lib/server/repository/routingForm"); -vi.mock("@calcom/lib/server/repository/user"); +vi.mock("@calcom/lib/server/repository/user", () => { + return { + UserRepository: vi.fn().mockImplementation(() => ({ + enrichUserWithItsProfile: vi.fn(), + })), + }; +}); vi.mock("@calcom/features/ee/organizations/lib/orgDomains"); vi.mock("@calcom/features/routing-forms/lib/isAuthorizedToViewForm"); vi.mock("@calcom/app-store/routing-forms/lib/getSerializableForm"); @@ -79,7 +85,17 @@ describe("getRoutedUrl", () => { // Provide default mock implementations vi.mocked(orgDomainConfig).mockReturnValue({ currentOrgDomain: null }); vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(null); - vi.mocked(UserRepository.enrichUserWithItsProfile).mockImplementation(async ({ user }) => user); + + const mockEnrichUserWithItsProfile = vi.fn().mockImplementation(async ({ user }) => user); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + enrichUserWithItsProfile: mockEnrichUserWithItsProfile, + } as any) + ); + } vi.mocked(isAuthorizedToViewFormOnOrgDomain).mockReturnValue(true); vi.mocked(getSerializableForm).mockResolvedValue(mockSerializableForm as never); vi.mocked(findMatchingRoute).mockReturnValue(null); diff --git a/packages/lib/server/getRoutedUrl.ts b/packages/lib/server/getRoutedUrl.ts index 3b73d22ed5..759ef2e1e3 100644 --- a/packages/lib/server/getRoutedUrl.ts +++ b/packages/lib/server/getRoutedUrl.ts @@ -22,6 +22,7 @@ import logger from "@calcom/lib/logger"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; @@ -102,9 +103,10 @@ const _getRoutedUrl = async (context: Pick { describe("create", () => { test("Should create a user without a password", async () => { - const user = await UserRepository.create({ + const user = await new UserRepository(prismock).create({ username: "test", email: "test@example.com", organizationId: null, @@ -50,7 +50,7 @@ describe("UserRepository", () => { }); test("If locked param is passed, user should be locked", async () => { - const user = await UserRepository.create({ + const user = await new UserRepository(prismock).create({ username: "test", email: "test@example.com", organizationId: null, @@ -78,7 +78,7 @@ describe("UserRepository", () => { const organizationId = 123; const username = "test"; - const user = await UserRepository.create({ + const user = await new UserRepository(prismock).create({ username, email: "test@example.com", organizationId, diff --git a/packages/lib/server/repository/user.ts b/packages/lib/server/repository/user.ts index 323ae2b181..adc6e05ecb 100644 --- a/packages/lib/server/repository/user.ts +++ b/packages/lib/server/repository/user.ts @@ -4,9 +4,9 @@ import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizatio import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server/i18n"; -import prisma, { availabilityUserSelect } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; -import type { User as UserType } from "@calcom/prisma/client"; +import { availabilityUserSelect } from "@calcom/prisma"; +import type { PrismaClient } from "@calcom/prisma"; +import type { Prisma, User as UserType } from "@calcom/prisma/client"; import type { CreationSource } from "@calcom/prisma/enums"; import { MembershipRole } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; @@ -23,6 +23,46 @@ export type { UserWithLegacySelectedCalendars } from "../withSelectedCalendars"; export { withSelectedCalendars }; export type UserAdminTeams = number[]; +export type SessionUser = { + id: number; + username: string | null; + name: string | null; + email: string; + emailVerified: Date | null; + bio: string | null; + avatarUrl: string | null; + timeZone: string; + weekStart: string; + startTime: number; + endTime: number; + defaultScheduleId: number | null; + bufferTime: number; + theme: string | null; + appTheme: string | null; + createdDate: Date; + hideBranding: boolean; + twoFactorEnabled: boolean; + disableImpersonation: boolean; + identityProvider: string | null; + identityProviderId: string | null; + brandColor: string | null; + darkBrandColor: string | null; + movedToProfileId: number | null; + completedOnboarding: boolean; + destinationCalendar: any; + locale: string; + timeFormat: number | null; + trialEndsAt: Date | null; + metadata: any; + role: string; + allowDynamicBooking: boolean; + allowSEOIndexing: boolean; + receiveMonthlyDigestEmail: boolean; + profiles: any[]; + allSelectedCalendars: any[]; + userLevelSelectedCalendars: any[]; +}; + const log = logger.getSubLogger({ prefix: ["[repository/user]"] }); export const ORGANIZATION_ID_UNKNOWN = "ORGANIZATION_ID_UNKNOWN"; @@ -80,8 +120,10 @@ const userSelect = { } satisfies Prisma.UserSelect; export class UserRepository { - static async findTeamsByUserId({ userId }: { userId: UserType["id"] }) { - const teamMemberships = await prisma.membership.findMany({ + constructor(private prismaClient: PrismaClient) {} + + async findTeamsByUserId({ userId }: { userId: UserType["id"] }) { + const teamMemberships = await this.prismaClient.membership.findMany({ where: { userId: userId, }, @@ -103,8 +145,8 @@ export class UserRepository { }; } - static async findOrganizations({ userId }: { userId: UserType["id"] }) { - const { acceptedTeamMemberships } = await UserRepository.findTeamsByUserId({ + async findOrganizations({ userId }: { userId: UserType["id"] }) { + const { acceptedTeamMemberships } = await this.findTeamsByUserId({ userId, }); @@ -122,20 +164,14 @@ export class UserRepository { /** * It is aware of the fact that a user can be part of multiple organizations. */ - static async findUsersByUsername({ - orgSlug, - usernameList, - }: { - orgSlug: string | null; - usernameList: string[]; - }) { - const { where, profiles } = await UserRepository._getWhereClauseForFindingUsersByUsername({ + async findUsersByUsername({ orgSlug, usernameList }: { orgSlug: string | null; usernameList: string[] }) { + const { where, profiles } = await this._getWhereClauseForFindingUsersByUsername({ orgSlug, usernameList, }); return ( - await prisma.user.findMany({ + await this.prismaClient.user.findMany({ select: userSelect, where, }) @@ -161,9 +197,9 @@ export class UserRepository { }); } - static async findPlatformMembersByUsernames({ usernameList }: { usernameList: string[] }) { + async findPlatformMembersByUsernames({ usernameList }: { usernameList: string[] }) { return ( - await prisma.user.findMany({ + await this.prismaClient.user.findMany({ select: userSelect, where: { username: { @@ -187,7 +223,7 @@ export class UserRepository { }); } - static async _getWhereClauseForFindingUsersByUsername({ + async _getWhereClauseForFindingUsersByUsername({ orgSlug, usernameList, }: { @@ -229,8 +265,8 @@ export class UserRepository { return { where, profiles }; } - static async findByEmail({ email }: { email: string }) { - const user = await prisma.user.findUnique({ + async findByEmail({ email }: { email: string }) { + const user = await this.prismaClient.user.findUnique({ where: { email: email.toLowerCase(), }, @@ -239,8 +275,8 @@ export class UserRepository { return user; } - static async findByEmailAndIncludeProfilesAndPassword({ email }: { email: string }) { - const user = await prisma.user.findUnique({ + async findByEmailAndIncludeProfilesAndPassword({ email }: { email: string }) { + const user = await this.prismaClient.user.findUnique({ where: { email: email.toLowerCase(), }, @@ -280,8 +316,8 @@ export class UserRepository { }; } - static async findById({ id }: { id: number }) { - const user = await prisma.user.findUnique({ + async findById({ id }: { id: number }) { + const user = await this.prismaClient.user.findUnique({ where: { id, }, @@ -297,8 +333,8 @@ export class UserRepository { }; } - static async findByIds({ ids }: { ids: number[] }) { - return prisma.user.findMany({ + async findByIds({ ids }: { ids: number[] }) { + return this.prismaClient.user.findMany({ where: { id: { in: ids, @@ -308,20 +344,20 @@ export class UserRepository { }); } - static async findByIdOrThrow({ id }: { id: number }) { - const user = await UserRepository.findById({ id }); + async findByIdOrThrow({ id }: { id: number }) { + const user = await this.findById({ id }); if (!user) { throw new Error(`User with id ${id} not found`); } return user; } - static async findManyByOrganization({ organizationId }: { organizationId: number }) { + async findManyByOrganization({ organizationId }: { organizationId: number }) { const profiles = await ProfileRepository.findManyForOrg({ organizationId }); return profiles.map((profile) => profile.user); } - static isAMemberOfOrganization({ + isAMemberOfOrganization({ user, organizationId, }: { @@ -331,7 +367,7 @@ export class UserRepository { return user.profiles.some((profile) => profile.organizationId === organizationId); } - static async findIfAMemberOfSomeOrganization({ user }: { user: { id: number } }) { + async findIfAMemberOfSomeOrganization({ user }: { user: { id: number } }) { return !!( await ProfileRepository.findManyForUser({ id: user.id, @@ -339,7 +375,7 @@ export class UserRepository { ).length; } - static isMigratedToOrganization({ + isMigratedToOrganization({ user, }: { user: { @@ -351,11 +387,11 @@ export class UserRepository { return !!user.metadata?.migratedToOrgFrom; } - static async isMovedToAProfile({ user }: { user: Pick }) { + async isMovedToAProfile({ user }: { user: Pick }) { return !!user.movedToProfileId; } - static async enrichUserWithTheProfile({ + async enrichUserWithTheProfile({ user, upId, }: { @@ -382,7 +418,13 @@ export class UserRepository { * 2. While dealing with a User that has been moved to a Profile i.e. he was invited to an organization when he was an existing user. * 3. We haven't added profileId to all the entities, so they aren't aware of which profile they belong to. So, we still mostly use this function to enrich the user with its profile. */ - static async enrichUserWithItsProfile({ + async enrichUserWithItsProfile< + T extends { + id: number; + username: string | null; + [key: string]: any; + } + >({ user, }: { user: T; @@ -420,7 +462,7 @@ export class UserRepository { }; } - static async enrichUsersWithTheirProfiles( + async enrichUsersWithTheirProfiles( users: T[] ): Promise< Array< @@ -479,7 +521,7 @@ export class UserRepository { }); } - static enrichUserWithItsProfileBuiltFromUser({ + enrichUserWithItsProfileBuiltFromUser({ user, }: { user: T; @@ -495,7 +537,7 @@ export class UserRepository { }; } - static async enrichEntityWithProfile< + async enrichEntityWithProfile< T extends | { profile: { @@ -554,7 +596,7 @@ export class UserRepository { } } - static async updateWhereId({ + async updateWhereId({ whereId, data, }: { @@ -563,7 +605,7 @@ export class UserRepository { movedToProfileId?: number | null; }; }) { - return prisma.user.update({ + return this.prismaClient.user.update({ where: { id: whereId, }, @@ -579,7 +621,7 @@ export class UserRepository { }); } - static async create( + async create( data: Omit & { username: string; hashedPassword?: string; @@ -595,7 +637,7 @@ export class UserRepository { const t = await getTranslation("en", "common"); const availability = getAvailabilityFromSchedule(DEFAULT_SCHEDULE); - const user = await prisma.user.create({ + const user = await this.prismaClient.user.create({ data: { username, email: email, @@ -635,8 +677,8 @@ export class UserRepository { return user; } - static async getUserAdminTeams(userId: number) { - return prisma.user.findUnique({ + async getUserAdminTeams({ userId }: { userId: number }) { + return await this.prismaClient.user.findUnique({ where: { id: userId, }, @@ -687,7 +729,7 @@ export class UserRepository { }, }); } - static async isAdminOfTeamOrParentOrg({ userId, teamId }: { userId: number; teamId: number }) { + async isAdminOfTeamOrParentOrg({ userId, teamId }: { userId: number; teamId: number }) { const membershipQuery = { members: { some: { @@ -696,7 +738,7 @@ export class UserRepository { }, }, }; - const teams = await prisma.team.findMany({ + const teams = await this.prismaClient.team.findMany({ where: { id: teamId, OR: [ @@ -712,8 +754,8 @@ export class UserRepository { }); return !!teams.length; } - static async isAdminOrOwnerOfTeam({ userId, teamId }: { userId: number; teamId: number }) { - const isAdminOrOwnerOfTeam = await prisma.membership.findUnique({ + async isAdminOrOwnerOfTeam({ userId, teamId }: { userId: number; teamId: number }) { + const isAdminOrOwnerOfTeam = await this.prismaClient.membership.findUnique({ where: { userId_teamId: { userId, @@ -728,8 +770,8 @@ export class UserRepository { }); return !!isAdminOrOwnerOfTeam; } - static async getTimeZoneAndDefaultScheduleId({ userId }: { userId: number }) { - return await prisma.user.findUnique({ + async getTimeZoneAndDefaultScheduleId({ userId }: { userId: number }) { + return await this.prismaClient.user.findUnique({ where: { id: userId, }, @@ -740,16 +782,16 @@ export class UserRepository { }); } - static async adminFindById(userId: number) { - return await prisma.user.findUniqueOrThrow({ + async adminFindById(userId: number) { + return await this.prismaClient.user.findUniqueOrThrow({ where: { id: userId, }, }); } - static async findUserTeams({ id }: { id: number }) { - const user = await prisma.user.findUnique({ + async findUserTeams({ id }: { id: number }) { + const user = await this.prismaClient.user.findUnique({ where: { id, }, @@ -776,10 +818,10 @@ export class UserRepository { return user; } - static async updateAvatar({ id, avatarUrl }: { id: number; avatarUrl: string }) { + async updateAvatar({ id, avatarUrl }: { id: number; avatarUrl: string }) { // Using updateMany here since if the user already has a profile it would throw an error // because no records were found to update the profile picture - await prisma.user.updateMany({ + await this.prismaClient.user.updateMany({ where: { id, avatarUrl: { @@ -791,8 +833,8 @@ export class UserRepository { }, }); } - static async findUserWithCredentials({ id }: { id: number }) { - const user = await prisma.user.findUnique({ + async findUserWithCredentials({ id }: { id: number }) { + const user = await this.prismaClient.user.findUnique({ where: { id, }, @@ -817,8 +859,8 @@ export class UserRepository { }; } - static async findUnlockedUserForSession({ userId }: { userId: number }) { - const user = await prisma.user.findUnique({ + async findUnlockedUserForSession({ userId }: { userId: number }) { + const user = await this.prismaClient.user.findUnique({ where: { id: userId, // Locked users can't login @@ -877,8 +919,8 @@ export class UserRepository { return withSelectedCalendars(user); } - static async getUserStats({ userId }: { userId: number }) { - const user = await prisma.user.findUnique({ + async getUserStats({ userId }: { userId: number }) { + const user = await this.prismaClient.user.findUnique({ where: { id: userId, }, @@ -927,8 +969,8 @@ export class UserRepository { }; } - static async findManyByIdsIncludeDestinationAndSelectedCalendars({ ids }: { ids: number[] }) { - const users = await prisma.user.findMany({ + async findManyByIdsIncludeDestinationAndSelectedCalendars({ ids }: { ids: number[] }) { + const users = await this.prismaClient.user.findMany({ where: { id: { in: ids } }, include: { selectedCalendars: true, @@ -938,7 +980,7 @@ export class UserRepository { return users.map(withSelectedCalendars); } - static async updateStripeCustomerId({ + async updateStripeCustomerId({ id, stripeCustomerId, existingMetadata, @@ -947,39 +989,33 @@ export class UserRepository { stripeCustomerId: string; existingMetadata: z.infer; }) { - return prisma.user.update({ + return this.prismaClient.user.update({ where: { id }, data: { metadata: { ...existingMetadata, stripeCustomerId } }, }); } - static async updateWhitelistWorkflows({ - id, - whitelistWorkflows, - }: { - id: number; - whitelistWorkflows: boolean; - }) { - return prisma.user.update({ + async updateWhitelistWorkflows({ id, whitelistWorkflows }: { id: number; whitelistWorkflows: boolean }) { + return this.prismaClient.user.update({ where: { id }, data: { whitelistWorkflows }, }); } - static async findManyUsersForDynamicEventType({ + async findManyUsersForDynamicEventType({ currentOrgDomain, usernameList, }: { currentOrgDomain: string | null; usernameList: string[]; }) { - const { where } = await UserRepository._getWhereClauseForFindingUsersByUsername({ + const { where } = await this._getWhereClauseForFindingUsersByUsername({ orgSlug: currentOrgDomain, usernameList, }); // TODO: Should be moved to UserRepository - return prisma.user.findMany({ + return this.prismaClient.user.findMany({ where, select: { allowDynamicBooking: true, diff --git a/packages/lib/server/service/userCreationService.test.ts b/packages/lib/server/service/userCreationService.test.ts index 39e35fe1de..098fd7649c 100644 --- a/packages/lib/server/service/userCreationService.test.ts +++ b/packages/lib/server/service/userCreationService.test.ts @@ -21,11 +21,11 @@ vi.mock("@calcom/features/auth/lib/hashPassword", () => ({ hashPassword: vi.fn().mockResolvedValue("hashed-password"), })); -vi.mock("../repository/user", async () => { +vi.mock("../repository/user", () => { return { - UserRepository: { + UserRepository: vi.fn().mockImplementation(() => ({ create: vi.fn(), - }, + })), }; }); @@ -48,15 +48,25 @@ describe("UserCreationService", () => { }); test("should create user", async () => { - vi.spyOn(UserRepository, "create").mockResolvedValue({ + const mockCreate = vi.fn().mockResolvedValue({ username: "test", locked: false, organizationId: null, } as any); + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + create: mockCreate, + } as any) + ); + } + const user = await UserCreationService.createUser({ data: mockUserData }); - expect(UserRepository.create).toHaveBeenCalledWith( + expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ username: "test", locked: false, @@ -70,9 +80,25 @@ describe("UserCreationService", () => { test("should lock user when email is in watchlist", async () => { vi.mocked(checkIfEmailIsBlockedInWatchlistController).mockResolvedValue(true); + const mockCreate = vi.fn().mockResolvedValue({ + username: "test", + locked: true, + organizationId: null, + } as any); + + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + create: mockCreate, + } as any) + ); + } + const user = await UserCreationService.createUser({ data: mockUserData }); - expect(UserRepository.create).toHaveBeenCalledWith( + expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ locked: true, }) @@ -85,12 +111,28 @@ describe("UserCreationService", () => { const mockPassword = "password"; vi.mocked(hashPassword).mockResolvedValue("hashed_password"); + const mockCreate = vi.fn().mockResolvedValue({ + username: "test", + locked: false, + organizationId: null, + } as any); + + const mockUserRepository = vi.mocked(UserRepository); + if (mockUserRepository && typeof mockUserRepository.mockImplementation === "function") { + mockUserRepository.mockImplementation( + () => + ({ + create: mockCreate, + } as any) + ); + } + const user = await UserCreationService.createUser({ data: { ...mockUserData, password: mockPassword }, }); expect(hashPassword).toHaveBeenCalledWith(mockPassword); - expect(UserRepository.create).toHaveBeenCalledWith( + expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ hashedPassword: "hashed_password", }) diff --git a/packages/lib/server/service/userCreationService.ts b/packages/lib/server/service/userCreationService.ts index 3b138aca71..9d149951c0 100644 --- a/packages/lib/server/service/userCreationService.ts +++ b/packages/lib/server/service/userCreationService.ts @@ -1,6 +1,7 @@ import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller"; import logger from "@calcom/lib/logger"; +import prisma from "@calcom/prisma"; import type { CreationSource, UserPermissionRole, IdentityProvider } from "@calcom/prisma/enums"; import slugify from "../../slugify"; @@ -37,7 +38,8 @@ export class UserCreationService { const hashedPassword = password ? await hashPassword(password) : null; - const user = await UserRepository.create({ + const userRepo = new UserRepository(prisma); + const user = await userRepo.create({ ...data, username: slugify(username), ...(hashedPassword && { hashedPassword }), diff --git a/packages/trpc/server/middlewares/sessionMiddleware.ts b/packages/trpc/server/middlewares/sessionMiddleware.ts index 5b9226fc78..8d479bf668 100644 --- a/packages/trpc/server/middlewares/sessionMiddleware.ts +++ b/packages/trpc/server/middlewares/sessionMiddleware.ts @@ -6,6 +6,7 @@ import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { ProfileRepository } from "@calcom/lib/server/repository/profile"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils"; import { TRPCError } from "@trpc/server"; @@ -24,7 +25,8 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe ["OWNER", "ADMIN"].includes(member.role)); + const isOrgAdmin = members.some((member: any) => ["OWNER", "ADMIN"].includes(member.role)); if (isOrgAdmin) { logger.debug("User is an org admin", safeStringify({ userId: user.id })); diff --git a/packages/trpc/server/routers/viewer/admin/whitelistUserWorkflows.handler.ts b/packages/trpc/server/routers/viewer/admin/whitelistUserWorkflows.handler.ts index 10e06e4ea0..0d92a279b5 100644 --- a/packages/trpc/server/routers/viewer/admin/whitelistUserWorkflows.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/whitelistUserWorkflows.handler.ts @@ -1,4 +1,5 @@ import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import type { TrpcSessionUser } from "../../../types"; import type { TWhitelistUserWorkflows } from "./whitelistUserWorkflows.schema"; @@ -13,7 +14,7 @@ type GetOptions = { export const whitelistUserWorkflows = async ({ input }: GetOptions) => { const { userId, whitelistWorkflows } = input; - const user = await UserRepository.updateWhitelistWorkflows({ + const user = await new UserRepository(prisma).updateWhitelistWorkflows({ id: userId, whitelistWorkflows, }); diff --git a/packages/trpc/server/routers/viewer/apps/appCredentialsByType.handler.ts b/packages/trpc/server/routers/viewer/apps/appCredentialsByType.handler.ts index b2ca283dc5..abd3a0ce79 100644 --- a/packages/trpc/server/routers/viewer/apps/appCredentialsByType.handler.ts +++ b/packages/trpc/server/routers/viewer/apps/appCredentialsByType.handler.ts @@ -16,7 +16,7 @@ type AppCredentialsByTypeOptions = { /** Used for grabbing credentials on specific app pages */ export const appCredentialsByTypeHandler = async ({ ctx, input }: AppCredentialsByTypeOptions) => { const { user } = ctx; - const userAdminTeams = await UserRepository.getUserAdminTeams(ctx.user.id); + const userAdminTeams = await new UserRepository(prisma).getUserAdminTeams({ userId: ctx.user.id }); const { user: _, ...safeCredentialSelectWithoutUser } = safeCredentialSelect; const userAdminTeamsIds = userAdminTeams?.teams?.map(({ team }) => team.id) ?? []; diff --git a/packages/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.handler.ts b/packages/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.handler.ts index f4335237c2..bea65d02da 100644 --- a/packages/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.handler.ts +++ b/packages/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.handler.ts @@ -54,7 +54,7 @@ export const findTeamMembersMatchingAttributeLogicHandler = async ({ } const matchingTeamMembersIds = matchingTeamMembersWithResult.map((member) => member.userId); - const matchingTeamMembers = await UserRepository.findByIds({ ids: matchingTeamMembersIds }); + const matchingTeamMembers = await new UserRepository(ctx.prisma).findByIds({ ids: matchingTeamMembersIds }); return { mainWarnings, diff --git a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts index bfb07c2979..274b3cc980 100644 --- a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts @@ -71,11 +71,12 @@ async function getTeamMembers({ distinct: ["userId"], }); + const userRepo = new UserRepository(prisma); const membershipWithUserProfile = []; for (const membership of memberships) { membershipWithUserProfile.push({ ...membership, - user: await UserRepository.enrichUserWithItsProfile({ + user: await userRepo.enrichUserWithItsProfile({ user: membership.user, }), }); diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 2f603c09f8..e490952a19 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -248,7 +248,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { const { newLocation, credentialId: conferenceCredentialId } = input; const { booking, user: loggedInUser } = ctx; - const organizer = await UserRepository.findByIdOrThrow({ id: booking.userId || 0 }); + const organizer = await new UserRepository(prisma).findByIdOrThrow({ id: booking.userId || 0 }); const newLocationInEvtFormat = await getLocationInEvtFormatOrThrow({ location: newLocation, diff --git a/packages/trpc/server/routers/viewer/eventTypes/util.ts b/packages/trpc/server/routers/viewer/eventTypes/util.ts index a511e6b57f..dee4917996 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/util.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/util.ts @@ -4,6 +4,7 @@ import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import type { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { PeriodType } from "@calcom/prisma/enums"; import type { CustomInputSchema } from "@calcom/prisma/zod-utils"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -202,7 +203,7 @@ export const mapEventType = async (eventType: EventType) => ({ users: await Promise.all( (!!eventType?.hosts?.length ? eventType?.hosts.map((host) => host.user) : eventType.users).map( async (u) => - await UserRepository.enrichUserWithItsProfile({ + await new UserRepository(prisma).enrichUserWithItsProfile({ user: u, }) ) @@ -214,7 +215,7 @@ export const mapEventType = async (eventType: EventType) => ({ users: await Promise.all( c.users.map( async (u) => - await UserRepository.enrichUserWithItsProfile({ + await new UserRepository(prisma).enrichUserWithItsProfile({ user: u, }) ) diff --git a/packages/trpc/server/routers/viewer/me/get.handler.ts b/packages/trpc/server/routers/viewer/me/get.handler.ts index 939a3ca48c..b25f9228a7 100644 --- a/packages/trpc/server/routers/viewer/me/get.handler.ts +++ b/packages/trpc/server/routers/viewer/me/get.handler.ts @@ -27,7 +27,7 @@ export const getHandler = async ({ ctx, input }: MeOptions) => { sessionUser ); - const user = await UserRepository.enrichUserWithTheProfile({ + const user = await new UserRepository(prisma).enrichUserWithTheProfile({ user: sessionUser, upId: session.upId, }); diff --git a/packages/trpc/server/routers/viewer/me/myStats.handler.ts b/packages/trpc/server/routers/viewer/me/myStats.handler.ts index 0b90317fea..40c8ba6dea 100644 --- a/packages/trpc/server/routers/viewer/me/myStats.handler.ts +++ b/packages/trpc/server/routers/viewer/me/myStats.handler.ts @@ -1,6 +1,7 @@ import type { Session } from "next-auth"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; type MyStatsOptions = { @@ -13,7 +14,7 @@ type MyStatsOptions = { export const myStatsHandler = async ({ ctx }: MyStatsOptions) => { const { user: sessionUser } = ctx; - const additionalUserInfo = await UserRepository.getUserStats({ userId: sessionUser.id }); + const additionalUserInfo = await new UserRepository(prisma).getUserStats({ userId: sessionUser.id }); const sumOfTeamEventTypes = additionalUserInfo?.teams.reduce( (sum, team) => sum + team.team.eventTypes.length, diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts index 20ac6e90df..b4bb25507c 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts @@ -262,7 +262,7 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { }); } - const user = await UserRepository.enrichUserWithItsProfile({ + const user = await new UserRepository(prisma).enrichUserWithItsProfile({ user: { ...orgOwner, organizationId: organization.id }, }); @@ -308,7 +308,7 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { } if (!organization.id) throw Error("User not created"); - const user = await UserRepository.enrichUserWithItsProfile({ + const user = await new UserRepository(prisma).enrichUserWithItsProfile({ user: { ...orgOwner, organizationId: organization.id }, }); diff --git a/packages/trpc/server/routers/viewer/organizations/createTeams.handler.ts b/packages/trpc/server/routers/viewer/organizations/createTeams.handler.ts index bf8972eadf..56d6df4311 100644 --- a/packages/trpc/server/routers/viewer/organizations/createTeams.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/createTeams.handler.ts @@ -84,7 +84,7 @@ export const createTeamsHandler = async ({ ctx, input }: CreateTeamsOptions) => const [teamSlugs, userSlugs] = [ await prisma.team.findMany({ where: { parentId: orgId }, select: { slug: true } }), - await UserRepository.findManyByOrganization({ organizationId: orgId }), + await new UserRepository(prisma).findManyByOrganization({ organizationId: orgId }), ]; const existingSlugs = teamSlugs diff --git a/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts index 792dabf557..f681e7985f 100644 --- a/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts @@ -250,7 +250,7 @@ export const listMembersHandler = async ({ ctx, input }: GetOptions) => { const members = await Promise.all( teamMembers?.map(async (membership) => { - const user = await UserRepository.enrichUserWithItsProfile({ user: membership.user }); + const user = await new UserRepository(prisma).enrichUserWithItsProfile({ user: membership.user }); let attributes; if (expand?.includes("attributes")) { diff --git a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts index e65f2b7814..c0913bf96e 100644 --- a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts @@ -108,7 +108,7 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => { for (const membership of members) { enrichedMemberships.push({ ...membership, - user: await UserRepository.enrichUserWithItsProfile({ + user: await new UserRepository(prisma).enrichUserWithItsProfile({ user: membership.user, }), }); 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 ff35fd7aec..1e14d039f5 100644 --- a/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts +++ b/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts @@ -17,6 +17,7 @@ import { getOrderedListOfLuckyUsers } from "@calcom/lib/server/getLuckyUser"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { UserRepository } from "@calcom/lib/server/repository/user"; import type { PrismaClient } from "@calcom/prisma"; +import prisma from "@calcom/prisma"; import { getAbsoluteEventTypeRedirectUrl } from "@calcom/routing-forms/getEventTypeRedirectUrl"; import { getSerializableForm } from "@calcom/routing-forms/lib/getSerializableForm"; import { getServerTimingHeader } from "@calcom/routing-forms/lib/getServerTimingHeader"; @@ -54,7 +55,7 @@ async function getEnrichedSerializableForm< >(form: TForm) { const formWithUserInfoProfile = { ...form, - user: await UserRepository.enrichUserWithItsProfile({ user: form.user }), + user: await new UserRepository(prisma).enrichUserWithItsProfile({ user: form.user }), }; const serializableForm = await getSerializableForm({ diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 901c0a2be5..ba31859a7b 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -50,6 +50,7 @@ import { SelectedSlotsRepository } from "@calcom/lib/server/repository/selectedS import { TeamRepository } from "@calcom/lib/server/repository/team"; import { UserRepository, withSelectedCalendars } from "@calcom/lib/server/repository/user"; import getSlots from "@calcom/lib/slots"; +import prisma from "@calcom/prisma"; import { PeriodType } from "@calcom/prisma/client"; import { SchedulingType } from "@calcom/prisma/enums"; import type { EventBusyDate, EventBusyDetails } from "@calcom/types/Calendar"; @@ -140,7 +141,7 @@ export class AvailableSlotsService { } const dynamicEventType = getDefaultEvent(input.eventTypeSlug); - const usersForDynamicEventType = await UserRepository.findManyUsersForDynamicEventType({ + const usersForDynamicEventType = await new UserRepository(prisma).findManyUsersForDynamicEventType({ currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null, usernameList: Array.isArray(input.usernameList) ? input.usernameList @@ -246,7 +247,7 @@ export class AvailableSlotsService { ) { const { currentOrgDomain, isValidOrgDomain } = organizationDetails; log.info("getUserIdFromUsername", safeStringify({ organizationDetails, username })); - const [user] = await UserRepository.findUsersByUsername({ + const [user] = await new UserRepository(prisma).findUsersByUsername({ usernameList: [username], orgSlug: isValidOrgDomain ? currentOrgDomain : null, }); diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts index 4163e338b0..f210d63ae4 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts @@ -7,6 +7,7 @@ import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server/i18n"; import { isOrganisationOwner } from "@calcom/lib/server/queries/organisations"; import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; import type { CreationSource } from "@calcom/prisma/enums"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; @@ -266,7 +267,10 @@ const inviteMembers = async ({ ctx, input }: InviteMemberOptions) => { if (isPlatform) { inviterOrgId = team.id; orgSlug = team ? team.slug || requestedSlugForTeam : null; - isInviterOrgAdmin = await UserRepository.isAdminOrOwnerOfTeam({ userId: inviter.id, teamId: team.id }); + isInviterOrgAdmin = await new UserRepository(prisma).isAdminOrOwnerOfTeam({ + userId: inviter.id, + teamId: team.id, + }); } await ensureAtleastAdminPermissions({ diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index 6c6c3d13c4..14d44477ca 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -150,7 +150,7 @@ export function canBeInvited(invitee: UserWithMembership, team: TeamWithParent) // If he is invited to a sub-team and is already part of the organization. if ( team.parentId && - UserRepository.isAMemberOfOrganization({ user: invitee, organizationId: team.parentId }) + new UserRepository(prisma).isAMemberOfOrganization({ user: invitee, organizationId: team.parentId }) ) { return INVITE_STATUS.CAN_BE_INVITED; } @@ -565,7 +565,7 @@ export function getAutoJoinStatus({ const isAutoAcceptEmail = connectionInfoMap[invitee.email].autoAccept; const isUserMemberOfTheTeamsParentOrganization = team.parentId - ? UserRepository.isAMemberOfOrganization({ user: invitee, organizationId: team.parentId }) + ? new UserRepository(prisma).isAMemberOfOrganization({ user: invitee, organizationId: team.parentId }) : null; if (isUserMemberOfTheTeamsParentOrganization) { diff --git a/packages/trpc/server/routers/viewer/teams/legacyListMembers.handler.ts b/packages/trpc/server/routers/viewer/teams/legacyListMembers.handler.ts index d465214ecf..677aa13622 100644 --- a/packages/trpc/server/routers/viewer/teams/legacyListMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/legacyListMembers.handler.ts @@ -106,7 +106,7 @@ export const legacyListMembers = async ({ ctx, input }: ListMembersOptions) => { const enrichedMembers = await Promise.all( memberships.map(async (membership) => - UserRepository.enrichUserWithItsProfile({ + new UserRepository(prisma).enrichUserWithItsProfile({ user: { ...membership.user, accepted: membership.accepted, diff --git a/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts b/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts index 65550f118c..ed2aeadbd5 100644 --- a/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts @@ -78,7 +78,7 @@ export const listMembersHandler = async ({ ctx, input }: ListMembersHandlerOptio const membersWithApps = await Promise.all( teamMembers.map(async (member) => { - const user = await UserRepository.enrichUserWithItsProfile({ + const user = await new UserRepository(prisma).enrichUserWithItsProfile({ user: member.user, }); const { profile, ...restUser } = user; diff --git a/yarn.lock b/yarn.lock index ad53c21f61..fc876c0ec4 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.249" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.252" "@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.249": - version: 0.0.249 - resolution: "@calcom/platform-libraries@npm:0.0.249" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.252": + version: 0.0.252 + resolution: "@calcom/platform-libraries@npm:0.0.252" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 5ca9eec4c30ffa78b817979202dc33fa96f701b97f54ed946311b0f6a35d1a3c6f37947229cef3b3133438134bf82bbea15dfe47b32156f0da6b1f59c9ed9763 + checksum: 70a3a5013e59c4df218464ad63c074e958f53388f7552085048f34083bf2dfac987cc953d21a4ab447d61d08c5e2be74c7557262b8f960885a284f046606af31 languageName: node linkType: hard