Skip to content

feat: api v2 team invite link endpoint#33

Open
tomerqodo wants to merge 2 commits intocoderabbit_full_base_feat_api_v2_team_invite_link_endpoint_pr6from
coderabbit_full_head_feat_api_v2_team_invite_link_endpoint_pr6
Open

feat: api v2 team invite link endpoint#33
tomerqodo wants to merge 2 commits intocoderabbit_full_base_feat_api_v2_team_invite_link_endpoint_pr6from
coderabbit_full_head_feat_api_v2_team_invite_link_endpoint_pr6

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 29, 2026

Benchmark PR from agentic-review-benchmarks#6

Summary by CodeRabbit

Release Notes

  • New Features

    • Added team invite functionality with generated invitation tokens and shareable invite links.
    • Team invite creation includes role-based access restrictions.
  • Tests

    • Added comprehensive end-to-end tests for team invite functionality across different access levels.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Walkthrough

This PR introduces a new Teams Invite feature with a dedicated controller, module, and DTOs. It registers the new TeamsInviteModule in the platform endpoints and updates TeamService to return token identifiers and revise invitation link logic for organizational context handling.

Changes

Cohort / File(s) Summary
Module Registration
apps/api/v2/src/ee/platform-endpoints-module.ts
Added import and registration of TeamsInviteModule in PlatformEndpointsModule.
Teams Invite Module & Controller
apps/api/v2/src/modules/teams/invite/teams-invite.module.ts, apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts
Created new NestJS module wiring PrismaModule, RedisModule, and MembershipsModule. Introduced TeamsInviteController with ApiAuthGuard and RolesGuard securing POST /invite endpoint that delegates to TeamService.createInvite.
Output DTOs
apps/api/v2/src/modules/teams/invite/outputs/invite.output.ts
Added InviteDataDto with token and inviteLink fields and CreateInviteOutputDto wrapping status and nested data for API responses.
E2E Tests
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts
New test suite validating role-based access control—admin users can create invites with token/link responses, members and non-members receive 403 errors. Verifies token regeneration on repeated requests.
Service Updates
packages/features/ee/teams/services/teamService.ts
Updated createInvite to return token identifier instead of raw token. Inverted buildInviteLink logic to return team invitation link for organization context, otherwise organization signup link.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Controller as TeamsInviteController
    participant Service as TeamService
    participant DB as Prisma/Redis

    Client->>Controller: POST /v2/teams/:teamId/invite
    Note over Controller: ApiAuthGuard & RolesGuard verify auth & ADMIN/MEMBER role
    Controller->>Service: createInvite(teamId)
    Service->>DB: Create token record in Redis
    Service->>DB: Retrieve token identifier
    Service->>Service: Build invite link (org or team context)
    Service-->>Controller: {token: identifier, inviteLink: url}
    Controller-->>Client: {status: SUCCESS_STATUS, data: {token, inviteLink}}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A new invite takes flight,
With tokens and links shining bright,
Guards check who's allowed,
To join the team proud,
Fresh module makes everything right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: api v2 team invite link endpoint' clearly and specifically describes the main change: adding a new API v2 endpoint for team invite links.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@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: 4

🤖 Fix all issues with AI agents
In
`@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts`:
- Around line 73-76: The test in teams-invite.controller.e2e-spec.ts fails
because teamService.ts returns newToken.identifier instead of the actual token
string used in inviteLink; update the token-generation/return path in the
service (the function that creates the invite token, e.g., the
createInvite/createInviteToken method in teamService.ts) so it returns the
actual token value (the property that was originally generated for the invite,
e.g., newToken.token or the correct token field) rather than
newToken.identifier, ensuring the response.body.data.token and
response.body.data.inviteLink contain the same token.

In `@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts`:
- Line 21: Replace the inconsistent import of TeamService in
teams-invite.controller.ts: change the current import that references
"@calcom/features/ee/teams/services/teamService" to import TeamService from
"@calcom/platform-libraries" so it matches other API v2 modules (ensure the
named import remains TeamService and update the import statement at the top of
the file).
- Around line 31-32: Update the route authorization on the TeamsInviteController
invite endpoint: replace the `@Roles`("TEAM_MEMBER") decorator on the POST
"/invite" handler with `@Roles`("TEAM_ADMIN") so only admin-level roles
(TEAM_ADMIN and above) can create invites; locate the decorator on the method
that defines POST("/invite") in teams-invite.controller.ts and change the role
string accordingly.

In `@packages/features/ee/teams/services/teamService.ts`:
- Around line 98-101: The return value is exposing newToken.identifier (a
predictable static string) instead of the actual randomly generated token stored
in the local variable token, causing mismatched token vs inviteLink; change the
returned object to use the token variable (the cryptographic token created
earlier) and ensure TeamService.buildInviteLink(token, ...) uses the same token
so both token and inviteLink match (replace newToken.identifier with token in
the return).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2333387 and 505197c.

📒 Files selected for processing (6)
  • apps/api/v2/src/ee/platform-endpoints-module.ts
  • apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts
  • apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts
  • apps/api/v2/src/modules/teams/invite/outputs/invite.output.ts
  • apps/api/v2/src/modules/teams/invite/teams-invite.module.ts
  • packages/features/ee/teams/services/teamService.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts (2)
apps/api/v2/test/fixtures/repository/users.repository.fixture.ts (1)
  • UserRepositoryFixture (7-62)
packages/platform/constants/api.ts (1)
  • SUCCESS_STATUS (9-9)
apps/api/v2/src/modules/teams/invite/outputs/invite.output.ts (1)
packages/platform/constants/api.ts (2)
  • SUCCESS_STATUS (9-9)
  • ERROR_STATUS (10-10)
apps/api/v2/src/modules/teams/invite/teams-invite.module.ts (1)
apps/api/v2/src/ee/platform-endpoints-module.ts (1)
  • Module (22-49)
🪛 Biome (2.3.13)
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts

[error] 21-21: API v2 should only import from @calcom/platform-constants, @calcom/platform-enums, @calcom/platform-libraries, @calcom/platform-types, @calcom/platform-utils, and @calcom/prisma. Other @calcom packages are not allowed.

(lint/style/noRestrictedImports)

🔇 Additional comments (5)
packages/features/ee/teams/services/teamService.ts (1)

104-112: Misleading variable names create confusion about invite link logic.

The variable names teamInviteLink and orgInviteLink appear swapped relative to the isOrgContext condition:

  • When isOrgContext is true (organization context), the method returns teamInviteLink (/teams?token=...)
  • When isOrgContext is false (standalone team), it returns orgInviteLink (/signup?token=...)

Please verify this logic is intentional. If correct, consider renaming the variables to match their actual purpose (e.g., directTeamLink vs signupFlowLink) to avoid future confusion.

apps/api/v2/src/ee/platform-endpoints-module.ts (1)

16-16: LGTM!

The TeamsInviteModule import and registration follows the established pattern in this file.

Also applies to: 36-36

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

1-11: LGTM!

The module structure follows NestJS conventions. The imports align with the controller's dependencies.

apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts (1)

19-208: Good test coverage for RBAC scenarios.

The e2e tests comprehensively cover:

  • Admin can create invites (200)
  • Member (non-admin) cannot create invites (403)
  • Non-member cannot create invites (403)
  • Token changes between requests

Test fixtures and cleanup are properly handled.

apps/api/v2/src/modules/teams/invite/outputs/invite.output.ts (1)

1-38: LGTM!

The DTOs are well-structured with appropriate:

  • Validation decorators (@IsString, @ValidateNested, @IsEnum)
  • Transformation decorators (@Expose, @Type)
  • Swagger documentation with clear descriptions and examples

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +73 to +76
expect(response.body.data.token.length).toBeGreaterThan(0);
expect(response.body.data.inviteLink).toEqual(expect.any(String));
expect(response.body.data.inviteLink).toContain(response.body.data.token);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test will fail due to service bug.

The assertion at line 75 expects inviteLink to contain token, but due to the bug in teamService.ts (returning newToken.identifier instead of the actual token), the response will have a mismatched token value. Once the service bug is fixed, this test should pass.

🤖 Prompt for AI Agents
In
`@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts`
around lines 73 - 76, The test in teams-invite.controller.e2e-spec.ts fails
because teamService.ts returns newToken.identifier instead of the actual token
string used in inviteLink; update the token-generation/return path in the
service (the function that creates the invite token, e.g., the
createInvite/createInviteToken method in teamService.ts) so it returns the
actual token value (the property that was originally generated for the invite,
e.g., newToken.token or the correct token field) rather than
newToken.identifier, ensuring the response.body.data.token and
response.body.data.inviteLink contain the same token.

import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { TeamService } from "@calcom/features/ee/teams/services/teamService";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other API v2 controllers handle similar service imports
rg -n "from \"@calcom/features" apps/api/v2/src/modules --type ts -C2 | head -100

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 635


🏁 Script executed:

#!/bin/bash
# Check for any documented API v2 import restrictions or patterns
fd "tsconfig\|eslint\|import" apps/api/v2 --type f | head -20

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Search for similar TeamService imports or patterns across the codebase
rg -n "TeamService" apps/api/v2/src --type ts -B2 -A2

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 6435


Fix import path to match API v2 conventions.

The TeamService import uses an inconsistent path. All other API v2 modules (teams-memberships, organizations-teams-invite, organizations-teams-memberships, organizations-membership) import TeamService from @calcom/platform-libraries, but this controller imports from @calcom/features/ee/teams/services/teamService. Change line 21 to:

import { TeamService } from "@calcom/platform-libraries";
🧰 Tools
🪛 Biome (2.3.13)

[error] 21-21: API v2 should only import from @calcom/platform-constants, @calcom/platform-enums, @calcom/platform-libraries, @calcom/platform-types, @calcom/platform-utils, and @calcom/prisma. Other @calcom packages are not allowed.

(lint/style/noRestrictedImports)

🤖 Prompt for AI Agents
In `@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts`
at line 21, Replace the inconsistent import of TeamService in
teams-invite.controller.ts: change the current import that references
"@calcom/features/ee/teams/services/teamService" to import TeamService from
"@calcom/platform-libraries" so it matches other API v2 modules (ensure the
named import remains TeamService and update the import statement at the top of
the file).

Comment on lines +31 to +32
@Post("/invite")
@Roles("TEAM_MEMBER")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find apps/api/v2/src -name "*roles*" -type f | grep -E "\.(ts|js)$"

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 2031


🏁 Script executed:

rg -n "class RolesGuard|TEAM_MEMBER|TEAM_ADMIN" apps/api/v2/src/modules/auth/guards --type ts -A 3

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 484


🏁 Script executed:

cat apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts | head -40

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 1485


🏁 Script executed:

cat apps/api/v2/src/modules/auth/guards/roles/roles.guard.ts

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 7796


🏁 Script executed:

find apps/api/v2/src/modules/teams/invite -name "*e2e-spec*" -o -name "*spec.ts"

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 170


🏁 Script executed:

rg -n "TEAM_MEMBER|TEAM_ADMIN" apps/api/v2/src/modules/teams/invite --type ts -B 2 -A 2

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 643


🏁 Script executed:

rg -n "TEAM_ROLES|TEAM_ADMIN|TEAM_MEMBER" apps/api/v2/src/lib/roles/constants --type ts -A 10

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 161


🏁 Script executed:

cat apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 7203


🏁 Script executed:

find apps/api/v2 -name "constants*" -o -name "roles*" | grep -E "constants\.(ts|js)$" | head -20

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 278


🏁 Script executed:

rg -n "export.*TEAM_ROLES\s*=" apps/api/v2 --type ts -A 5

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 437


Change @Roles("TEAM_MEMBER") to @Roles("TEAM_ADMIN").

The current decorator allows all team members to create invites, but the e2e tests confirm that only team admins should have this permission. The RolesGuard evaluates roles based on the TEAM_ROLES hierarchy [TEAM_OWNER, TEAM_ADMIN, TEAM_MEMBER], where users with a role at index 0 or 1 (OWNER/ADMIN) can access endpoints requiring "TEAM_MEMBER" (index 2). However, the test "User is Team Member (not Admin)" expects a 403 response when a MEMBER attempts to create an invite, indicating the endpoint should require ADMIN-level access.

🤖 Prompt for AI Agents
In `@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts`
around lines 31 - 32, Update the route authorization on the
TeamsInviteController invite endpoint: replace the `@Roles`("TEAM_MEMBER")
decorator on the POST "/invite" handler with `@Roles`("TEAM_ADMIN") so only
admin-level roles (TEAM_ADMIN and above) can create invites; locate the
decorator on the method that defines POST("/invite") in
teams-invite.controller.ts and change the role string accordingly.

Comment on lines 98 to 101
return {
token,
token: newToken.identifier,
inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: Returning identifier instead of the actual token.

Line 99 returns newToken.identifier which is the static string "invite-link-for-teamId-${teamId}", not the cryptographically random token generated at line 87. This breaks the invite functionality since:

  1. The returned "token" is predictable and contains no secret
  2. The inviteLink at line 100 still uses the correct token variable, so inviteLink won't contain the returned token value - they'll be mismatched
🐛 Proposed fix
     return {
-      token: newToken.identifier,
+      token,
       inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
     };
📝 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
return {
token,
token: newToken.identifier,
inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
};
return {
token,
inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
};
🤖 Prompt for AI Agents
In `@packages/features/ee/teams/services/teamService.ts` around lines 98 - 101,
The return value is exposing newToken.identifier (a predictable static string)
instead of the actual randomly generated token stored in the local variable
token, causing mismatched token vs inviteLink; change the returned object to use
the token variable (the cryptographic token created earlier) and ensure
TeamService.buildInviteLink(token, ...) uses the same token so both token and
inviteLink match (replace newToken.identifier with token in the return).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant