Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { unstable_cache } from "next/cache";
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
import prisma from "@calcom/prisma";
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";

import { TeamMembersView } from "~/teams/team-members-view";
Expand Down Expand Up @@ -36,8 +37,10 @@ const getCachedTeamRoles = unstable_cache(
const getCachedTeamAttributes = unstable_cache(
async (organizationId?: number) => {
if (!organizationId) return [];
const attributeRepo = new PrismaAttributeRepository(prisma);

try {
return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId: organizationId });
return await attributeRepo.findAllByOrgIdWithOptions({ orgId: organizationId });
} catch (error) {
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { unstable_cache } from "next/cache";

import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
import prisma from "@calcom/prisma";
import { viewerOrganizationsRouter } from "@calcom/trpc/server/routers/viewer/organizations/_router";

import { MembersView } from "~/members/members-view";
Expand All @@ -19,7 +20,9 @@ export const generateMetadata = async () =>

const getCachedAttributes = unstable_cache(
async (orgId: number) => {
return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId });
const attributeRepo = new PrismaAttributeRepository(prisma);

return await attributeRepo.findAllByOrgIdWithOptions({ orgId });
},
undefined,
{ revalidate: 3600, tags: ["viewer.attributes.list"] } // Cache for 1 hour
Expand Down
16 changes: 9 additions & 7 deletions packages/lib/server/repository/PrismaAttributeRepository.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import prisma from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";

export class PrismaAttributeRepository {
static async findManyByNamesAndOrgIdIncludeOptions({
constructor(private prismaClient: PrismaClient) {}

async findManyByNamesAndOrgIdIncludeOptions({
attributeNames,
orgId,
}: {
attributeNames: string[];
orgId: number;
}) {
return prisma.attribute.findMany({
return this.prismaClient.attribute.findMany({
where: {
name: { in: attributeNames, mode: "insensitive" },
teamId: orgId,
Expand All @@ -25,9 +27,9 @@ export class PrismaAttributeRepository {
});
}

static async findManyByOrgId({ orgId }: { orgId: number }) {
async findManyByOrgId({ orgId }: { orgId: number }) {
// It should be a faster query because of lesser number of attributes record and index on teamId
const result = await prisma.attribute.findMany({
const result = await this.prismaClient.attribute.findMany({
where: {
teamId: orgId,
},
Expand All @@ -43,8 +45,8 @@ export class PrismaAttributeRepository {
return result;
}

static async findAllByOrgIdWithOptions({ orgId }: { orgId: number }) {
return await prisma.attribute.findMany({
async findAllByOrgIdWithOptions({ orgId }: { orgId: number }) {
return await this.prismaClient.attribute.findMany({
where: {
teamId: orgId,
},
Expand Down
8 changes: 5 additions & 3 deletions packages/lib/server/repository/host.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import prisma from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";

export class HostRepository {
static async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
return await prisma.host.updateMany({
constructor(private prismaClient: PrismaClient) {}

async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
return await this.prismaClient.host.updateMany({
where: {
userId,
scheduleId: oldScheduleId,
Expand Down
3 changes: 2 additions & 1 deletion packages/lib/service/attribute/server/assignValueToUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const findAttributesByName = async ({
orgId: number;
attributeNames: AttributeName[];
}) => {
const attributesFromDb = await PrismaAttributeRepository.findManyByNamesAndOrgIdIncludeOptions({
const attributeRepo = new PrismaAttributeRepository(prisma);
const attributesFromDb = await attributeRepo.findManyByNamesAndOrgIdIncludeOptions({
attributeNames,
orgId,
});
Expand Down
4 changes: 3 additions & 1 deletion packages/lib/service/attribute/server/getAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,11 @@ async function _getOrgMembershipToUserIdForTeam({ orgId, teamId }: { orgId: numb
}

async function _queryAllData({ orgId, teamId }: { orgId: number; teamId: number }) {
const attributeRepo = new PrismaAttributeRepository(prisma);

const [orgMembershipToUserIdForTeamMembers, attributesOfTheOrg] = await Promise.all([
_getOrgMembershipToUserIdForTeam({ orgId, teamId }),
PrismaAttributeRepository.findManyByOrgId({ orgId }),
attributeRepo.findManyByOrgId({ orgId }),
]);

const orgMembershipIds = Array.from(orgMembershipToUserIdForTeamMembers.keys());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
import prisma from "@calcom/prisma";

import { TRPCError } from "@trpc/server";

Expand All @@ -19,8 +20,9 @@ const listHandler = async (opts: GetOptions) => {
message: "You need to be apart of an organization to use this feature",
});
}
const attributeRepo = new PrismaAttributeRepository(prisma);

return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId: org.id });
return await attributeRepo.findAllByOrgIdWithOptions({ orgId: org.id });
};
Comment on lines +23 to 26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Instance-based repository usage looks good; ensure downstream repo query uses select not include

The switch to new PrismaAttributeRepository(prisma) is on point. However, findAllByOrgIdWithOptions in the repo uses include: { options: true }. Our guideline is “only select data you need; never use include”. Please update the repo method to use select and return only the necessary fields (id, name, slug, type, options[id,value,slug,(isGroup?),(contains?)]).

Run this script to (a) ensure no static uses of PrismaAttributeRepository remain and (b) locate include: usage inside the repository:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking for static method calls on PrismaAttributeRepository..."
rg -nP --type=ts --type=tsx -C2 '\bPrismaAttributeRepository\.\w+\s*\(' || true

echo
echo "Checking for 'include:' usage within PrismaAttributeRepository..."
rg -nP -C3 '(?s)class\s+PrismaAttributeRepository\b.*?\binclude\s*:' || true

Length of output: 500


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locating the PrismaAttributeRepository implementation file..."
rg -l "class\s\+PrismaAttributeRepository" --type=ts --type=js

echo
echo "Inspecting 'include:' usage inside PrismaAttributeRepository..."
for repo_file in $(rg -l "class\s\+PrismaAttributeRepository" --type=ts --type=js); do
  echo "---- $repo_file ----"
  rg -n "include\s*:" "$repo_file" || echo "No 'include:' found in $repo_file"
done

Length of output: 217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locating the repository method definition..."
repo_file=$(rg -l "findAllByOrgIdWithOptions" -g "*.ts" -g "*.js" | head -n1)
if [[ -z "$repo_file" ]]; then
  echo "Error: Method definition not found."
  exit 1
fi
echo "Found in: $repo_file"

echo
echo "Checking for 'include:' vs 'select:' usage in $repo_file..."
rg -n -C2 "include\s*:" "$repo_file" || echo "No 'include:' usage found in $repo_file"
rg -n -C2 "select\s*:" "$repo_file" || echo "No 'select:' usage found in $repo_file"

Length of output: 1319


Refactor findAllByOrgIdWithOptions to use select instead of include

The method in packages/lib/server/repository/PrismaAttributeRepository.ts still uses:

// …  
include: {
  options: true,
},
// …

Please replace it with a select block that returns only the fields you need, for example:

- include: {
-   options: true,
- },
+ select: {
+   id: true,
+   name: true,
+   slug: true,
+   type: true,
+   options: {
+     select: {
+       id: true,
+       value: true,
+       slug: true,
+       isGroup: true,
+       contains: true,
+     },
+   },
+ },

This ensures we “only select data you need; never use include.”

🤖 Prompt for AI Agents
In packages/trpc/server/routers/viewer/attributes/list.handler.ts around lines
23 to 26, the call to attributeRepo.findAllByOrgIdWithOptions triggers a
repository method that uses Prisma's include: { options: true }; change that
repository method in packages/lib/server/repository/PrismaAttributeRepository.ts
to use a select block instead of include, selecting only the attribute fields
and the specific option fields you actually need (e.g., id, value, sortOrder,
etc.), update the return type accordingly, and ensure any callers (including
this handler) still receive the expected shape.


export default listHandler;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { HostRepository } from "@calcom/lib/server/repository/host";
import { prisma } from "@calcom/prisma";

Comment on lines +1 to 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Import style consistency for prisma

This file uses a named import import { prisma } from "@calcom/prisma"; while other files (e.g., the Next.js page) use a default import import prisma from "@calcom/prisma". Ensure we standardize on one style to avoid confusion.

Run this to audit usage:


🏁 Script executed:

#!/bin/bash
rg -n --type=ts -C1 -e $'^import\\s+\\{\\s*prisma\\s*\\}\\s+from\\s+"@calcom/prisma"' -e $'^import\\s+prisma\\s+from\\s+"@calcom/prisma"'

Length of output: 203787


Standardize prisma import style
Update this file to use the default export from @calcom/prisma instead of a named import:

--- packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts
@@
-import { prisma } from "@calcom/prisma";
+import prisma from "@calcom/prisma";

• Only one import style should be used across the codebase—prefer the default import (import prisma from "@calcom/prisma").
• Consider adding an ESLint rule or codemod to enforce this consistently.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { HostRepository } from "@calcom/lib/server/repository/host";
import { prisma } from "@calcom/prisma";
import { HostRepository } from "@calcom/lib/server/repository/host";
import prisma from "@calcom/prisma";
🤖 Prompt for AI Agents
In packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts
around lines 1 to 3, the file imports prisma as a named import but our codebase
standard is to use the default export; change the import to use the default
export from "@calcom/prisma" (import prisma from "@calcom/prisma") and update
any usages if necessary to match the default-imported identifier; ensure no
other named imports are required from that module and run TypeScript/ESLint to
verify, and consider adding an ESLint rule or codemod separately to enforce this
style across the repo.

import { TRPCError } from "@trpc/server";

import type { TrpcSessionUser } from "../../../../types";
import { updateHostsWithNewDefaultSchedule } from "../util";
import type { TDeleteInputSchema } from "./delete.schema";

type DeleteOptions = {
Expand All @@ -15,6 +15,7 @@ type DeleteOptions = {

export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
const { user } = ctx;
const hostRepo = new HostRepository(prisma);

const scheduleToDelete = await prisma.schedule.findUnique({
where: {
Expand Down Expand Up @@ -46,7 +47,7 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
// to throw the error if there arent any other schedules
if (!scheduleToSetAsDefault) throw new TRPCError({ code: "BAD_REQUEST" });

await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id);
await hostRepo.updateHostsSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id);

await prisma.user.update({
where: {
Expand All @@ -57,7 +58,7 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
},
});
} else if (user.defaultScheduleId) {
await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, user.defaultScheduleId);
await hostRepo.updateHostsSchedule(user.id, input.scheduleId, user.defaultScheduleId);
}

await prisma.schedule.delete({
Expand Down
14 changes: 6 additions & 8 deletions packages/trpc/server/routers/viewer/availability/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { describe, expect, it, vi, beforeEach } from "vitest";

import { HostRepository } from "@calcom/lib/server/repository/host";
import { PrismaClient } from "@calcom/prisma";

import {
getDefaultScheduleId,
hasDefaultSchedule,
setupDefaultSchedule,
updateHostsWithNewDefaultSchedule,
} from "./util";
import { getDefaultScheduleId, hasDefaultSchedule, setupDefaultSchedule } from "./util";

vi.mock("@calcom/prisma", () => {
const mockPrisma = {
Expand All @@ -31,9 +27,11 @@ vi.mock("@calcom/prisma", () => {

describe("Availability Utils", () => {
let prisma: PrismaClient;
let hostRepo: HostRepository;

beforeEach(() => {
prisma = new PrismaClient();
hostRepo = new HostRepository(prisma);
vi.clearAllMocks();
});

Expand Down Expand Up @@ -158,7 +156,7 @@ describe("Availability Utils", () => {

(prisma.host.updateMany as any).mockResolvedValue(updateResult);

const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
const result = await hostRepo.updateHostsSchedule(userId, oldScheduleId, newScheduleId);

expect(prisma.host.updateMany).toHaveBeenCalledWith({
where: {
Expand All @@ -180,7 +178,7 @@ describe("Availability Utils", () => {

(prisma.host.updateMany as any).mockResolvedValue(updateResult);

const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
const result = await hostRepo.updateHostsSchedule(userId, oldScheduleId, newScheduleId);

expect(prisma.host.updateMany).toHaveBeenCalledWith({
where: {
Copy link
Contributor Author

@ThyMinimalDev ThyMinimalDev Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempted to fully remove those tests, basically these tests are just testing that a method calling prisma is calling prisma, no real value added

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah would be better as integration tests

Expand Down
9 changes: 0 additions & 9 deletions packages/trpc/server/routers/viewer/availability/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { User } from "@prisma/client";

import { HostRepository } from "@calcom/lib/server/repository/host";
import type { PrismaClient } from "@calcom/prisma";

export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => {
Expand Down Expand Up @@ -54,11 +53,3 @@ export const setupDefaultSchedule = async (userId: number, scheduleId: number, p
},
});
};

export const updateHostsWithNewDefaultSchedule = async (
userId: number,
defaultScheduleId: number,
scheduleId: number
) => {
return await HostRepository.updateHostsSchedule(userId, defaultScheduleId, scheduleId);
};
Comment on lines -58 to -64
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not useful method, just calling repository, deleted it

Loading