Skip to content

Comments

chore: v2 dont allow mixing platform teams/users with non-platform#20296

Merged
supalarry merged 9 commits intomainfrom
lauris/cal-5334-platform-refactor-dont-allow-mixing-managed-users-with-non
Sep 9, 2025
Merged

chore: v2 dont allow mixing platform teams/users with non-platform#20296
supalarry merged 9 commits intomainfrom
lauris/cal-5334-platform-refactor-dont-allow-mixing-managed-users-with-non

Conversation

@supalarry
Copy link
Contributor

Linear CAL-5334

@supalarry supalarry requested a review from a team March 21, 2025 11:23
@supalarry supalarry requested a review from a team as a code owner March 21, 2025 11:23
@linear
Copy link

linear bot commented Mar 21, 2025

@graphite-app graphite-app bot requested a review from a team March 21, 2025 11:23
@keithwillcode keithwillcode added core area: core, team members only platform Anything related to our platform plan labels Mar 21, 2025
@vercel
Copy link

vercel bot commented Mar 21, 2025

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

3 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Preview Sep 9, 2025 0:56am
cal-eu Ignored Ignored Preview Sep 9, 2025 0:56am
calcom-web-canary Ignored Ignored Preview Sep 9, 2025 0:56am

@graphite-app
Copy link

graphite-app bot commented Mar 21, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (03/21/25)

1 reviewer was added to this PR based on Keith Williams's automation.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 21, 2025

E2E results are ready!

@supalarry supalarry disabled auto-merge March 21, 2025 12:57
@github-actions
Copy link
Contributor

github-actions bot commented Apr 5, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Apr 5, 2025
@github-actions github-actions bot removed the Stale label Apr 24, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 9, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label May 9, 2025
@delve-auditor
Copy link

delve-auditor bot commented Jul 4, 2025

No security or compliance issues detected. Reviewed everything up to 87017a8.

Security Overview
  • 🔎 Scanned files: 4 changed file(s)
Detected Code Changes
Change Type Relevant files
Enhancement ► memberships.service.ts
    Add user and team OAuth validation
► organizations.module.ts
    Update module imports and providers
► organizations-teams-memberships.service.ts
    Add team membership validation
► teams-memberships.service.ts
    Add team membership validation

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Warning

Rate limit exceeded

@supalarry has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between ef53f9e and b851809.

📒 Files selected for processing (1)
  • apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts (1 hunks)

Walkthrough

Adds OAuth-client–aware validation to organization and team membership creation. TeamsMembershipsService and OrganizationsMembershipService now inject OAuthClientRepository, expose new error constants, and implement canUserBeAddedToTeam / canUserBeAddedToOrg which enforce that user and team/org are either both non-platform or created by the same OAuth client (throwing BadRequest errors otherwise). OrganizationsTeamsMembershipsService now depends on TeamsMembershipsService and calls its pre-check before creating a team membership. OAuthClientRepository gains getByOrgId. TeamsMembershipsModule exports TeamsMembershipsService. An e2e suite exercises allowed and disallowed combinations.

Possibly related PRs

Pre-merge checks (4 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title concisely and accurately highlights the central change—preventing mixing of platform-managed and non-platform teams/users—and directly reflects the main objective of the changeset without being misleading or off-topic.
Linked Issues Check ✅ Passed The implementation adds validation methods and pre-checks in both TeamsMembershipsService and OrganizationsMembershipService to enforce that team and organization memberships must involve either both non-platform entities or entities created by the same OAuth client, directly satisfying each requirement from CAL-5334.
Out of Scope Changes Check ✅ Passed All modifications—including module wiring updates and comprehensive end-to-end tests—directly support the core objective of preventing mixed platform and non-platform memberships and introduce no unrelated or extraneous code.
Description Check ✅ Passed The description references the linked Linear issue CAL-5334 and stays on topic with the changeset’s objective to enforce platform-managed and non-platform constraints, ensuring it is related to the pull request content.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lauris/cal-5334-platform-refactor-dont-allow-mixing-managed-users-with-non

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.

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: 1

♻️ Duplicate comments (1)
apps/api/v2/src/modules/memberships/services/memberships.service.ts (1)

29-44: Platform/non-platform and same-OAuth-client logic is sound.

Covers all cases; early-returns keep it simple. Nice.

🧹 Nitpick comments (6)
apps/api/v2/src/modules/memberships/services/memberships.service.ts (5)

46-54: Tighten error messages (single-line, consistent phrasing).

Current template literals introduce newlines/indentation in responses. Prefer single-line messages for cleaner API errors.

Apply:

-    if (userOAuthClient && !teamOAuthClient) {
-      throw new BadRequestException(`Can't add user to team - the user is platform managed user but team is not because team probably
-        was not created using OAuth credentials.`);
-    }
+    if (userOAuthClient && !teamOAuthClient) {
+      throw new BadRequestException(
+        "Can't add user to team — user is platform-managed but team is not (team likely not created via OAuth)."
+      );
+    }
@@
-    if (!userOAuthClient && teamOAuthClient) {
-      throw new BadRequestException(`Can't add user to team - the user is not platform managed user but team is platform managed. Both have to be created
-        using OAuth credentials.`);
-    }
+    if (!userOAuthClient && teamOAuthClient) {
+      throw new BadRequestException(
+        "Can't add user to team — user is not platform-managed but team is platform-managed. Both must be created using OAuth."
+      );
+    }

56-60: Minor: unify final message style.

Consider the same dash style and no trailing period for consistency with messages above.

-      throw new BadRequestException(
-        `Can't add user to team - managed user and team were created using different OAuth clients.`
-      );
+      throw new BadRequestException(
+        "Can't add user to team — user and team were created using different OAuth clients"
+      );

62-62: Unreachable return.

After prior returns/throws, this return true is redundant.

-    return true;
+    // no-op: all valid paths already returned; invalid ones threw

29-63: Optional: make this an assertion method returning void.

Call sites don’t use the boolean. Renaming clarifies intent and reduces misleading return types.

-  async canUserBeAddedToTeam(userId: number, teamId: number) {
+  async assertUserCanBeAddedToTeam(userId: number, teamId: number): Promise<void> {
@@
-      return true;
+      return;
@@
-      return true;
+      return;
@@
-    // no-op: all valid paths already returned; invalid ones threw
+    // no-op

Follow-up usage (outside this hunk) in teams-memberships.service.ts:

await this.membershipsService.assertUserCanBeAddedToTeam(data.userId, teamId);

29-63: Tests missing for mismatch cases.

Add unit/integration tests covering: both unmanaged; both managed same client; user-only managed; team-only managed; both managed different clients.

I can scaffold tests with fixtures for user/team OAuth-client mappings if helpful.

apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (1)

17-17: Pre-check before create is correct; consider atomicity

Gating creation via canUserBeAddedToTeam is correct—there remains a TOCTOU window between the check and the repository call; consolidate them into a single repository‐level method or Prisma transaction (e.g., createOrgTeamMembershipIfAllowed). canUserBeAddedToTeam is already async and contains no Prisma include.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 3c72d7a and 4a9b3a6.

📒 Files selected for processing (4)
  • apps/api/v2/src/modules/memberships/services/memberships.service.ts (2 hunks)
  • apps/api/v2/src/modules/organizations/organizations.module.ts (2 hunks)
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (2 hunks)
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{service,repository}.ts

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

Avoid dot-suffixes like .service.ts or .repository.ts for new files; reserve .test.ts, .spec.ts, .types.ts for their specific purposes

Files:

  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/memberships/services/memberships.service.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
**/*.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:

  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/memberships/services/memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.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:

  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/memberships/services/memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.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:

  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/memberships/services/memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
🧬 Code graph analysis (2)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
apps/api/v2/src/modules/teams/memberships/inputs/create-team-membership.input.ts (1)
  • CreateTeamMembershipInput (6-25)
apps/api/v2/src/modules/memberships/services/memberships.service.ts (2)
apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts (1)
  • Injectable (88-1100)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
  • Injectable (9-62)
⏰ 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). (4)
  • GitHub Check: Type check / check-types
  • GitHub Check: Linters / lint
  • GitHub Check: Tests / Unit
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (6)
apps/api/v2/src/modules/memberships/services/memberships.service.ts (1)

29-33: Confirm select-only id in OAuthClientRepository methods
Ensure getByUserId and getByTeamId use a Prisma select: { id: true } clause to fetch only the id field (oauth-client.repository.ts:98–110).

apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (2)

16-19: Pre-check placement is correct.

Guarding createTeamMembership with the assertion ensures invariant before write. Good.


11-14: DI configuration verified
TeamsMembershipsModule already imports MembershipsModule (which exports MembershipsService), so the new DI dependency is correctly wired and no changes are needed.

apps/api/v2/src/modules/organizations/organizations.module.ts (1)

16-16: MembershipsModule exports MembershipsService and MembershipsRepository; import is appropriate.

apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (2)

1-1: LGTM: importing MembershipsService to enforce platform/OAuth policy

Import looks correct and aligns with the PR objective to gate membership creation.


12-14: DI wiring verified; no circular dependency
No reverse import from MembershipsService to OrganizationsTeamsMembershipsService, and MembershipsModule is included in the organizations module imports.

@pull-request-size pull-request-size bot added size/XL and removed size/M labels Sep 9, 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: 3

🧹 Nitpick comments (16)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (2)

11-13: Tighten wording and consistency in user-facing error messages.

Minor grammar/hyphenation for clarity and consistency with “platform-managed” phrasing used elsewhere.

-export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR = `Can't add user to organization - the user is platform managed user but organization is not because organization probably was not created using OAuth credentials.`;
-export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR = `Can't add user to organization - the user is not platform managed user but organization is platform managed. Both have to be created using OAuth credentials.`;
-export const MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to organization - managed user and organization were created using different OAuth clients.`;
+export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR = `Can't add user to organization — the user is platform-managed but the organization is not (likely not created via OAuth).`;
+export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR = `Can't add user to organization — the user is not platform-managed but the organization is. Both must be created via OAuth.`;
+export const MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to organization — the managed user and organization were created using different OAuth clients.`;

109-146: Reduce data fetched, add explicit return type, and simplify logic.

  • Add : Promise<boolean> to the signature.
  • Since getByOrgId can return only { id }, rely on IDs and avoid carrying full rows.
  • Slightly simplify boolean checks to reduce branches.
-  async canUserBeAddedToOrg(userId: number, orgId: number) {
-    const [userOAuthClient, orgOAuthClients] = await Promise.all([
+  async canUserBeAddedToOrg(userId: number, orgId: number): Promise<boolean> {
+    const [userOAuthClient, orgOAuthClients] = await Promise.all([
       this.oAuthClientsRepository.getByUserId(userId),
       this.oAuthClientsRepository.getByOrgId(orgId),
     ]);
 
-    const userAndOrgAreNotPlatform = !userOAuthClient && (!orgOAuthClients || orgOAuthClients.length === 0);
+    const userAndOrgAreNotPlatform = !userOAuthClient && (!orgOAuthClients || orgOAuthClients.length === 0);
     if (userAndOrgAreNotPlatform) {
       return true;
     }
 
-    const userAndOrgAreCreatedBySameOAuthClient =
-      userOAuthClient &&
-      orgOAuthClients &&
-      orgOAuthClients.some((orgClient) => orgClient.id === userOAuthClient.id);
+    const userAndOrgAreCreatedBySameOAuthClient =
+      !!userOAuthClient && !!orgOAuthClients?.some((orgClient) => orgClient.id === userOAuthClient.id);
     if (userAndOrgAreCreatedBySameOAuthClient) {
       return true;
     }
 
     if (userOAuthClient && (!orgOAuthClients || orgOAuthClients.length === 0)) {
       throw new BadRequestException(PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR);
     }
 
     if (!userOAuthClient && orgOAuthClients && orgOAuthClients.length > 0) {
       throw new BadRequestException(REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR);
     }
 
-    if (
-      userOAuthClient &&
-      orgOAuthClients &&
-      orgOAuthClients.length > 0 &&
-      !orgOAuthClients.some((orgClient) => orgClient.id === userOAuthClient.id)
-    ) {
+    if (userOAuthClient && orgOAuthClients?.length && !orgOAuthClients.some((c) => c.id === userOAuthClient.id)) {
       throw new BadRequestException(MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR);
     }
 
     return true;
   }

Note: getByUserId currently returns a full row; if feasible, add a narrow variant (e.g., getIdByUserId) or update it to select: { id: true } to fully adhere to the “select-only” guideline.

Would you like me to raise a follow-up PR to introduce getIdByUserId and migrate the two membership services to use it?

apps/api/v2/src/modules/organizations/organizations.module.ts (2)

69-71: Prefer importing TeamsMembershipsModule over hand-wiring service/repository

Wiring TeamsMembershipsService and TeamsMembershipsRepository directly couples modules. If TeamsMembershipsModule already exports the service, import that module instead for cleaner DI boundaries.

Apply:

+import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";

And see diff below (providers section).


153-156: Import TeamsMembershipsModule and drop duplicate providers
TeamsMembershipsModule exports TeamsMembershipsService, so add it to imports and remove TeamsMembershipsService and TeamsMembershipsRepository from providers to avoid multiple instances.

apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (3)

9-12: Tighten error copy (optional)

Messages are clear but a bit verbose. Consider shorter, action-focused phrasing to reduce log noise (keeping exact strings if tests assert them).


67-99: Return void from guard and drop unused booleans

The guard’s return value isn’t used. Returning void simplifies the contract and avoids misleading “true” returns.

-  async canUserBeAddedToTeam(userId: number, teamId: number) {
+  async canUserBeAddedToTeam(userId: number, teamId: number): Promise<void> {
@@
-    if (userAndTeamAreNotPlatform) {
-      return true;
-    }
+    if (userAndTeamAreNotPlatform) return;
@@
-    if (userAndTeamAreCreatedBySameOAuthClient) {
-      return true;
-    }
+    if (userAndTeamAreCreatedBySameOAuthClient) return;
@@
-    return true;
+    return;

67-71: Fetch only what you need from Prisma

If OAuthClientRepository returns full rows, add repo methods that select only { id: true } to reduce payload and surface area.

I can send a follow-up PR adding getIdByUserId/getIdByTeamId with select and wire them here.

apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts (9)

109-111: Remove duplicate ApiKeysRepositoryFixture instantiation.

It’s already constructed on Line 79.

-    apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
     const { keyString } = await apiKeysRepositoryFixture.createApiKey(orgOwner.id, null);
     orgApiKey = `cal_test_${keyString}`;

309-319: Disambiguate test title.

Two tests share the same name; make the scope explicit.

-    it("should not add managed user to organization team", async () => {
+    it("should not add managed user to organization team (org route)", async () => {

321-333: Disambiguate test title (teams route).

-    it("should not add managed user to organization team", async () => {
+    it("should not add managed user to organization team (teams route)", async () => {

378-389: Disambiguate platform test title (org route).

-      it("should not add user to platform organization team", async () => {
+      it("should not add user to platform organization team (org route)", async () => {

390-403: Disambiguate platform test title (teams route).

-      it("should not add user to platform organization team", async () => {
+      it("should not add user to platform organization team (teams route)", async () => {

9-11: Import org-level OAuth-client mismatch error constant to cover missing negative case.

 import {
   PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR,
   REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR,
+  MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR,
 } from "@/modules/organizations/memberships/services/organizations-membership.service";

418-419: Add missing negative: platform org membership with user from a different OAuth client should fail.

       });
     });
+    it("should not add user to platform organization because user is created using different oAuth client", async () => {
+      const response = await request(app.getHttpServer())
+        .post(`/v2/organizations/${platformOrg.id}/memberships`)
+        .set("x-cal-client-id", platformOAuthClient.id)
+        .set("x-cal-secret-key", platformOAuthClient.secret)
+        .send({
+          userId: secondPlatformOrgUser.user.id,
+          accepted: true,
+          role: "MEMBER",
+        } satisfies CreateOrgTeamMembershipDto)
+        .expect(400);
+      expect(response.body.error.message).toEqual(
+        MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR
+      );
+    });

25-27: Prefer concrete NestExpressApplication typing to avoid casting.

-import { INestApplication } from "@nestjs/common";
 import { NestExpressApplication } from "@nestjs/platform-express";
@@
-  let app: INestApplication;
+  let app: NestExpressApplication;
@@
-    app = moduleRef.createNestApplication();
-    bootstrap(app as NestExpressApplication);
+    app = moduleRef.createNestApplication<NestExpressApplication>();
+    bootstrap(app);

Also applies to: 43-47, 150-154


450-456: Avoid Prisma include in fixtures; select only needed fields.

oauthClientRepositoryFixture.getUsers() uses include: { users: true }. Per guidelines, switch to select to fetch only the needed user fields (email).

Apply this change in apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts:

-    const response = await this.prismaReadClient.platformOAuthClient.findFirst({
-      where: { id: clientId },
-      include: {
-        users: true,
-      },
-    });
+    const response = await this.prismaReadClient.platformOAuthClient.findFirst({
+      where: { id: clientId },
+      select: {
+        users: { select: { email: true } },
+      },
+    });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 4a9b3a6 and fdd9399.

📒 Files selected for processing (7)
  • apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts (1 hunks)
  • apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (2 hunks)
  • apps/api/v2/src/modules/organizations/organizations.module.ts (4 hunks)
  • apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts (1 hunks)
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (1 hunks)
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (2 hunks)
  • apps/api/v2/src/modules/teams/memberships/teams-memberships.module.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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:

  • apps/api/v2/src/modules/teams/memberships/teams-memberships.module.ts
  • apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
  • apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.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:

  • apps/api/v2/src/modules/teams/memberships/teams-memberships.module.ts
  • apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
  • apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.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:

  • apps/api/v2/src/modules/teams/memberships/teams-memberships.module.ts
  • apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
  • apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
  • apps/api/v2/src/modules/organizations/organizations.module.ts
**/*.{service,repository}.ts

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

Avoid dot-suffixes like .service.ts or .repository.ts for new files; reserve .test.ts, .spec.ts, .types.ts for their specific purposes

Files:

  • apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
  • apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
  • apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts
  • apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
🧬 Code graph analysis (4)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (4)
apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts (1)
  • Injectable (6-144)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
  • Injectable (13-100)
apps/api/v2/src/modules/organizations/memberships/organizations-membership.repository.ts (1)
  • Injectable (32-122)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership-output.service.ts (1)
  • Injectable (16-89)
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (2)
apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts (1)
  • Injectable (6-144)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
  • Injectable (13-100)
apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts (13)
apps/api/v2/test/fixtures/repository/membership.repository.fixture.ts (1)
  • MembershipRepositoryFixture (8-44)
apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts (1)
  • OAuthClientRepositoryFixture (8-64)
apps/api/v2/test/fixtures/repository/profiles.repository.fixture.ts (1)
  • ProfileRepositoryFixture (6-30)
apps/api/v2/test/fixtures/repository/api-keys.repository.fixture.ts (1)
  • ApiKeysRepositoryFixture (6-37)
apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output.ts (2)
  • CreateManagedUserData (9-16)
  • CreateManagedUserOutput (18-31)
apps/api/v2/src/app.ts (1)
  • bootstrap (27-89)
apps/api/v2/src/modules/users/inputs/create-user.input.ts (1)
  • CreateUserInput (24-165)
packages/platform/constants/api.ts (1)
  • SUCCESS_STATUS (9-9)
apps/api/v2/src/modules/users/inputs/create-managed-user.input.ts (1)
  • CreateManagedUserInput (10-79)
packages/platform/types/api.ts (1)
  • ApiSuccessResponse (8-8)
apps/api/v2/src/modules/organizations/teams/memberships/inputs/create-organization-team-membership.input.ts (1)
  • CreateOrgTeamMembershipDto (6-24)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (2)
  • PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR (11-11)
  • REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR (12-12)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (3)
  • PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR (9-9)
  • REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR (10-10)
  • PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR (11-11)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (3)
apps/api/v2/src/modules/oauth-clients/oauth-client.repository.ts (1)
  • Injectable (6-144)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (1)
  • Injectable (15-147)
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (1)
  • Injectable (9-83)
⏰ 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). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (14)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (3)

1-1: DI wiring and imports look correct.

Importing the repository and exceptions is appropriate for the new pre-checks.

Also applies to: 4-4


19-21: Constructor changes LGTM.

New dependency is clearly injected and used only for pre-checks.


103-107: Good guard: pre-validate before creating membership.

Early eligibility check matches the PR objective and mirrors the teams flow.

apps/api/v2/src/modules/teams/memberships/teams-memberships.module.ts (1)

15-15: No circular dependency detected between TeamsMembershipsModule and OrganizationsModule; no forwardRef needed.

apps/api/v2/src/modules/organizations/organizations.module.ts (1)

99-99: Import of MembershipsModule looks good

No issues spotted with the module import.

apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (3)

4-4: LGTM: Using TeamsMembershipsService for pre-checks

Import is appropriate and aligns org-team membership creation with team membership rules.


17-18: Pre-check before creation is correct; mirror on updates if userId can change

If UpdateOrgTeamMembershipDto allows changing userId, add the same canUserBeAddedToTeam guard in update to keep invariants intact.

Do we allow userId changes? If yes, I can draft the guarded update flow.


12-14: Confirm potential circular dependency between TeamsMembershipsService and OrganizationsModule

TeamsMembershipsModule and OrganizationsTeamsMembershipsModule imports arrays don’t reference each other; manually verify whether TeamsMembershipsService (or its module) imports OrganizationsModule transitively and, if so, inject via @Inject(forwardRef(() => TeamsMembershipsService)).

apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (5)

1-1: LGTM: Injecting OAuthClientRepository

Necessary for platform/OAuth consistency checks.


5-5: LGTM: Exception types added

BadRequestException is appropriate for mismatch cases.


15-19: LGTM: DI for repo + OAuth client repo

Constructor wiring looks good.


21-23: LGTM: Pre-check integrated into create path

This enforces invariants on creation.


68-71: Assumption: one OAuth client per user/team

Using findFirst implies singular ownership. Verify schema guarantees at most one PlatformOAuthClient per user and per team; otherwise, compare against all linked client ids.

I can script a quick check against the Prisma schema or adjust logic to handle many-to-many if needed.

apps/api/v2/src/modules/organizations/teams/memberships/e2e/organizations-teams-memberships-guard.controller.e2e-spec.ts (1)

179-185: Confirm intentional route prefix mix (/v2 vs /api/v2).

Platform endpoints use /api/v2/... while org endpoints use /v2/.... Validate this aligns with the router setup and won’t regress if global prefixes/versioning change.

Also applies to: 207-212, 238-243

Copy link
Contributor

@volnei volnei left a comment

Choose a reason for hiding this comment

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

LGTM

@supalarry supalarry merged commit a2d0cbf into main Sep 9, 2025
43 checks passed
@supalarry supalarry deleted the lauris/cal-5334-platform-refactor-dont-allow-mixing-managed-users-with-non branch September 9, 2025 19:24
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 platform Anything related to our platform plan ready-for-e2e size/XL Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants