feat: api v2 team invite link endpoint#33
Conversation
WalkthroughThis 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
Sequence DiagramsequenceDiagram
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}}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 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
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 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
📒 Files selected for processing (6)
apps/api/v2/src/ee/platform-endpoints-module.tsapps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.tsapps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.tsapps/api/v2/src/modules/teams/invite/outputs/invite.output.tsapps/api/v2/src/modules/teams/invite/teams-invite.module.tspackages/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
teamInviteLinkandorgInviteLinkappear swapped relative to theisOrgContextcondition:
- When
isOrgContextistrue(organization context), the method returnsteamInviteLink(/teams?token=...)- When
isOrgContextisfalse(standalone team), it returnsorgInviteLink(/signup?token=...)Please verify this logic is intentional. If correct, consider renaming the variables to match their actual purpose (e.g.,
directTeamLinkvssignupFlowLink) to avoid future confusion.apps/api/v2/src/ee/platform-endpoints-module.ts (1)
16-16: LGTM!The
TeamsInviteModuleimport 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.
| 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); | ||
| }); |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
🧩 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 -100Repository: 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 -20Repository: 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 -A2Repository: 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).
| @Post("/invite") | ||
| @Roles("TEAM_MEMBER") |
There was a problem hiding this comment.
🧩 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 3Repository: 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 -40Repository: qodo-benchmark/cal.com-combined-coderabbit
Length of output: 1485
🏁 Script executed:
cat apps/api/v2/src/modules/auth/guards/roles/roles.guard.tsRepository: 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 2Repository: 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 10Repository: 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.tsRepository: 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 -20Repository: qodo-benchmark/cal.com-combined-coderabbit
Length of output: 278
🏁 Script executed:
rg -n "export.*TEAM_ROLES\s*=" apps/api/v2 --type ts -A 5Repository: 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.
| return { | ||
| token, | ||
| token: newToken.identifier, | ||
| inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), | ||
| }; |
There was a problem hiding this comment.
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:
- The returned "token" is predictable and contains no secret
- The
inviteLinkat line 100 still uses the correcttokenvariable, soinviteLinkwon't contain the returnedtokenvalue - 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.
| 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).
Benchmark PR from agentic-review-benchmarks#6
Summary by CodeRabbit
Release Notes
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.