feat: api v2 team invite link endpoint#6
Conversation
📝 WalkthroughWalkthroughThe 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
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).
| 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); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
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:
-
Service bug:
TeamService.createInvitereturnsnewToken.identifier(the descriptive key) instead of the actual token, so the assertion at line 75 (inviteLink.toContain(token)) will fail. -
Role mismatch: The controller uses
@Roles("TEAM_MEMBER"), but the test at line 149 expects aMEMBERrole user to receive 403. This suggests either the role should beTEAM_ADMINor theRolesGuardimplementation 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"; |
There was a problem hiding this comment.
🧩 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 -30Repository: 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 -30Repository: 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 -20Repository: 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 fRepository: 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 -20Repository: 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 1Repository: 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 -10Repository: 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.
| @Post("/invite") | ||
| @Roles("TEAM_MEMBER") |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 tsRepository: 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 -30Repository: 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.tsRepository: 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.tsRepository: 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.tsRepository: 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.tsRepository: 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.
| return { | ||
| token, | ||
| token: newToken.identifier, | ||
| inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), | ||
| }; |
There was a problem hiding this comment.
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.
| return { | |
| token, | |
| token: newToken.identifier, | |
| inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), | |
| }; | |
| return { | |
| token: newToken.token, | |
| inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), | |
| }; |
| 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).
Benchmark PR from agentic-review-benchmarks#6
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.