-
Notifications
You must be signed in to change notification settings - Fork 10.2k
fix: Consistently remove a member from organization/sub-team/team - through all the APIs and webapp actions #22806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughThis change refactors the logic for removing users from teams and organizations across the codebase. The main removal logic is consolidated within a new Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Hey there and thank you for opening this pull request! 👋🏼 We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted. Details:
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎ |
E2E results are ready! |
…embership-deletion
8069a94
to
262ffe4
Compare
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
5f8035e
to
fe77f51
Compare
fe77f51
to
881ba5d
Compare
881ba5d
to
5cd93f3
Compare
…ic methods - Moved removeMember and all related helper functions from separate file into TeamService class - Made all functions private static methods instead of exporting them - Deleted the original removeMember.ts file since it's no longer needed - Updated imports to use the new location - All integration tests pass successfully
- Renamed memberId parameter to userId in removeMember private method - Renamed memberIds parameter to userIds in removeMembers public method - Updated all calls to removeMembers to use userIds instead of memberIds - Parameter names now accurately reflect that they are user IDs, not membership IDs
…letion - Add unit tests for membership deletion services - Remove redundant deletion behavior tests from controllers - Keep only happy path tests in e2e controller tests - Fix unused imports and variables
baf51ea
to
3fef285
Compare
Graphite Automations"Add consumer team as reviewer" took an action on this PR • (07/31/25)1 reviewer was added to this PR based on Keith Williams's automation. |
There was a problem hiding this 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
🔭 Outside diff range comments (2)
apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts (1)
112-137
: Fix duplicate membership updates and incorrect return valueThe method updates the membership twice with the same parameters and returns the result from the first update instead of the second. This appears to be unintentional code duplication.
Apply this diff to fix the logic:
async updateTeamMembership( @Param("teamId", ParseIntPipe) teamId: number, @Param("membershipId", ParseIntPipe) membershipId: number, @Body() body: UpdateTeamMembershipInput ): Promise<UpdateTeamMembershipOutput> { - const membership = await this.teamsMembershipsService.updateTeamMembership(teamId, membershipId, body); - const currentMembership = await this.teamsMembershipsService.getTeamMembership(teamId, membershipId); const updatedMembership = await this.teamsMembershipsService.updateTeamMembership( teamId, membershipId, body ); if (!currentMembership.accepted && updatedMembership.accepted) { try { await updateNewTeamMemberEventTypes(updatedMembership.userId, teamId); } catch (err) { this.logger.error("Could not update new team member eventTypes", err); } } return { status: SUCCESS_STATUS, - data: plainToClass(TeamMembershipOutput, membership, { strategy: "excludeAll" }), + data: plainToClass(TeamMembershipOutput, updatedMembership, { strategy: "excludeAll" }), }; }packages/lib/server/service/teamService.ts (1)
84-112
: Consider error handling strategy for batch removalsThe current implementation uses
Promise.all
for member removals, which will reject entirely if any single removal fails. This could leave the operation in a partially completed state.Consider using
Promise.allSettled
to handle failures gracefully and provide detailed results:static async removeMembers({ teamIds, userIds, isOrg = false, }: { teamIds: number[]; userIds: number[]; isOrg?: boolean; }) { const deleteMembershipPromises: Promise<RemoveMemberResult>[] = []; for (const userId of userIds) { for (const teamId of teamIds) { deleteMembershipPromises.push( TeamService.removeMember({ teamId, userId, isOrg, }) ); } } - await Promise.all(deleteMembershipPromises); + const results = await Promise.allSettled(deleteMembershipPromises); + + // Log failures but continue with billing updates + const failures = results.filter((r) => r.status === 'rejected'); + if (failures.length > 0) { + log.error('Some membership removals failed', { failures }); + } const teamsBilling = await TeamBilling.findAndInitMany(teamIds); const teamBillingPromises = teamsBilling.map((teamBilling) => teamBilling.updateQuantity()); await Promise.allSettled(teamBillingPromises); + + // Optionally, throw an aggregate error if any removals failed + if (failures.length > 0) { + throw new Error(`Failed to remove ${failures.length} membership(s)`); + } }
🧹 Nitpick comments (4)
packages/platform/libraries/package.json (1)
3-3
: Consider using semantic versioning instead of 9.9.9.The version jump from "0.0.0" to "9.9.9" is unusual and doesn't follow typical semantic versioning practices. For a significant refactor introducing new
TeamService
functionality, consider using a more appropriate version like "1.0.0" (major version for breaking changes) or "0.1.0" (minor version if maintaining pre-1.0 status).- "version": "9.9.9", + "version": "1.0.0",apps/api/v2/package.json (1)
41-41
: Dependency version update aligns with package changes.The update to "@calcom/platform-libraries@9.9.9" is consistent with the version bump in the libraries package. However, consider using proper semantic versioning (e.g., "1.0.0") instead of "9.9.9" for better version management practices.
packages/lib/server/service/teamService.ts (1)
195-270
: Consider implementing the TODO: Move fetch methods to repositoriesThe TODO comments correctly identify that these fetch methods should be moved to their respective repository classes for better separation of concerns and adherence to the repository pattern.
Consider creating:
MembershipRepository
forfetchMembershipOrThrow
- Update existing
TeamRepository
forfetchTeamOrThrow
UserRepository
forfetchUserOrThrow
This would improve code organization and make the service layer focus on business logic rather than data access.
packages/lib/server/service/__tests__/teamService.integration-test.ts (1)
174-178
: Simplify the membership deletion query.The current OR mapping can be simplified.
- await prisma.membership.deleteMany({ - where: { - OR: teamIds.map((teamId) => ({ teamId })), - }, - }); + await prisma.membership.deleteMany({ + where: { + teamId: { in: teamIds }, + }, + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (17)
apps/api/v2/package.json
(1 hunks)apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
(2 hunks)apps/api/v2/src/modules/organizations/teams/memberships/organizations-teams-memberships.controller.ts
(0 hunks)apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
(2 hunks)apps/api/v2/src/modules/teams/event-types/services/teams-event-types.service.ts
(0 hunks)apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts
(1 hunks)apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts
(2 hunks)apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts
(1 hunks)apps/api/v2/test/fixtures/repository/membership.repository.fixture.ts
(1 hunks)packages/features/ee/dsync/lib/removeUserFromOrg.ts
(1 hunks)packages/features/ee/teams/lib/removeMember.ts
(0 hunks)packages/lib/server/service/__tests__/teamService.integration-test.ts
(1 hunks)packages/lib/server/service/teamService.ts
(3 hunks)packages/platform/libraries/index.ts
(1 hunks)packages/platform/libraries/package.json
(1 hunks)packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
(1 hunks)packages/trpc/server/routers/viewer/teams/removeMember.test.ts
(0 hunks)
💤 Files with no reviewable changes (4)
- apps/api/v2/src/modules/teams/event-types/services/teams-event-types.service.ts
- packages/features/ee/teams/lib/removeMember.ts
- apps/api/v2/src/modules/organizations/teams/memberships/organizations-teams-memberships.controller.ts
- packages/trpc/server/routers/viewer/teams/removeMember.test.ts
🧰 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/organizations/memberships/services/organizations-membership.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 useinclude
, always useselect
Ensure thecredential.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/test/fixtures/repository/event-types.repository.fixture.ts
apps/api/v2/test/fixtures/repository/membership.repository.fixture.ts
packages/platform/libraries/index.ts
packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
packages/features/ee/dsync/lib/removeUserFromOrg.ts
apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
packages/lib/server/service/__tests__/teamService.integration-test.ts
packages/lib/server/service/teamService.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/test/fixtures/repository/event-types.repository.fixture.ts
apps/api/v2/test/fixtures/repository/membership.repository.fixture.ts
packages/platform/libraries/index.ts
packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts
packages/features/ee/dsync/lib/removeUserFromOrg.ts
apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts
packages/lib/server/service/__tests__/teamService.integration-test.ts
packages/lib/server/service/teamService.ts
**/*Service.ts
📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)
Service files must include
Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts
)
Files:
packages/lib/server/service/teamService.ts
🧠 Learnings (6)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
packages/platform/libraries/index.ts (1)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (1)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (2)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Repository.ts : Repository files must include Repository
suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts
), and use PascalCase matching the exported class
packages/lib/server/service/__tests__/teamService.integration-test.ts (2)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{service,repository}.ts : Avoid dot-suffixes like .service.ts
or .repository.ts
for new files; reserve .test.ts
, .spec.ts
, .types.ts
for their specific purposes
packages/lib/server/service/teamService.ts (1)
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*Service.ts : Service files must include Service
suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts
)
🧬 Code Graph Analysis (6)
apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (1)
packages/lib/server/service/teamService.ts (1)
TeamService
(50-381)
apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts (1)
packages/lib/event-types/getEventTypeById.ts (1)
EventType
(34-34)
packages/trpc/server/routers/viewer/teams/removeMember.handler.ts (1)
packages/lib/server/service/teamService.ts (1)
TeamService
(50-381)
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (1)
packages/lib/server/service/teamService.ts (1)
TeamService
(50-381)
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (1)
packages/lib/server/service/teamService.ts (1)
TeamService
(50-381)
packages/lib/server/service/__tests__/teamService.integration-test.ts (1)
packages/lib/server/service/teamService.ts (1)
TeamService
(50-381)
🔇 Additional comments (16)
packages/platform/libraries/index.ts (1)
122-123
: LGTM! Proper export of the centralized TeamService.The addition of
TeamService
export aligns with the PR objective to centralize team membership removal logic. The export is correctly placed and follows the established patterns in this file.packages/trpc/server/routers/viewer/teams/removeMember.handler.ts (1)
62-62
: LGTM! Improved API design with named parameters.The change from positional arguments to a named parameter object improves the API's maintainability and reduces the chance of parameter ordering errors. The parameter mapping from
memberIds
touserIds
aligns with the newTeamService.removeMembers
signature.apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts (1)
63-65
: LGTM! Useful addition for test scenarios.The new
findById
method follows established patterns in the fixture class, correctly uses the read client for query operations, and maintains consistency with the parameter typing used in thedelete
method. This method will be valuable for testing event type retrieval scenarios, especially given the enhanced event type cleanup logic in the newTeamService.removeMembers
implementation.apps/api/v2/test/fixtures/repository/membership.repository.fixture.ts (1)
39-41
: LGTM! Good addition for testing the refactored services.The new
findById
method correctly usesfindUnique
for ID-based lookups, which is more semantically appropriate and potentially more efficient thanfindFirst
. This supports the testing needs for the refactored deletion services.apps/api/v2/src/modules/teams/memberships/services/teams-memberships.service.ts (2)
6-6
: LGTM! Proper import for the refactored TeamService.The import correctly brings in
TeamService
from@calcom/platform-libraries
to support the centralized member removal functionality.
45-56
: Excellent refactor for proper cleanup and error handling.The updated
deleteTeamMembership
method now:
- Fetches the membership first to ensure it exists and get the
userId
- Provides clear error messaging if membership not found
- Delegates to
TeamService.removeMembers
for comprehensive cleanup of related data- Uses
isOrg: false
appropriately for team (not organization) membershipsThis ensures atomic cleanup of memberships, event types, hosts, and other related data, addressing the core issue described in the PR objectives.
apps/api/v2/src/modules/organizations/teams/memberships/services/organizations-teams-memberships.service.ts (2)
6-6
: LGTM! Consistent import pattern.The import correctly adds
TeamService
from@calcom/platform-libraries
for the centralized member removal functionality.
62-79
: Excellent refactor with improved error messaging.The updated
deleteOrgTeamMembership
method follows the same pattern as other services:
- Fetches membership first to validate existence and get
userId
- Provides detailed error message including all relevant IDs for better debugging
- Uses
TeamService.removeMembers
withisOrg: false
(correct since this removes from a team, not from the organization itself)- Ensures comprehensive cleanup of related data
The approach is consistent with the other membership services and addresses the cleanup requirements outlined in the PR objectives.
apps/api/v2/src/modules/organizations/memberships/services/organizations-membership.service.ts (2)
5-5
: LGTM! Consistent import for centralized member removal.The import correctly adds
TeamService
from@calcom/platform-libraries
to support the refactored deletion logic.
66-86
: Excellent refactor with correct organization-level removal.The updated
deleteOrgMembership
method:
- Fetches membership first to validate existence and get
userId
- Provides clear error messaging with relevant IDs
- Correctly uses
isOrg: true
since this removes a user from the entire organization- Delegates to
TeamService.removeMembers
for comprehensive cleanup- Maintains the existing API contract by returning the formatted membership output
This ensures proper cleanup of all organization-related data while maintaining backward compatibility.
packages/features/ee/dsync/lib/removeUserFromOrg.ts (2)
1-1
: LGTM! Direct import for TeamService.The import uses the direct path to
TeamService
which is appropriate for this package context.
4-4
: Excellent simplification with proper organization removal.The refactored function correctly:
- Uses
TeamService.removeMembers
for centralized cleanup logic- Sets
isOrg: true
appropriately for organization-level removal- Maintains the same function signature for backward compatibility
- Simplifies the implementation while ensuring comprehensive data cleanup
This aligns with the broader refactor to centralize member removal logic in
TeamService
.packages/lib/server/service/__tests__/teamService.integration-test.ts (4)
22-116
: Well-structured test helper functions!The helper functions are properly designed with unique identifiers using timestamps and random suffixes to prevent conflicts during parallel test execution.
144-147
: Good adherence to Prisma query guidelines!Excellent use of
select
to fetch only the requiredorganizationId
field, following the coding guideline to avoid usinginclude
.
636-707
: Excellent transaction rollback test!This test comprehensively verifies transactional integrity by intentionally causing a username conflict and ensuring all operations are rolled back. Great coverage of all affected entities.
818-884
: Comprehensive test for the PR's main fix!This test perfectly captures the API v2 deletion scenario that the PR aims to fix, including:
- Username conflict resolution by appending userId
- Profile deletion
- Host removal from sub-teams
- Complete membership cleanup
Great job ensuring the fix works as intended.
packages/lib/server/service/__tests__/teamService.integration-test.ts
Outdated
Show resolved
Hide resolved
c30f71e
to
d705d2a
Compare
The E2E API v2 tests were failing with TypeScript compilation errors because TeamService imports from @calcom/platform-libraries require the package to be built first to generate the dist/ folder with compiled exports. This adds the same build step that was added to unit-tests.yml to resolve the module resolution errors. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>
The unit and E2e Tests are failing because platform-libraries aren't published. Ran a workflow where platform-loibraries were built and then tests were run and it passed. |
…embership-deletion
…hub.com/calcom/cal.com into fix-api-v2-org-user-membership-deletion
f04aa13
to
879194f
Compare
879194f
to
7b50b3e
Compare
…embership-deletion
@hariombalhara do you think it's good to also rename services using camel case pattern as per contributions guide? |
What does this PR do?
This PR fixes the organization membership deletion functionality in API V2 by properly calling the TeamService.removeMembers method instead of directly deleting the membership record. This ensures that all necessary cleanup operations are performed when removing a user from an organization.
Fixes PRI-297
Bug Fixes
Technical Details:
TeamService.removeMembers({ teamIds: [organizationId], userIds: [membership.userId], isOrg: true })
Visual Demo (For contributors especially)
N/A - This is a backend API change with no visual components.
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Prerequisites:
Testing Steps:
Checklist