diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 16ba151377..e96aeca30e 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.252", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.253", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/web/app/(use-page-wrapper)/(main-nav)/teams/server-page.tsx b/apps/web/app/(use-page-wrapper)/(main-nav)/teams/server-page.tsx index 7a38747fd7..27db5f6a4b 100644 --- a/apps/web/app/(use-page-wrapper)/(main-nav)/teams/server-page.tsx +++ b/apps/web/app/(use-page-wrapper)/(main-nav)/teams/server-page.tsx @@ -6,6 +6,7 @@ import { unstable_cache } from "next/cache"; import { TeamsListing } from "@calcom/features/ee/teams/components/TeamsListing"; import { TeamRepository } from "@calcom/lib/server/repository/team"; import { TeamService } from "@calcom/lib/server/service/team"; +import prisma from "@calcom/prisma"; import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router"; import { TRPCError } from "@trpc/server"; @@ -14,7 +15,8 @@ import { TeamsCTA } from "./CTA"; const getCachedTeams = unstable_cache( async (userId: number) => { - return await TeamRepository.findTeamsByUserId({ + const teamRepo = new TeamRepository(prisma); + return await teamRepo.findTeamsByUserId({ userId, includeOrgs: true, }); diff --git a/packages/features/ee/billing/credit-service.test.ts b/packages/features/ee/billing/credit-service.test.ts index a11ad985ac..e94a93962a 100644 --- a/packages/features/ee/billing/credit-service.test.ts +++ b/packages/features/ee/billing/credit-service.test.ts @@ -388,10 +388,13 @@ describe("CreditService", () => { describe("getMonthlyCredits", () => { it("should return 0 if subscription is not active", async () => { - vi.mocked(TeamRepository.findTeamWithMembers).mockResolvedValue({ - id: 1, - members: [{ accepted: true }], - } as any); + const mockTeamRepo = { + findTeamWithMembers: vi.fn().mockResolvedValue({ + id: 1, + members: [{ accepted: true }], + }), + }; + vi.mocked(TeamRepository).mockImplementation(() => mockTeamRepo as any); const mockTeamBillingService = { getSubscriptionStatus: vi.fn().mockResolvedValue("trialing"), @@ -405,10 +408,13 @@ describe("CreditService", () => { }); it("should calculate credits based on active members and price", async () => { - vi.mocked(TeamRepository.findTeamWithMembers).mockResolvedValue({ - id: 1, - members: [{ accepted: true }, { accepted: true }, { accepted: true }], - } as any); + const mockTeamRepo = { + findTeamWithMembers: vi.fn().mockResolvedValue({ + id: 1, + members: [{ accepted: true }, { accepted: true }, { accepted: true }], + }), + }; + vi.mocked(TeamRepository).mockImplementation(() => mockTeamRepo as any); const mockTeamBillingService = { getSubscriptionStatus: vi.fn().mockResolvedValue("active"), diff --git a/packages/features/ee/billing/credit-service.ts b/packages/features/ee/billing/credit-service.ts index 9166e49ae5..72e5e0c636 100644 --- a/packages/features/ee/billing/credit-service.ts +++ b/packages/features/ee/billing/credit-service.ts @@ -532,7 +532,8 @@ export class CreditService { } async getMonthlyCredits(teamId: number) { - const team = await TeamRepository.findTeamWithMembers(teamId); + const teamRepo = new TeamRepository(prisma); + const team = await teamRepo.findTeamWithMembers(teamId); if (!team) return 0; diff --git a/packages/lib/createAProfileForAnExistingUser.ts b/packages/lib/createAProfileForAnExistingUser.ts index ddbbbffd0a..a84eeb437c 100644 --- a/packages/lib/createAProfileForAnExistingUser.ts +++ b/packages/lib/createAProfileForAnExistingUser.ts @@ -21,7 +21,8 @@ export const createAProfileForAnExistingUser = async ({ }; organizationId: number; }) => { - const org = await TeamRepository.findById({ id: organizationId }); + const teamRepo = new TeamRepository(prisma); + const org = await teamRepo.findById({ id: organizationId }); if (!org) { throw new Error(`Organization with id ${organizationId} not found`); } diff --git a/packages/lib/server/repository/TeamRepository.test.ts b/packages/lib/server/repository/TeamRepository.test.ts index 1af241381d..c620625b26 100644 --- a/packages/lib/server/repository/TeamRepository.test.ts +++ b/packages/lib/server/repository/TeamRepository.test.ts @@ -6,8 +6,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { TeamRepository } from "./team"; describe("TeamRepository", () => { + let teamRepository: TeamRepository; + beforeEach(() => { vi.resetAllMocks(); + teamRepository = new TeamRepository(prismaMock); }); afterEach(() => { @@ -17,7 +20,7 @@ describe("TeamRepository", () => { describe("findById", () => { it("should return null if team is not found", async () => { prismaMock.team.findUnique.mockResolvedValue(null); - const result = await TeamRepository.findById({ id: 1 }); + const result = await teamRepository.findById({ id: 1 }); expect(result).toBeNull(); }); @@ -37,7 +40,7 @@ describe("TeamRepository", () => { requestedSlug: null, }; prismaMock.team.findUnique.mockResolvedValue(mockTeam as unknown as Team); - const result = await TeamRepository.findById({ id: 1 }); + const result = await teamRepository.findById({ id: 1 }); expect(result).toEqual(mockTeam); }); }); @@ -67,7 +70,7 @@ describe("TeamRepository", () => { return callback(mockTx); }); - const result = await TeamRepository.deleteById({ id: 1 }); + const result = await teamRepository.deleteById({ id: 1 }); expect(deleteManyEventTypeMock).toHaveBeenCalledWith({ where: { @@ -93,7 +96,7 @@ describe("TeamRepository", () => { it("should return all teams with given parentId", async () => { const mockTeams = [{ id: 1 }, { id: 2 }]; prismaMock.team.findMany.mockResolvedValue(mockTeams as unknown as Team[]); - const result = await TeamRepository.findAllByParentId({ parentId: 1 }); + const result = await teamRepository.findAllByParentId({ parentId: 1 }); expect(prismaMock.team.findMany).toHaveBeenCalledWith({ where: { parentId: 1 }, select: { @@ -116,7 +119,7 @@ describe("TeamRepository", () => { it("should return team with its members", async () => { const mockTeam = { id: 1, members: [] }; prismaMock.team.findUnique.mockResolvedValue(mockTeam as unknown as Team & { members: [] }); - const result = await TeamRepository.findTeamWithMembers(1); + const result = await teamRepository.findTeamWithMembers(1); expect(prismaMock.team.findUnique).toHaveBeenCalledWith({ where: { id: 1 }, select: { diff --git a/packages/lib/server/repository/team.ts b/packages/lib/server/repository/team.ts index 3bc10350b0..10324e0c08 100644 --- a/packages/lib/server/repository/team.ts +++ b/packages/lib/server/repository/team.ts @@ -3,6 +3,7 @@ import type { z } from "zod"; import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains"; import logger from "@calcom/lib/logger"; +import type { PrismaClient } from "@calcom/prisma"; import prisma from "@calcom/prisma"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; @@ -166,8 +167,10 @@ const teamSelect = { } satisfies Prisma.TeamSelect; export class TeamRepository { - static async findById({ id }: { id: number }) { - const team = await prisma.team.findUnique({ + constructor(private prismaClient: PrismaClient) {} + + async findById({ id }: { id: number }) { + const team = await this.prismaClient.team.findUnique({ where: { id, }, @@ -179,14 +182,14 @@ export class TeamRepository { return getParsedTeam(team); } - static async findAllByParentId({ + async findAllByParentId({ parentId, select = teamSelect, }: { parentId: number; select?: Prisma.TeamSelect; }) { - return await prisma.team.findMany({ + return await this.prismaClient.team.findMany({ where: { parentId, }, @@ -194,7 +197,7 @@ export class TeamRepository { }); } - static async findByIdAndParentId({ + async findByIdAndParentId({ id, parentId, select = teamSelect, @@ -203,7 +206,7 @@ export class TeamRepository { parentId: number; select?: Prisma.TeamSelect; }) { - return await prisma.team.findFirst({ + return await this.prismaClient.team.findFirst({ where: { id, parentId, @@ -212,7 +215,7 @@ export class TeamRepository { }); } - static async findFirstBySlugAndParentSlug({ + async findFirstBySlugAndParentSlug({ slug, parentSlug, select = teamSelect, @@ -221,7 +224,7 @@ export class TeamRepository { parentSlug: string | null; select?: Prisma.TeamSelect; }) { - return await prisma.team.findFirst({ + return await this.prismaClient.team.findFirst({ where: { slug, parent: parentSlug ? whereClauseForOrgWithSlugOrRequestedSlug(parentSlug) : null, @@ -230,8 +233,8 @@ export class TeamRepository { }); } - static async deleteById({ id }: { id: number }) { - const deletedTeam = await prisma.$transaction(async (tx) => { + async deleteById({ id }: { id: number }) { + const deletedTeam = await this.prismaClient.$transaction(async (tx) => { await tx.eventType.deleteMany({ where: { teamId: id, @@ -258,8 +261,8 @@ export class TeamRepository { return deletedTeam; } - static async findTeamWithMembers(teamId: number) { - return await prisma.team.findUnique({ + async findTeamWithMembers(teamId: number) { + return await this.prismaClient.team.findUnique({ where: { id: teamId }, select: { members: { @@ -275,8 +278,8 @@ export class TeamRepository { }); } - static async findTeamsByUserId({ userId, includeOrgs }: { userId: number; includeOrgs?: boolean }) { - const memberships = await prisma.membership.findMany({ + async findTeamsByUserId({ userId, includeOrgs }: { userId: number; includeOrgs?: boolean }) { + const memberships = await this.prismaClient.membership.findMany({ where: { // Show all the teams this user belongs to regardless of the team being part of the user's org or not // We don't want to restrict in the listing here. If we need to restrict a situation where a user is part of the org along with being part of a non-org team, we should do that instead of filtering out from here @@ -317,8 +320,8 @@ export class TeamRepository { })); } - static async findTeamWithOrganizationSettings(teamId: number) { - return await prisma.team.findUnique({ + async findTeamWithOrganizationSettings(teamId: number) { + return await this.prismaClient.team.findUnique({ where: { id: teamId }, select: { parent: { diff --git a/packages/lib/server/service/TeamService.alternative.test.ts b/packages/lib/server/service/TeamService.alternative.test.ts index cf993854d1..acc311adc3 100644 --- a/packages/lib/server/service/TeamService.alternative.test.ts +++ b/packages/lib/server/service/TeamService.alternative.test.ts @@ -35,14 +35,17 @@ const mockTeamBilling = { }; // Mock implementations that modify the in-memory database -vi.mocked(TeamRepository.deleteById).mockImplementation(async ({ id }) => { - const team = database.teams.get(id); - if (team) { - database.teams.delete(id); - return team; - } - throw new Error(`Team with id ${id} not found`); -}); +const mockTeamRepo = { + deleteById: vi.fn().mockImplementation(async ({ id }) => { + const team = database.teams.get(id); + if (team) { + database.teams.delete(id); + return team; + } + throw new Error(`Team with id ${id} not found`); + }), +}; +vi.mocked(TeamRepository).mockImplementation(() => mockTeamRepo as any); vi.mocked(deleteDomain).mockImplementation(async (slug) => { database.domains.delete(slug); diff --git a/packages/lib/server/service/TeamService.test.ts b/packages/lib/server/service/TeamService.test.ts index e98f04ef1f..8ce57f926c 100644 --- a/packages/lib/server/service/TeamService.test.ts +++ b/packages/lib/server/service/TeamService.test.ts @@ -47,14 +47,17 @@ describe("TeamService", () => { }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - vi.mocked(TeamRepository.deleteById).mockResolvedValue(mockDeletedTeam); + const mockTeamRepo = { + deleteById: vi.fn().mockResolvedValue(mockDeletedTeam), + }; + vi.mocked(TeamRepository).mockImplementation(() => mockTeamRepo as any); const result = await TeamService.delete({ id: 1 }); expect(TeamBilling.findAndInit).toHaveBeenCalledWith(1); expect(mockTeamBilling.cancel).toHaveBeenCalled(); expect(WorkflowService.deleteWorkflowRemindersOfRemovedTeam).toHaveBeenCalledWith(1); - expect(TeamRepository.deleteById).toHaveBeenCalledWith({ id: 1 }); + expect(mockTeamRepo.deleteById).toHaveBeenCalledWith({ id: 1 }); expect(deleteDomain).toHaveBeenCalledWith("deleted-team"); expect(result).toEqual(mockDeletedTeam); }); diff --git a/packages/lib/server/service/insightsBooking.ts b/packages/lib/server/service/insightsBooking.ts index e035613501..6d9bb04f40 100644 --- a/packages/lib/server/service/insightsBooking.ts +++ b/packages/lib/server/service/insightsBooking.ts @@ -144,7 +144,8 @@ export class InsightsBookingService { options: Extract ): Promise { // Get all teams from the organization - const teamsFromOrg = await TeamRepository.findAllByParentId({ + const teamRepo = new TeamRepository(this.prisma); + const teamsFromOrg = await teamRepo.findAllByParentId({ parentId: options.orgId, select: { id: true }, }); @@ -183,7 +184,8 @@ export class InsightsBookingService { private async buildTeamAuthorizationCondition( options: Extract ): Promise { - const childTeamOfOrg = await TeamRepository.findByIdAndParentId({ + const teamRepo = new TeamRepository(this.prisma); + const childTeamOfOrg = await teamRepo.findByIdAndParentId({ id: options.teamId, parentId: options.orgId, select: { id: true }, diff --git a/packages/lib/server/service/insightsRouting.ts b/packages/lib/server/service/insightsRouting.ts index 4b35ac2a06..e28d0248fb 100644 --- a/packages/lib/server/service/insightsRouting.ts +++ b/packages/lib/server/service/insightsRouting.ts @@ -126,7 +126,8 @@ export class InsightsRoutingService { options: Extract ): Promise { // Get all teams from the organization - const teamsFromOrg = await TeamRepository.findAllByParentId({ + const teamRepo = new TeamRepository(this.prisma); + const teamsFromOrg = await teamRepo.findAllByParentId({ parentId: options.orgId, select: { id: true }, }); @@ -141,7 +142,8 @@ export class InsightsRoutingService { private async buildTeamAuthorizationCondition( options: Extract ): Promise { - const childTeamOfOrg = await TeamRepository.findByIdAndParentId({ + const teamRepo = new TeamRepository(this.prisma); + const childTeamOfOrg = await teamRepo.findByIdAndParentId({ id: options.teamId, parentId: options.orgId, select: { id: true }, diff --git a/packages/lib/server/service/team.ts b/packages/lib/server/service/team.ts index b402c109e1..424809e590 100644 --- a/packages/lib/server/service/team.ts +++ b/packages/lib/server/service/team.ts @@ -34,7 +34,8 @@ export class TeamService { } // Step 3: Delete the team from the database. This is the core "commit" point. - const deletedTeam = await TeamRepository.deleteById({ id }); + const teamRepo = new TeamRepository(prisma); + const deletedTeam = await teamRepo.deleteById({ id }); // Step 4: Clean up any final, non-critical external state. if (deletedTeam && deletedTeam.isOrganization && deletedTeam.slug) { diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index ba31859a7b..4c58e327a9 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -292,7 +292,8 @@ export class AvailableSlotsService { organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean } ) { const { currentOrgDomain, isValidOrgDomain } = organizationDetails; - const team = await TeamRepository.findFirstBySlugAndParentSlug({ + const teamRepo = new TeamRepository(prisma); + const team = await teamRepo.findFirstBySlugAndParentSlug({ slug, parentSlug: isValidOrgDomain && currentOrgDomain ? currentOrgDomain : null, select: { id: true }, diff --git a/packages/trpc/server/routers/viewer/teams/list.handler.ts b/packages/trpc/server/routers/viewer/teams/list.handler.ts index c50dd1df52..de41749d3e 100644 --- a/packages/trpc/server/routers/viewer/teams/list.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/list.handler.ts @@ -1,4 +1,5 @@ import { TeamRepository } from "@calcom/lib/server/repository/team"; +import prisma from "@calcom/prisma"; import type { TrpcSessionUser } from "../../../types"; import type { TGetListSchema } from "./list.schema"; @@ -11,7 +12,8 @@ type ListOptions = { }; export const listHandler = async ({ ctx, input }: ListOptions) => { - return TeamRepository.findTeamsByUserId({ + const teamRepo = new TeamRepository(prisma); + return teamRepo.findTeamsByUserId({ userId: ctx.user.id, includeOrgs: input?.includeOrgs, }); diff --git a/yarn.lock b/yarn.lock index fc876c0ec4..5c6a40b307 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.252" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.253" "@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.252": - version: 0.0.252 - resolution: "@calcom/platform-libraries@npm:0.0.252" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.253": + version: 0.0.253 + resolution: "@calcom/platform-libraries@npm:0.0.253" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: 70a3a5013e59c4df218464ad63c074e958f53388f7552085048f34083bf2dfac987cc953d21a4ab447d61d08c5e2be74c7557262b8f960885a284f046606af31 + checksum: 797435f9a72750dc7f8bc6d3b52b1b1fa93701451907f4ae29028403619365649bdc7c5632ac1db326bd6ed1b90e27544d14ae108e042fe18eaeac4ca11e8606 languageName: node linkType: hard