Skip to content

Comments

perf: Replace isTeamMember util with an index DB call#24066

Merged
hbjORbj merged 4 commits intomainfrom
perf/remove-isTeamMember
Sep 25, 2025
Merged

perf: Replace isTeamMember util with an index DB call#24066
hbjORbj merged 4 commits intomainfrom
perf/remove-isTeamMember

Conversation

@hbjORbj
Copy link
Contributor

@hbjORbj hbjORbj commented Sep 25, 2025

What does this PR do?

  • Remove isTeamMember util and use MembershipRepository.findUnique call instead

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A - I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

  • Removed isTeamMember from packages/features/ee/teams/lib/queries.ts, eliminating its export.
  • Updated MembershipRepository.findUniqueByUserIdAndTeamId in packages/lib/server/repository/membership.ts to accept an optional accepted boolean and include it in the where filter.
  • Replaced isTeamMember checks in:
    • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts
    • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
      with MembershipRepository.findUniqueByUserIdAndTeamId lookups.
  • Added explicit UNAUTHORIZED errors with message "User is not a member of this team" when no membership is found.
  • No changes to data retrieval logic beyond membership verification adjustments.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title succinctly summarizes the primary change by indicating the removal of the isTeamMember util and its replacement with an index DB call, aligning directly with the diff. It uses a conventional perf: prefix to signal the nature of the update and avoids extraneous details or vague language. The title is concise, clear, and immediately conveys the main objective to any reviewer.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed The pull request description clearly states that the isTeamMember utility is removed and replaced with MembershipRepository.findUnique calls, which directly reflects the changes in the diff. It accurately conveys the intent of the PR without being off-topic or unrelated.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/remove-isTeamMember

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app graphite-app bot requested review from a team September 25, 2025 08:21
@hbjORbj hbjORbj changed the title perf: remove isteammember perf: Remove isTeamMember util Sep 25, 2025
@hbjORbj hbjORbj changed the title perf: Remove isTeamMember util perf: Replace isTeamMember util with an index DB call Sep 25, 2025
@keithwillcode keithwillcode added core area: core, team members only foundation labels Sep 25, 2025
@hbjORbj hbjORbj force-pushed the perf/remove-isTeamMember branch from 7526044 to 45f02e8 Compare September 25, 2025 08:22
@vercel
Copy link

vercel bot commented Sep 25, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 25, 2025 8:26am
cal-eu Ignored Ignored Sep 25, 2025 8:26am

@pull-request-size pull-request-size bot added size/M and removed size/L labels Sep 25, 2025
@dosubot dosubot bot added the teams area: teams, round robin, collective, managed event-types label Sep 25, 2025
sean-brydon
sean-brydon previously approved these changes Sep 25, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d7cdf9 and 0bd9f8e.

📒 Files selected for processing (4)
  • packages/features/ee/teams/lib/queries.ts (0 hunks)
  • packages/lib/server/repository/membership.ts (1 hunks)
  • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts (2 hunks)
  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/features/ee/teams/lib/queries.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/repository/membership.ts
  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
  • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/repository/membership.ts
  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
  • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/repository/membership.ts
  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
  • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: SinghaAnirban005
PR: calcom/cal.com#23343
File: packages/features/insights/server/trpc-router.ts:1080-1101
Timestamp: 2025-08-26T08:08:23.395Z
Learning: In packages/features/insights/server/trpc-router.ts, when filtering personal event types (userId provided, no teamId, not isAll), the query correctly uses user.id (authenticated user) instead of the input userId parameter for security reasons. This prevents users from accessing other users' personal event types by passing arbitrary user IDs.
📚 Learning: 2025-08-27T12:15:43.830Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/testCall.handler.ts:41-44
Timestamp: 2025-08-27T12:15:43.830Z
Learning: In calcom/cal.com, the AgentService.getAgent() method in packages/features/calAIPhone/providers/retellAI/services/AgentService.ts does NOT include authorization checks - it only validates the agentId parameter and directly calls the repository without verifying user/team access. This contrasts with other methods like getAgentWithDetails() which properly use findByIdWithUserAccessAndDetails() for authorization. When reviewing updateToolsFromAgentId() calls, always verify both agent ownership and eventType ownership are checked.

Applied to files:

  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
📚 Learning: 2025-08-26T20:23:28.396Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/calAIPhone/providers/retellAI/services/AgentService.ts:83-88
Timestamp: 2025-08-26T20:23:28.396Z
Learning: In calcom/cal.com PR #22995, the workflow update handler in packages/trpc/server/routers/viewer/workflows/update.handler.ts includes workflow-level authorization via isAuthorized(userWorkflow, ctx.user.id, "workflow.update") which validates the user can update the workflow before calling updateToolsFromAgentId (per maintainer Udit-takkar).

Applied to files:

  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
📚 Learning: 2025-08-26T08:08:23.395Z
Learnt from: SinghaAnirban005
PR: calcom/cal.com#23343
File: packages/features/insights/server/trpc-router.ts:1080-1101
Timestamp: 2025-08-26T08:08:23.395Z
Learning: In packages/features/insights/server/trpc-router.ts, when filtering personal event types (userId provided, no teamId, not isAll), the query correctly uses user.id (authenticated user) instead of the input userId parameter for security reasons. This prevents users from accessing other users' personal event types by passing arbitrary user IDs.

Applied to files:

  • packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts
  • packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts (1)
packages/platform/libraries/index.ts (1)
  • TRPCError (66-66)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
  • GitHub Check: Production builds / Build API v1
🔇 Additional comments (1)
packages/trpc/server/routers/viewer/teams/getInternalNotesPresets.handler.ts (1)

17-25: Same accepted/findUnique bug applies here.

This call hits the same repository bug noted in getMemberAvailability.handler.ts: the helper still performs findUnique with an accepted filter, which Prisma rejects at runtime. Once the repository is updated to use findFirst (or an equivalent fix), this guard will work; until then, this handler will throw on every invocation.

Comment on lines +306 to 323
static async findUniqueByUserIdAndTeamId({
userId,
teamId,
accepted,
}: {
userId: number;
teamId: number;
accepted?: boolean;
}) {
return await prisma.membership.findUnique({
where: {
userId_teamId: {
userId,
teamId,
},
accepted,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the Prisma findUnique misuse with the accepted filter.

prisma.membership.findUnique only accepts a unique selector; adding accepted alongside the userId_teamId composite key makes the query invalid (MembershipWhereUniqueInput rejects non-unique fields). With the current change, any call passing accepted: true will throw an Unknown arg 'accepted' error at runtime. Please switch to a findFirst (or split the check) so the optional accepted predicate is respected.

-    return await prisma.membership.findUnique({
-      where: {
-        userId_teamId: {
-          userId,
-          teamId,
-        },
-        accepted,
-      },
-    });
+    return await prisma.membership.findFirst({
+      where: {
+        userId,
+        teamId,
+        ...(accepted !== undefined ? { accepted } : {}),
+      },
+    });
📝 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
static async findUniqueByUserIdAndTeamId({
userId,
teamId,
accepted,
}: {
userId: number;
teamId: number;
accepted?: boolean;
}) {
return await prisma.membership.findUnique({
where: {
userId_teamId: {
userId,
teamId,
},
accepted,
},
});
static async findUniqueByUserIdAndTeamId({
userId,
teamId,
accepted,
}: {
userId: number;
teamId: number;
accepted?: boolean;
}) {
return await prisma.membership.findFirst({
where: {
userId,
teamId,
...(accepted !== undefined ? { accepted } : {}),
},
});
🤖 Prompt for AI Agents
In packages/lib/server/repository/membership.ts around lines 306 to 323, the
code misuses prisma.membership.findUnique by passing a non-unique field
(accepted) alongside the composite unique userId_teamId key which causes an
"Unknown arg 'accepted'" runtime error; change the call to
prisma.membership.findFirst (or equivalent) and move the selector into a where
object that includes userId, teamId, and the optional accepted predicate so the
optional accepted filter is honored without violating the unique-input contract.

Comment on lines +20 to +28
const membership = await MembershipRepository.findUniqueByUserIdAndTeamId({
userId: ctx.user.id,
teamId: input.teamId,
accepted: true,
});

if (!membership) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not a member of this team" });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix membership lookup: accepted filter breaks findUnique.

MembershipRepository.findUniqueByUserIdAndTeamId currently forwards accepted to prisma.membership.findUnique. Prisma only allows unique fields inside findUnique, so the extra accepted immediately triggers Unknown arg 'accepted' in where.accepted for type MembershipWhereUniqueInput. As a result this guard will now throw for every request, blocking the handler.

Please switch the repository helper to use findFirst (or otherwise add a proper composite unique) before adding the accepted criterion. For example:

-    return await prisma.membership.findUnique({
-      where: {
-        userId_teamId: {
-          userId,
-          teamId,
-        },
-        accepted,
-      },
-    });
+    return await prisma.membership.findFirst({
+      where: {
+        userId,
+        teamId,
+        ...(accepted !== undefined ? { accepted } : {}),
+      },
+    });

Until that happens this handler will remain unusable.

Committable suggestion skipped: line range outside the PR's diff.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2025

E2E results are ready!

@hbjORbj hbjORbj merged commit 8ed0fb6 into main Sep 25, 2025
65 of 67 checks passed
@hbjORbj hbjORbj deleted the perf/remove-isTeamMember branch September 25, 2025 12:55
hbjORbj added a commit that referenced this pull request Sep 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only foundation ready-for-e2e size/M teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants