Skip to content

Conversation

hariombalhara
Copy link
Member

@hariombalhara hariombalhara commented Jul 30, 2025

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

  • When setting organizationId=null, we explicitly make username unique so that slug constraint DB error is avoided
  • ChildrenManaged Events weren't deleted in case of team membership deletion, that is now fixed
  • Even though there are multiple tables need to be cleaned up when deleting a membership from org, that is being done in an atomic(mostly) manner now.

Technical Details:

  • The previous implementation was directly deleting memberships which could leave orphaned data
  • Now uses TeamService.removeMembers({ teamIds: [organizationId], userIds: [membership.userId], isOrg: true })
  • Maintains backward compatibility while ensuring proper cleanup

Visual Demo (For contributors especially)

N/A - This is a backend API change with no visual components.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - This is an internal API fix that doesn't change the public API interface.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Prerequisites:

  • Set up a local Cal.com development environment
  • Ensure you have API V2 enabled
  • Create a test organization with multiple members

Testing Steps:

  • Membership deletion should return 200 with the deleted membership data
  • All related data (hosts, permissions, etc.) should be properly cleaned up
  • Attempting to delete a non-existent membership returns 404
  • Non-admin users should receive 403 when attempting to delete memberships

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings

Copy link
Contributor

coderabbitai bot commented Jul 30, 2025

Walkthrough

The PR centralizes member-removal into TeamService.removeMembers, refactors deletion flows to fetch memberships and throw NotFound when absent, and replaces direct repository deletes with calls to TeamService (isOrg flag supported). It removes the legacy removeMember utility and a TeamsEventTypesService method, updates API v2 services/controllers to call TeamService, adds integration tests for TeamService.removeMembers, adds small test fixtures, and exports TeamService from platform libraries. Non-functional tweaks (imports/formatting) and a deleted skipped TRPC test were also included.

Assessment against linked issues

Objective Addressed Explanation
Remove sub-team memberships on org member removal (PRI-297)
Remove sub-team event hosts on org member removal (PRI-297)
Remove sub-team managed events for the user (PRI-297)
Delete user’s organization profile on org member removal (PRI-297)

Out-of-scope changes

Code Change Explanation
Removed method deleteUserTeamEventTypesAndHosts (apps/api/v2/src/modules/teams/event-types/services/teams-event-types.service.ts) This deletes a controller/service method unrelated to the specific PRI-297 objectives; it's a refactor choice rather than a direct requirement of the linked issue.
Deleted TRPC removeMember test file (packages/trpc/server/routers/viewer/teams/removeMember.test.ts) Test removal is not part of the functional objectives in PRI-297 and may reduce test coverage for TRPC handler behavior.

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 729861b and 07bd81c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • apps/api/v2/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Atoms E2E Tests
🔇 Additional comments (1)
apps/api/v2/package.json (1)

41-41: Validated TeamService integration with pinned @calcom/platform-libraries

All imports of TeamService and calls to TeamService.removeMembers({ … }) across the codebase align with the object signature introduced in version 0.0.314. No mismatches or API-breaks were found.

• Verified imports in apps/api/v2/src/modules/**/services/*service.ts
• Confirmed removeMembers calls in both API and server packages
• Integration tests in packages/lib/server/service/__tests__/teamService.integration-test.ts pass against the new signature

If you intentionally want to bypass local workspace sources and always consume the published package, you can leave the alias as-is. Otherwise, switch back to workspace resolution to reflect local changes during development:

-    "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.314",
+    "@calcom/platform-libraries": "workspace:*",
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-api-v2-org-user-membership-deletion

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Jul 30, 2025

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:

No release type found in pull request title "Fix membership deletion handling and add tests". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit

@hariombalhara hariombalhara self-assigned this Jul 30, 2025
@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Jul 30, 2025
Copy link

linear bot commented Jul 30, 2025

PRI-297

Copy link

vercel bot commented Jul 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 18, 2025 10:16am
cal-eu Ignored Ignored Aug 18, 2025 10:16am

Copy link
Contributor

github-actions bot commented Jul 30, 2025

E2E results are ready!

@hariombalhara hariombalhara force-pushed the fix-api-v2-org-user-membership-deletion branch from 8069a94 to 262ffe4 Compare July 30, 2025 09:19
Copy link

socket-security bot commented Jul 30, 2025

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

…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
@hariombalhara hariombalhara force-pushed the fix-api-v2-org-user-membership-deletion branch from baf51ea to 3fef285 Compare July 31, 2025 07:52
@alishaz-polymath
Copy link
Member

Love this overall 💪, only left a question/comment around where I am a bit curious before I give the green light 🙏

Copy link
Contributor

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

🔭 Outside diff range comments (8)
packages/features/calAIPhone/AIPhoneServiceRegistry.ts (1)

78-85: Fix loss of type narrowing causing a potential TS error

Switching from this.defaultProvider to AIPhoneServiceRegistry.defaultProvider after the null-check breaks TypeScript’s control-flow narrowing, making the argument type string | null and likely causing a compile-time error.

Use this.defaultProvider here to retain the narrowing from the guard above.

Apply this diff:

-    return this.createProvider(AIPhoneServiceRegistry.defaultProvider, config);
+    return this.createProvider(this.defaultProvider, config);

If you want to keep class-qualified access, mirror the guard and use a non-null assertion:

if (!AIPhoneServiceRegistry.defaultProvider) {
  throw new Error("No default provider set. Please initialize the registry with a default provider or register at least one provider.");
}
return AIPhoneServiceRegistry.createProvider(AIPhoneServiceRegistry.defaultProvider!, config);
apps/web/app/api/auth/saml/userinfo/route.ts (3)

14-17: Harden Authorization header parsing: enforce Bearer scheme and handle malformed headers

Current logic accepts any scheme and may mis-parse tokens when extra spaces are present. It can also return an empty token. Enforce the Bearer scheme and extract robustly.

Apply this diff:

-  const parts = (authHeader || "").split(" ");
-  if (parts.length > 1) return parts[1];
+  if (authHeader) {
+    const match = authHeader.match(/^Bearer\s+(.+)$/i);
+    if (match && match[1].trim().length > 0) return match[1].trim();
+  }

32-34: Validate non-empty access_token at the schema level

Shift the empty-string check into zod. This removes the need for runtime length checks and yields clearer validation errors.

Apply this diff:

-const requestQuery = z.object({
-  access_token: z.string(),
-});
+const requestQuery = z.object({
+  access_token: z.string().min(1, "access_token must be a non-empty string"),
+});

41-48: Don’t downgrade authorization failures to 500; preserve/propagate HttpError status

Catching and throwing a generic Error converts all failures (including invalid/expired tokens) into 500s. Preserve HttpError when thrown upstream, and map unknown errors to an appropriate status.

Apply this diff:

-  } catch (error) {
-    const uid = uuid();
-    log.error(`trace: ${uid} Error getting user info from token: ${error}`);
-    throw new Error(`Error getting user info from token. trace: ${uid}`);
-  }
+  } catch (error: unknown) {
+    const uid = uuid();
+    log.error(`trace: ${uid} Error getting user info from token: ${String(error)}`);
+    if (error instanceof HttpError) {
+      throw error;
+    }
+    const statusCode =
+      typeof (error as any)?.statusCode === "number"
+        ? (error as any).statusCode
+        : typeof (error as any)?.status === "number"
+        ? (error as any).status
+        : 500;
+    throw new HttpError({
+      statusCode,
+      message:
+        statusCode === 401 || statusCode === 403
+          ? `Unauthorized. trace: ${uid}`
+          : `Error getting user info from token. trace: ${uid}`,
+    });
+  }
apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts (1)

114-123: Make the "list private links" test self-contained to avoid inter-test coupling

This test assumes at least one link exists (length >= 1) but doesn’t create one within the test, relying on earlier tests. That can lead to flaky tests if order changes or failures happen earlier.

Pre-create a link inside this test before listing:

 it("GET /v2/event-types/:eventTypeId/private-links - list private links", async () => {
+  // Ensure at least one link exists for this test in isolation
+  await request(app.getHttpServer())
+    .post(`/api/v2/event-types/${eventType.id}/private-links`)
+    .set("Authorization", `Bearer whatever`)
+    .send({ maxUsageCount: 1 })
+    .expect(201);
+
   const response = await request(app.getHttpServer())
     .get(`/api/v2/event-types/${eventType.id}/private-links`)
     .set("Authorization", `Bearer whatever`)
     .expect(200);

   expect(response.body.status).toBe(SUCCESS_STATUS);
   expect(Array.isArray(response.body.data)).toBe(true);
   expect(response.body.data.length).toBeGreaterThanOrEqual(1);
 });
apps/web/components/getting-started/steps-views/UserProfile.tsx (1)

60-66: Event type creation isn’t awaited; use mutateAsync so setup completes before redirect

createEventType.mutate is not Promise-based; wrapping it in Promise.all doesn’t await completion. This can cause racing with the redirect and incomplete default event creation. Switch to mutateAsync.

-          await Promise.all(
-            DEFAULT_EVENT_TYPES.map(async (event) => {
-              return createEventType.mutate(event);
-            })
-          );
+          await Promise.all(
+            DEFAULT_EVENT_TYPES.map((event) => createEventType.mutateAsync(event))
+          );

Optional: if partial failures are acceptable without blocking the flow, use Promise.allSettled and log/report the failures.

packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (1)

58-65: Use HttpError consistently instead of generic Error for API-surface errors

updateAgent currently throws plain Error for validation and failure paths, while the rest of this service uses HttpError with status codes. Standardize to HttpError to propagate correct status codes to callers.

Apply:

-    if (!agentId?.trim()) {
-      throw new Error("Agent ID is required and cannot be empty");
-    }
+    if (!agentId?.trim()) {
+      throw new HttpError({ statusCode: 400, message: "Agent ID is required and cannot be empty" });
+    }
@@
-    if (!data || Object.keys(data).length === 0) {
-      throw new Error("Update data is required");
-    }
+    if (!data || Object.keys(data).length === 0) {
+      throw new HttpError({ statusCode: 400, message: "Update data is required" });
+    }
@@
-      throw new Error(`Failed to update agent ${agentId}`);
+      throw new HttpError({ statusCode: 500, message: `Failed to update agent ${agentId}` });

Also applies to: 75-76

packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts (1)

41-56: Adapter drops outboundAgentId on create — repository doesn't accept it; fix required

Short: PrismaPhoneNumberRepository.createPhoneNumber does not accept or persist outboundAgentId, while the adapter/interface expect it and PrismaTransactionAdapter already supports it. This will silently drop outboundAgentId for non-transactional creates.

Files to change:

  • packages/lib/server/repository/PrismaPhoneNumberRepository.ts — add outboundAgentId to createPhoneNumber signature, include in data and select.
  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts — pass outboundAgentId through to the repository.
  • (Verify callers that call the repository directly and update if needed.)

Proposed diffs:

packages/lib/server/repository/PrismaPhoneNumberRepository.ts

   static async createPhoneNumber({
     phoneNumber,
     provider,
     userId,
-    teamId,
+    teamId,
+    outboundAgentId,
   }: {
     phoneNumber: string;
     provider?: string;
     userId: number;
-    teamId?: number;
+    teamId?: number;
+    outboundAgentId?: string | null;
   }) {
     return await prisma.calAiPhoneNumber.create({
       select: {
         id: true,
         phoneNumber: true,
         provider: true,
         userId: true,
         teamId: true,
+        inboundAgentId: true,
+        outboundAgentId: true,
         subscriptionStatus: true,
         createdAt: true,
         updatedAt: true,
       },
       data: {
         provider,
         userId,
         teamId,
+        outboundAgentId,
         phoneNumber,
       },
     });
   }

packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts

   async createPhoneNumber(params: {
     phoneNumber: string;
     provider?: string;
     userId: number;
     teamId?: number;
     outboundAgentId?: string | null;
   }): Promise<PhoneNumberData> {
     const createParams = {
       phoneNumber: params.phoneNumber,
       provider: params.provider,
       userId: params.userId,
       teamId: params.teamId,
+      outboundAgentId: params.outboundAgentId ?? undefined,
     };

     return await PrismaPhoneNumberRepository.createPhoneNumber(createParams);
   }

Reason: keep interface and transaction behavior consistent; prefer adding the field to the repository so callers that aren't using transactions still persist outboundAgentId. If this is intentional (field must not be set outside transactions), remove the parameter from the interface and adapter instead.

🧹 Nitpick comments (15)
apps/api/v2/src/ee/bookings/2024-08-13/services/output.service.ts (1)

170-173: Avoid disabling ESLint; omit property without introducing an unused binding

You can drop the rule suppression and keep the same runtime behavior by copying and deleting the property.

-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    const { videoCallUrl, ...userDefinedMetadata } = databaseMetadata;
-
-    return userDefinedMetadata;
+    const userDefinedMetadata = { ...databaseMetadata };
+    delete userDefinedMetadata.videoCallUrl;
+    return userDefinedMetadata;
packages/features/calAIPhone/AIPhoneServiceRegistry.ts (2)

94-96: Prefer consistent static access style within the class

Elsewhere in this class, static fields are accessed via this. Stick to one convention for readability; using this also plays better with control-flow narrowing.

Apply this diff:

-    return AIPhoneServiceRegistry.defaultProvider;
+    return this.defaultProvider;

If you intend to standardize on class-qualified access, consider updating all other static references in the class for consistency.


115-117: Minor consistency nit: align static access style

Same note as above—prefer one style of static access within the class.

Apply this diff:

-    return AIPhoneServiceRegistry.initialized;
+    return this.initialized;
apps/web/app/api/auth/saml/userinfo/route.ts (2)

25-29: Simplify query param handling; avoid unnecessary temporary array

The current array dance adds complexity without benefit. Just return the token if non-empty; otherwise fall through to the 401.

Apply this diff:

-  const access_token = tokenParse.data.access_token;
-  arr = arr.concat(access_token);
-  if (arr[0].length > 0) return arr[0];
+  const tokenFromQuery = tokenParse.data.access_token;
+  if (tokenFromQuery.length > 0) return tokenFromQuery;

21-23: Use warn-level for expected validation failures

Missing/invalid tokens are common and expected; logging them as errors can create noise. Suggest downgrading to warn.

Option:

-    log.error(`Error parsing request query: ${tokenParse.error} trace ${uid}`);
+    log.warn(`Invalid request query for token extraction: ${tokenParse.error} trace ${uid}`);
apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts (3)

14-16: Redundant beforeAll — re-instantiation already happens in beforeEach.

beforeAll is unnecessary since pipe is recreated before each test. Removing it reduces noise.

-  beforeAll(() => {
-    pipe = new GoogleCalendarEventInputPipe();
-  });

112-174: Accessing private helpers directly makes tests brittle. Consider testing via public API.

Using pipe["transformResponseStatus"] and pipe["transformEventStatus"] ties tests to internal details. Prefer asserting these mappings through the public transform method, or extract the mapping functions and export them for direct testing.


22-80: Missing coverage for attendees transformation.

Given recent touch-ups in attendees mapping in the pipe, add at least one test that exercises transforming attendees from UpdateUnifiedCalendarEventInput to the expected Google payload.

Example to add near existing transform tests:

     it("should handle null description", () => {
       const input: UpdateUnifiedCalendarEventInput = {
         description: null,
       };

       const expectedOutput = {
         description: null,
       };

       const result = pipe.transform(input);
       expect(result).toEqual(expectedOutput);
     });
+
+    it("should transform attendees", () => {
+      const input: UpdateUnifiedCalendarEventInput = {
+        attendees: [
+          { email: "a@example.com", name: "Alice", responseStatus: CalendarEventResponseStatus.ACCEPTED },
+          { email: "b@example.com", name: "Bob", responseStatus: CalendarEventResponseStatus.PENDING },
+        ],
+      };
+      const result = pipe.transform(input);
+      // Be flexible on exact shape if needed; minimally assert mapped emails and response statuses.
+      expect(result.attendees).toEqual(
+        expect.arrayContaining([
+          expect.objectContaining({ email: "a@example.com" }),
+          expect.objectContaining({ email: "b@example.com" }),
+        ])
+      );
+    });
apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts (2)

15-16: Organization/OAuth scaffolding seems unused; streamline test or make intent explicit

The created organization and OAuth client aren’t used by the test cases. This adds setup time and potential cleanup burden.

  • Option A (preferred): Remove org/OAuth creation entirely (and drop TeamRepositoryFixture/OAuthClientRepositoryFixture usage and related helper). This will speed up the test and reduce side effects.
  • Option B: If you intentionally keep it for side-effects, explicitly acknowledge the ignored result to satisfy linters/readers.

Minimal change for Option B:

-    await createOAuthClient(organization.id);
+    void (await createOAuthClient(organization.id));

If you choose Option A (bigger edit), I can help produce a patch that:

  • Removes the organization and OAuth client creation and their imports/fixtures
  • Simplifies beforeAll to only create the user and event type
  • Removes the createOAuthClient helper

Also applies to: 62-62


164-173: Clean up all created entities to prevent test DB pollution

Only the event type is deleted. Consider also deleting:

  • OAuth client created for the organization
  • The organization itself
  • The user

If repository fixtures don’t expose delete helpers, I can help add them to keep tests hermetic and faster to iterate.

apps/web/components/getting-started/steps-views/UserProfile.tsx (2)

121-129: Remove unused placeholder on hidden input and fix likely typo in Tailwind class

  • The placeholder on a hidden input has no effect and adds an unlocalized string in TSX.
  • Likely typo: focus:ring-empthasis should be focus:ring-emphasis.
         <input
           ref={avatarRef}
           type="hidden"
           name="avatar"
           id="avatar"
-          placeholder="URL"
-          className="border-default focus:ring-empthasis mt-1 block w-full rounded-sm border px-3 py-2 text-sm focus:border-gray-800 focus:outline-none"
+          className="border-default focus:ring-emphasis mt-1 block w-full rounded-sm border px-3 py-2 text-sm focus:border-gray-800 focus:outline-none"
           defaultValue={imageSrc}
         />

92-96: Drop unnecessary async from updateProfileHandler

No await is used; returning a Promise here is unnecessary.

-  async function updateProfileHandler(newAvatar: string) {
-    avatarMutation.mutate({
-      avatarUrl: newAvatar,
-    });
-  }
+  function updateProfileHandler(newAvatar: string) {
+    avatarMutation.mutate({
+      avatarUrl: newAvatar,
+    });
+  }
packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (1)

200-221: Nit: Add a short comment clarifying that name is intentionally not persisted here

Per prior maintainers’ decision (captured in team learnings), name is not persisted by updateAgentConfiguration. To avoid future churn, add a brief inline comment.

Apply:

   async updateAgentConfiguration({
     id,
     userId,
     name,
     generalPrompt,
     beginMessage,
     generalTools,
     voiceId,
     updateLLMConfiguration,
   }: {
@@
-  }) {
+  }) {
+    // Intentionally not persisting `name` here as per current product decision.
+    // If requirements change, persist via repository in this method.
     const agent = await this.agentRepository.findByIdWithAdminAccess({
       id,
       userId,
     });

Note: This acknowledges existing behavior based on prior decisions.

packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts (1)

173-174: Remove redundant await in simple return statements

These methods simply proxy to the service and don’t need await. Dropping it avoids wrapping/rethrowing and a tiny bit of overhead.

Apply:

-    return await this.service.generatePhoneNumberCheckoutSession(params);
+    return this.service.generatePhoneNumberCheckoutSession(params);
@@
-    return await this.service.cancelPhoneNumberSubscription(params);
+    return this.service.cancelPhoneNumberSubscription(params);
@@
-    return await this.service.updatePhoneNumberWithAgents(params);
+    return this.service.updatePhoneNumberWithAgents(params);
@@
-    return await this.service.listAgents(params);
+    return this.service.listAgents(params);
@@
-    return await this.service.getAgentWithDetails(params);
+    return this.service.getAgentWithDetails(params);
@@
-    return await this.service.updateAgentConfiguration(params);
+    return this.service.updateAgentConfiguration(params);
@@
-    return await this.service.deleteAgent(params);
+    return this.service.deleteAgent(params);
@@
-    return await this.service.createTestCall(params);
+    return this.service.createTestCall(params);

Also applies to: 181-182, 191-192, 202-203, 210-211, 246-247, 249-251, 263-264

packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts (1)

68-73: Strengthen typing and harmonize naming for clarity

  • subscriptionStatus is typed as string | null in PhoneNumberData while the interface already imports PhoneNumberSubscriptionStatus. Prefer the enum type for stronger guarantees.
  • Naming is inconsistent: updateAgents uses inboundProviderAgentId/outboundProviderAgentId while PhoneNumberData uses inboundAgentId/outboundAgentId. Consider aligning naming across interface/data to reduce confusion.

Apply for subscriptionStatus:

 export interface PhoneNumberData {
   id: number;
   phoneNumber: string;
   userId: number | null;
   teamId: number | null;
-  subscriptionStatus: string | null;
+  subscriptionStatus: PhoneNumberSubscriptionStatus | null;
   stripeSubscriptionId: string | null;
   stripeCustomerId: string | null;
   provider: string | null;
   inboundAgentId: string | null;
   outboundAgentId: string | null;
   createdAt: Date;
   updatedAt: Date;
 }

For naming consistency, consider:

  • Either rename PhoneNumberData fields to inboundProviderAgentId/outboundProviderAgentId
  • Or rename updateAgents params to inboundAgentId/outboundAgentId

If you want, I can generate a follow-up PR to unify these names across provider, adapter, and repository.

Also applies to: 79-91

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54a3f0 and e63a377.

📒 Files selected for processing (39)
  • apps/api/v2/src/ee/bookings/2024-08-13/services/output.service.ts (1 hunks)
  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts (3 hunks)
  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.ts (1 hunks)
  • apps/api/v2/src/ee/event-types/event-types_2024_04_15/controllers/event-types.controller.e2e-spec.ts (1 hunks)
  • apps/api/v2/src/modules/billing/services/billing.service.ts (0 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/inputs/update-unified-calendar-event.input.ts (1 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/outputs/get-unified-calendar-event.output.ts (1 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/get-calendar-event-details-output-pipe.ts (1 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input-pipe.ts (4 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts (1 hunks)
  • apps/api/v2/src/modules/cal-unified-calendars/services/google-calendar.service.ts (0 hunks)
  • apps/api/v2/test/setEnvVars.ts (1 hunks)
  • apps/web/app/api/auth/forgot-password/route.ts (1 hunks)
  • apps/web/app/api/auth/saml/userinfo/route.ts (1 hunks)
  • apps/web/components/getting-started/steps-views/UserProfile.tsx (1 hunks)
  • apps/web/pages/api/book/event.ts (1 hunks)
  • apps/web/pages/api/book/instant-event.ts (1 hunks)
  • apps/web/pages/api/book/recurring-event.ts (1 hunks)
  • packages/app-store/btcpayserver/components/EventTypeAppSettingsInterface.tsx (1 hunks)
  • packages/app-store/btcpayserver/lib/currencyOptions.ts (3 hunks)
  • packages/app-store/routing-forms/trpc/formMutation.handler.ts (0 hunks)
  • packages/app-store/routing-forms/trpc/formQuery.handler.ts (0 hunks)
  • packages/app-store/routing-forms/trpc/permissions.ts (1 hunks)
  • packages/app-store/stripepayment/lib/utils.ts (2 hunks)
  • packages/features/auth/lib/verifyEmail.ts (1 hunks)
  • packages/features/calAIPhone/AIPhoneServiceRegistry.ts (3 hunks)
  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts (1 hunks)
  • packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/RetellSDKClient.test.ts (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/services/PhoneNumberService.ts (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/services/__tests__/BillingService.test.ts (0 hunks)
  • packages/features/calAIPhone/providers/retellAI/services/__tests__/CallService.test.ts (0 hunks)
  • packages/features/ee/workflows/components/WorkflowDetailsPage.tsx (1 hunks)
  • packages/features/embed/RoutingFormEmbed.tsx (2 hunks)
  • packages/features/eventtypes/components/DuplicateDialog.tsx (1 hunks)
  • packages/features/eventtypes/components/tabs/ai/AIEventController.tsx (1 hunks)
  • packages/lib/bookingSuccessRedirect.test.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • apps/api/v2/src/modules/cal-unified-calendars/services/google-calendar.service.ts
  • packages/features/calAIPhone/providers/retellAI/services/tests/BillingService.test.ts
  • packages/app-store/routing-forms/trpc/formMutation.handler.ts
  • apps/api/v2/src/modules/billing/services/billing.service.ts
  • packages/app-store/routing-forms/trpc/formQuery.handler.ts
  • packages/features/calAIPhone/providers/retellAI/services/tests/CallService.test.ts
✅ Files skipped from review due to trivial changes (23)
  • apps/web/pages/api/book/event.ts
  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.ts
  • apps/web/pages/api/book/instant-event.ts
  • apps/api/v2/test/setEnvVars.ts
  • packages/features/ee/workflows/components/WorkflowDetailsPage.tsx
  • packages/features/calAIPhone/providers/retellAI/RetellSDKClient.test.ts
  • packages/features/calAIPhone/providers/retellAI/services/PhoneNumberService.ts
  • apps/api/v2/src/ee/event-types/event-types_2024_04_15/controllers/event-types.controller.e2e-spec.ts
  • packages/features/eventtypes/components/DuplicateDialog.tsx
  • packages/app-store/btcpayserver/components/EventTypeAppSettingsInterface.tsx
  • packages/lib/bookingSuccessRedirect.test.ts
  • packages/app-store/stripepayment/lib/utils.ts
  • packages/features/embed/RoutingFormEmbed.tsx
  • apps/web/pages/api/book/recurring-event.ts
  • packages/app-store/routing-forms/trpc/permissions.ts
  • apps/api/v2/src/modules/cal-unified-calendars/inputs/update-unified-calendar-event.input.ts
  • apps/api/v2/src/modules/cal-unified-calendars/outputs/get-unified-calendar-event.output.ts
  • packages/features/auth/lib/verifyEmail.ts
  • packages/features/eventtypes/components/tabs/ai/AIEventController.tsx
  • apps/web/app/api/auth/forgot-password/route.ts
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/get-calendar-event-details-output-pipe.ts
  • packages/app-store/btcpayserver/lib/currencyOptions.ts
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input-pipe.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
  • apps/api/v2/src/ee/bookings/2024-08-13/services/output.service.ts
  • packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts
  • apps/web/app/api/auth/saml/userinfo/route.ts
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts
  • packages/features/calAIPhone/AIPhoneServiceRegistry.ts
  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.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:

  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
  • apps/api/v2/src/ee/bookings/2024-08-13/services/output.service.ts
  • packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts
  • apps/web/app/api/auth/saml/userinfo/route.ts
  • apps/web/components/getting-started/steps-views/UserProfile.tsx
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts
  • packages/features/calAIPhone/AIPhoneServiceRegistry.ts
  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.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/features/calAIPhone/providers/retellAI/services/AgentService.ts
**/*.{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/ee/bookings/2024-08-13/services/output.service.ts
**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/components/getting-started/steps-views/UserProfile.tsx
🧠 Learnings (3)
📚 Learning: 2025-08-08T10:26:13.362Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/providers/retellAI/services/PhoneNumberService.ts:212-220
Timestamp: 2025-08-08T10:26:13.362Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/providers/retellAI/services/PhoneNumberService.ts should include the phone number in client-facing HttpError messages (e.g., in updatePhoneNumber/getPhoneNumber catch blocks). Do not suggest redacting the phone number from these errors unless requirements change (per maintainer: Udit-takkar).

Applied to files:

  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts
  • packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts
📚 Learning: 2025-08-08T09:29:11.681Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.681Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.

Applied to files:

  • packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
  • packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts
  • packages/features/calAIPhone/AIPhoneServiceRegistry.ts
📚 Learning: 2025-08-08T09:27:23.896Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/providers/retellAI/services/AgentService.ts:195-216
Timestamp: 2025-08-08T09:27:23.896Z
Learning: In PR calcom/cal.com#22919, file packages/features/calAIPhone/providers/retellAI/services/AgentService.ts, the updateAgentConfiguration method intentionally does not persist the optional `name` parameter to the repository for now, per maintainer (Udit-takkar). Future reviews should not flag this unless requirements change.

Applied to files:

  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
  • packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Atoms E2E Tests
🔇 Additional comments (12)
apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts (2)

5-8: LGTM on import cleanup.

Scoped imports for CalendarEventResponseStatus and CalendarEventStatus look correct and improve readability.


9-9: Incorrect — import path is correct; no change required

The implementation file is apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input-pipe.ts (dash) and it exports GoogleCalendarEventInputPipe. The spec already imports from "./google-calendar-event-input-pipe" (apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts:9). The suggested change to "./google-calendar-event-input.pipe" would be incorrect.

Files to note:

  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input-pipe.ts — exports class GoogleCalendarEventInputPipe
  • apps/api/v2/src/modules/cal-unified-calendars/pipes/google-calendar-event-input.pipe.spec.ts:9 — imports from "./google-calendar-event-input-pipe"

Likely an incorrect or invalid review comment.

apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts (3)

18-18: LGTM: withApiAuth correctly seeds authenticated context for the test module

Good call bringing withApiAuth in; combined with the PermissionsGuard override, it ensures the request user context is present for these endpoints.


20-21: LGTM: Strong typing and constants usage

Using CreatePrivateLinkInput for the request body and SUCCESS_STATUS for assertions improves test clarity and type-safety.


3-3: Keep EventTypesModule_2024_06_14 import — AppModule does not import it

Checked apps/api/v2/src/app.module.ts — it does not reference EventTypesModule_2024_06_14 (script output: "Not found"), so the import in apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts is not redundant and should remain.

  • apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.e2e-spec.ts — keep the import
  • apps/api/v2/src/app.module.ts — confirmed no import of EventTypesModule_2024_06_14

Likely an incorrect or invalid review comment.

apps/web/components/getting-started/steps-views/UserProfile.tsx (2)

71-75: onBoardingRedirect key is consistent — no action required

Search found only "onBoardingRedirect" (5 matches) and zero "onboardingRedirect" matches. Locations found:

  • apps/web/modules/signup-view.tsx — localStorage.setItem("onBoardingRedirect", ...)
  • apps/web/modules/event-types/views/event-types-listing-view.tsx — localStorage.getItem/removeItem("onBoardingRedirect", ...)
  • apps/web/components/getting-started/steps-views/UserProfile.tsx — localStorage.getItem/removeItem("onBoardingRedirect", ...)

12-12: Confirmed: localStorage is a named export from @calcom/lib/webstorage — no change needed

Checked packages/lib/webstorage.ts — it contains export const localStorage = { ... } (line 6), and the import in the reviewed file is correct.

  • Definition: packages/lib/webstorage.ts — export const localStorage = { ... } (line 6)
  • Reviewed usage: apps/web/components/getting-started/steps-views/UserProfile.tsx — import { localStorage } from "@calcom/lib/webstorage"; (no changes required)

import { localStorage } from "@calcom/lib/webstorage";

packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (2)

15-16: Good switch to type-only import and separate value import for getLlmId

This reduces runtime bundle size and avoids circular/side-effect pitfalls. Keeping getLlmId as a value import is correct.


322-331: Confirm intended behavior: swallow external delete failures but still return success

deleteAgent logs provider deletion failures but doesn’t surface them to callers and still returns a success message. If that’s intentional (to avoid blocking local deletion), consider:

  • Returning a warning in the message or a “partialSuccess” indicator.
  • Emitting a structured event/metric for operational follow-up.

Do you want the API response to reflect partial deletion when external clean-up fails?

packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts (1)

25-25: Type-only import is correct here

RetellAIRepository is used purely as a type for DI; importing it as type avoids unnecessary runtime dependency.

packages/features/calAIPhone/providers/adapters/PrismaPhoneNumberRepositoryAdapter.ts (1)

2-2: Good: switch to type-only import for Prisma enum

This keeps runtime clean and aligns with the broader refactor.

packages/features/calAIPhone/providers/interfaces/PhoneNumberRepositoryInterface.ts (1)

1-1: Type-only import for enum is correct

No runtime dependency on enums is needed here.

@hariombalhara
Copy link
Member Author

hariombalhara commented Aug 14, 2025

@ThyMinimalDev could you publish a new version with the changes, I took the latest merge of main which had bumped platform libraries and the API V2 build started failing again

@alishaz-polymath replied to your feedback.

ThyMinimalDev
ThyMinimalDev previously approved these changes Aug 14, 2025
ThyMinimalDev
ThyMinimalDev previously approved these changes Aug 14, 2025
Copy link
Contributor

@keithwillcode keithwillcode left a comment

Choose a reason for hiding this comment

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

Approving since previously approved and libraries were bumped

@hariombalhara hariombalhara merged commit 88bbab2 into main Aug 18, 2025
39 checks passed
@hariombalhara hariombalhara deleted the fix-api-v2-org-user-membership-deletion branch August 18, 2025 11:38
@coderabbitai coderabbitai bot mentioned this pull request Aug 19, 2025
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api area: API, enterprise API, access token, OAuth core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO organizations area: organizations, orgs platform Anything related to our platform plan ready-for-e2e teams area: teams, round robin, collective, managed event-types
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants