Skip to content

feat: api v2 team invite link endpoint#6

Open
tomerqodo wants to merge 2 commits into
coderabbit_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#6
tomerqodo wants to merge 2 commits into
coderabbit_full_base_feat_api_v2_team_invite_link_endpoint_pr6from
coderabbit_full_head_feat_api_v2_team_invite_link_endpoint_pr6

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 29, 2026

Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#6

Summary by CodeRabbit

  • New Features

    • Added team invite link generation functionality, allowing team members with appropriate permissions to create secure invite links for onboarding new team members.
  • Tests

    • Added comprehensive test coverage for invite functionality, validating access control across three user scenarios: team admins, members, and non-members.

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

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request introduces a complete Teams Invite feature for API v2, including a new TeamsInviteModule with controller and output DTOs, comprehensive E2E test coverage for role-based access control, and updates to TeamService's invite creation and link-building logic.

Changes

Cohort / File(s) Summary
Module Integration
apps/api/v2/src/ee/platform-endpoints-module.ts, apps/api/v2/src/modules/teams/invite/teams-invite.module.ts
Added TeamsInviteModule to platform endpoints with dependencies on PrismaModule, RedisModule, and MembershipsModule.
Invite Controller & Endpoint
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts
New POST /v2/teams/:teamId/invite endpoint with TEAM_MEMBER role requirement that calls TeamService.createInvite.
Output DTOs
apps/api/v2/src/modules/teams/invite/outputs/invite.output.ts
New InviteDataDto (token, inviteLink) and CreateInviteOutputDto (status, data) with validation and API documentation decorators.
E2E Test Suite
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts
Comprehensive tests covering three scenarios: admin can create invites with valid tokens, members cannot create (403), and non-members cannot create (403).
TeamService Invite Logic
packages/features/ee/teams/services/teamService.ts
Modified createInvite to return newToken.identifier; inverted conditional in buildInviteLink to prefer organization links when in org context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With invites hopping through the team,
New members join our rabbit dream!
Tokens dance and links align,
Together we grow, hop by hop so fine! ✨

🚥 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' directly summarizes the main change: adding a new API v2 endpoint for team invite link creation. It is concise, clear, and specific.
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

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 67-92: Fix two upstream issues so the invite tests pass: in
TeamService.createInvite ensure the method returns the actual token string (the
generated token value) not newToken.identifier (the descriptive key) so callers
like the tests and inviteLink assertions receive the real token; update the
return value/reference in the createInvite implementation to return the token
variable/field that holds the raw token. Also align controller role requirements
in teams-invite.controller (the `@Roles` decoration) with test expectations by
changing `@Roles`("TEAM_MEMBER") to the intended admin role (e.g.,
`@Roles`("TEAM_ADMIN") or the exact enum/value used in the tests) or vice versa so
the RolesGuard behavior matches the test that a MEMBER receives 403; ensure you
reference the same role constant/enum used elsewhere in the app.

In `@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts`:
- Line 21: Replace the direct feature-layer import with the platform library
re-export: update the import of TeamService in teams-invite.controller.ts so it
comes from the public boundary export (use the TeamService symbol imported from
"@calcom/platform-libraries" instead of
"@calcom/features/ee/teams/services/teamService"); ensure any usages of
TeamService remain unchanged and remove the old import.
- Around line 31-32: Update the access decorator on the POST /invite handler so
only admins can create invites: in the TeamsInviteController replace the current
`@Roles`("TEAM_MEMBER") annotation on the method handling the "/invite" POST route
with `@Roles`("TEAM_ADMIN") to ensure MEMBER-role users are denied as expected by
the e2e tests.

In `@packages/features/ee/teams/services/teamService.ts`:
- Around line 98-101: The returned object mistakenly returns newToken.identifier
(which is the generated identifier string) instead of the actual shareable token
stored in the local variable token, causing inviteLink and token to mismatch;
update the return in the function that builds the invite to return token (the
raw token value) and keep inviteLink built via
TeamService.buildInviteLink(token, ...), ensuring the returned token and
inviteLink are consistent (referencing newToken.identifier, token, and
TeamService.buildInviteLink to locate the change).

Comment on lines +67 to +92
it("should create a team invite", async () => {
return request(app.getHttpServer())
.post(`/v2/teams/${team.id}/invite`)
.expect(200)
.then((response) => {
expect(response.body.status).toEqual(SUCCESS_STATUS);
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);
});
});

it("should create a new invite on each request", async () => {
const first = await request(app.getHttpServer()).post(`/v2/teams/${team.id}/invite`).expect(200);
const firstToken = first.body.data.token as string;

return request(app.getHttpServer())
.post(`/v2/teams/${team.id}/invite`)
.expect(200)
.then((response) => {
expect(response.body.status).toEqual(SUCCESS_STATUS);
expect(response.body.data.token).not.toEqual(firstToken);
expect(response.body.data.inviteLink).toEqual(expect.any(String));
expect(response.body.data.inviteLink).toContain(response.body.data.token);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tests are well-structured but will fail due to upstream issues.

The test logic is correct and follows good practices:

  • Validates response structure and status
  • Verifies token/inviteLink integrity
  • Confirms new tokens are generated per request

However, these tests will currently fail due to two issues identified in the reviewed files:

  1. Service bug: TeamService.createInvite returns newToken.identifier (the descriptive key) instead of the actual token, so the assertion at line 75 (inviteLink.toContain(token)) will fail.

  2. Role mismatch: The controller uses @Roles("TEAM_MEMBER"), but the test at line 149 expects a MEMBER role user to receive 403. This suggests either the role should be TEAM_ADMIN or the RolesGuard implementation differs from expectations.

Please address the upstream issues in teamService.ts and teams-invite.controller.ts to make these tests pass.

🤖 Prompt for AI Agents
In
`@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts`
around lines 67 - 92, Fix two upstream issues so the invite tests pass: in
TeamService.createInvite ensure the method returns the actual token string (the
generated token value) not newToken.identifier (the descriptive key) so callers
like the tests and inviteLink assertions receive the real token; update the
return value/reference in the createInvite implementation to return the token
variable/field that holds the raw token. Also align controller role requirements
in teams-invite.controller (the `@Roles` decoration) with test expectations by
changing `@Roles`("TEAM_MEMBER") to the intended admin role (e.g.,
`@Roles`("TEAM_ADMIN") or the exact enum/value used in the tests) or vice versa so
the RolesGuard behavior matches the test that a MEMBER receives 403; ensure you
reference the same role constant/enum used elsewhere in the app.

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
Copy Markdown

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:

#!/bin/bash
# Check how other API v2 modules import services
rg "from.*@calcom/features" apps/api/v2/src/modules --type ts | head -30

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 250


🏁 Script executed:

#!/bin/bash
# Also check what imports are used in other API v2 controllers
fd "\.controller\.ts$" apps/api/v2/src/modules --exec head -30 {} \; | grep "^import" | head -30

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 2428


🏁 Script executed:

#!/bin/bash
# Check if there's an eslint rule or import restriction config
fd "eslint\|import" apps/api/v2 --type f -e js -e ts -e json | head -20

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Check if TeamService exists in the API v2 teams module
find apps/api/v2/src/modules/teams -name "*service*" -type f

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Check what's exported from `@calcom/platform-libraries` related to teams
rg "export.*Team|TeamService" packages/platform/libraries --type ts | head -20

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 1497


🏁 Script executed:

#!/bin/bash
# Confirm the exact import structure by checking platform-libraries index
rg "export.*TeamService" packages/platform/libraries/index.ts -A 1 -B 1

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Check how other API v2 services import from platform-libraries
rg "from.*@calcom/platform-libraries" apps/api/v2/src/modules --type ts | head -10

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 1501


Use @calcom/platform-libraries to import TeamService.

API v2 modules should not import directly from @calcom/features. The TeamService is already re-exported through @calcom/platform-libraries, which is the proper boundary layer for API v2 consumption.

Change line 21 from:

import { TeamService } from "@calcom/features/ee/teams/services/teamService";

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 direct feature-layer import with the platform library
re-export: update the import of TeamService in teams-invite.controller.ts so it
comes from the public boundary export (use the TeamService symbol imported from
"@calcom/platform-libraries" instead of
"@calcom/features/ee/teams/services/teamService"); ensure any usages of
TeamService remain unchanged and remove the old import.

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

Copy link
Copy Markdown

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:

#!/bin/bash
# Find the RolesGuard implementation to understand how roles are interpreted
find apps/api/v2/src -name "*roles*" -type f | head -20

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 1860


🏁 Script executed:

#!/bin/bash
# Search for the e2e tests mentioned (lines 148-150)
rg -B 5 -A 5 "MEMBER.*403|403.*MEMBER" apps/api/v2 --type ts

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Look for role definitions
rg "TEAM_MEMBER|TEAM_ADMIN" apps/api/v2/src --type ts | head -30

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 3390


🏁 Script executed:

#!/bin/bash
# Read the entire teams-invite.controller.ts file to see the e2e tests
cat -n apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 1785


🏁 Script executed:

#!/bin/bash
# Read the RolesGuard implementation
cat -n apps/api/v2/src/modules/auth/guards/roles/roles.guard.ts

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 9214


🏁 Script executed:

#!/bin/bash
# Check if there's an e2e spec file for the invite controller
find apps/api/v2/src/modules/teams/invite -name "*.e2e-spec.ts" -o -name "*.spec.ts"

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Read the e2e spec file to find the tests
cat -n apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 8677


🏁 Script executed:

#!/bin/bash
# Find and read the TEAM_ROLES constants
rg -A 10 "export const TEAM_ROLES" apps/api/v2/src/lib/roles/constants.ts

Repository: agentic-review-benchmarks/cal.com-combined-coderabbit

Length of output: 239


Change @Roles("TEAM_MEMBER") to @Roles("TEAM_ADMIN") to match test expectations.

The e2e test at lines 148-150 expects users with MEMBER role to receive a 403 response when creating invites. However, @Roles("TEAM_MEMBER") grants access to any team member (MEMBER, ADMIN, or OWNER). Based on the role hierarchy where lower indices indicate higher privilege, a MEMBER-role user with the current decorator would pass validation (index 2 ≤ 2). To restrict invite creation to admins only, use @Roles("TEAM_ADMIN") instead, which would correctly deny MEMBER-role users (index 2 > 1).

🐛 Proposed fix
   `@Post`("/invite")
-  `@Roles`("TEAM_MEMBER")
+  `@Roles`("TEAM_ADMIN")
   `@ApiOperation`({ summary: "Create team invite link" })
🤖 Prompt for AI Agents
In `@apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts`
around lines 31 - 32, Update the access decorator on the POST /invite handler so
only admins can create invites: in the TeamsInviteController replace the current
`@Roles`("TEAM_MEMBER") annotation on the method handling the "/invite" POST route
with `@Roles`("TEAM_ADMIN") to ensure MEMBER-role users are denied as expected by
the e2e tests.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Returns identifier instead of actual token value.

The return statement at line 99 returns newToken.identifier which is the string "invite-link-for-teamId-${teamId}", not the actual shareable token. The inviteLink is correctly built using the raw token variable, causing a mismatch.

This will break the e2e test assertion at line 75 (expect(response.body.data.inviteLink).toContain(response.body.data.token)) since the returned token won't be contained in the invite link.

🐛 Proposed fix
     return {
-      token: newToken.identifier,
+      token: newToken.token,
       inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
     };

Or alternatively, since token variable already holds the correct value:

     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: newToken.token,
inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization),
};
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 returned object mistakenly returns newToken.identifier (which is the
generated identifier string) instead of the actual shareable token stored in the
local variable token, causing inviteLink and token to mismatch; update the
return in the function that builds the invite to return token (the raw token
value) and keep inviteLink built via TeamService.buildInviteLink(token, ...),
ensuring the returned token and inviteLink are consistent (referencing
newToken.identifier, token, and TeamService.buildInviteLink to locate the
change).

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