Skip to content

fix: handle collective multiple host on destinationCalendar#4

Open
everettbu wants to merge 1 commit into
enhance-collective-scheduling-foundationfrom
fix/handle-collective-multiple-host-destinations
Open

fix: handle collective multiple host on destinationCalendar#4
everettbu wants to merge 1 commit into
enhance-collective-scheduling-foundationfrom
fix/handle-collective-multiple-host-destinations

Conversation

@everettbu

@everettbu everettbu commented Jul 28, 2025

Copy link
Copy Markdown

Test 4

Summary by CodeRabbit

  • New Features

    • Added support for associating multiple destination calendars with a single event, allowing events to be managed across several calendars simultaneously.
    • Calendar events now include scheduling type information for enhanced flexibility.
  • Bug Fixes

    • Improved consistency by ensuring the destination calendar property is always handled as an array, preventing potential errors from single or undefined values.
  • Refactor

    • Updated event creation, updating, and cancellation logic to process multiple calendar references, supporting more robust multi-calendar workflows.
    • Enhanced error handling and credential management for calendar operations.
  • Documentation

    • Updated type definitions to reflect support for multiple destination calendars and new scheduling type property.
  • Style

    • Standardized terminology (e.g., "organizer" spelling) and improved code readability without affecting functionality.

@coderabbitai

coderabbitai Bot commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change set refactors the handling of the destinationCalendar property across the codebase, standardizing it from a single object or null to always being an array (possibly empty) of DestinationCalendar objects. It also updates related type definitions, method signatures, and logic in event and booking management to support multiple destination calendars, with adjustments in error handling, credential selection, and email templates.

Changes

Cohort / File(s) Change Summary
Core Event Handling and Management
packages/core/EventManager.ts, packages/core/CalendarManager.ts, packages/core/builders/CalendarEvent/class.ts, packages/core/builders/CalendarEvent/builder.ts, packages/types/Calendar.d.ts, packages/types/EventManager.d.ts
Refactored all logic to treat destinationCalendar as an array, updated types/interfaces, method signatures, and internal event/credential handling to support multiple calendars. Added optional externalId to EventResult.
Calendar Service Integrations
packages/app-store/googlecalendar/lib/CalendarService.ts, packages/app-store/larkcalendar/lib/CalendarService.ts, packages/app-store/office365calendar/lib/CalendarService.ts, packages/lib/CalendarService.ts
Updated calendar service logic to handle destinationCalendar as an array, refactored organizer and calendar ID selection, and method signatures to support multiple calendars and credential selection.
Booking and Event Feature Logic
packages/features/bookings/lib/handleNewBooking.ts, packages/features/bookings/lib/handleCancelBooking.ts
Modified booking creation and cancellation to aggregate and process multiple destination calendars, improved error handling, and credential fetching.
API and Webhook Endpoints
apps/web/pages/api/cron/bookingReminder.ts, packages/features/ee/payments/api/paypal-webhook.ts, packages/features/ee/payments/api/webhook.ts, apps/web/playwright/webhook.e2e.ts
Refactored endpoints and tests to expect and process destinationCalendar as an array, updated payload shapes and assertions.
TRPC Handlers (Bookings and Credentials)
packages/trpc/server/routers/viewer/bookings/confirm.handler.ts, packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts, packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts, packages/trpc/server/routers/loggedInViewer/deleteCredential.handler.ts
Standardized assignment of destinationCalendar as arrays in booking and credential handlers, ensuring consistent structure.
Email Templates
packages/emails/src/templates/BrokenIntegrationEmail.tsx
Updated email logic to extract integration names from the first element of the destinationCalendar array.
Organization Creation Logic
packages/trpc/server/routers/viewer/organizations/create.handler.ts
Inverted conditional logic for slug and metadata assignment based on the team billing feature flag.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant EventManager
    participant CalendarService
    participant CredentialDB

    User->>API: Create/Update Booking
    API->>EventManager: create(event) / update(event)
    EventManager->>CredentialDB: Fetch credentials for each destinationCalendar
    loop For each destinationCalendar
        EventManager->>CalendarService: createEvent(event, credentialId)
        CalendarService->>CalendarService: Select calendar by credentialId
        CalendarService->>EventManager: Return event result (with externalId)
    end
    EventManager->>API: Return result with all calendar references
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

A bunny hopped through fields of code,
Arrays of calendars now bestowed.
No more lonely single slots,
Each event finds many spots!
Credentials fetched with care anew,
Refactored flows—so much to do!
🐇✨ Hooray for bookings, multiplied and true!

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/handle-collective-multiple-host-destinations

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

@github-actions

github-actions Bot commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🔭 Outside diff range comments (1)
packages/core/EventManager.ts (1)

338-397: Refactor duplicate credential fetching logic

The credential fetching logic is duplicated here and in updateAllCalendarEvents. This increases maintenance burden and potential for bugs.

Consider extracting a helper method:

private async getCalendarCredential(credentialId: number): Promise<CredentialWithAppName | null> {
  let credential = this.calendarCredentials.find((c) => c.id === credentialId);
  if (!credential) {
    const credentialFromDB = await prisma.credential.findUnique({
      include: {
        app: {
          select: {
            slug: true,
          },
        },
      },
      where: {
        id: credentialId,
      },
    });
    if (credentialFromDB && credentialFromDB.app?.slug) {
      credential = {
        appName: credentialFromDB.app.slug,
        id: credentialFromDB.id,
        type: credentialFromDB.type,
        key: credentialFromDB.key,
        userId: credentialFromDB.userId,
        teamId: credentialFromDB.teamId,
        invalid: credentialFromDB.invalid,
        appId: credentialFromDB.appId,
      };
    }
  }
  return credential || null;
}

Also consider batching credential fetches to reduce database queries:

const missingCredentialIds = event.destinationCalendar
  ?.filter(dest => dest.credentialId && !this.calendarCredentials.find(c => c.id === dest.credentialId))
  .map(dest => dest.credentialId) ?? [];

if (missingCredentialIds.length > 0) {
  const credentials = await prisma.credential.findMany({
    where: { id: { in: missingCredentialIds } },
    include: { app: { select: { slug: true } } }
  });
  // Process and add to this.calendarCredentials
}
♻️ Duplicate comments (2)
packages/app-store/googlecalendar/lib/CalendarService.ts (1)

315-317: Same logic error in deleteEvent as in updateEvent.

The calendar selection logic has the same circular search issue. When externalCalendarId is provided, the find operation will never match.

Apply similar fix as suggested for updateEvent:

      const calendarId = externalCalendarId
-       ? externalCalendarId
-       : event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
+       || event.destinationCalendar?.[0]?.externalId
+       || defaultCalendarId;
packages/core/EventManager.ts (1)

503-550: Duplicated credential fetching logic

This is the same credential fetching logic as in createAllCalendarEvents. As mentioned earlier, this should be extracted to a shared method.

🧹 Nitpick comments (1)
packages/app-store/googlecalendar/lib/CalendarService.ts (1)

87-87: Well-implemented support for multiple destination calendars in createEvent.

The code correctly:

  • Extracts the first calendar for backward compatibility
  • Selects the appropriate calendar based on credentialId
  • Falls back to "primary" when no match is found

One minor suggestion: Line 168 still uses selectedCalendar without the fallback to "primary". Consider applying the same fallback pattern:

-              calendarId: selectedCalendar,
+              calendarId: selectedCalendar || "primary",

Also applies to: 100-103, 122-124, 147-154

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 500bfb4 and bd797c1.

📒 Files selected for processing (22)
  • apps/web/pages/api/cron/bookingReminder.ts (2 hunks)
  • apps/web/playwright/webhook.e2e.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (8 hunks)
  • packages/app-store/larkcalendar/lib/CalendarService.ts (4 hunks)
  • packages/app-store/office365calendar/lib/CalendarService.ts (1 hunks)
  • packages/core/CalendarManager.ts (3 hunks)
  • packages/core/EventManager.ts (8 hunks)
  • packages/core/builders/CalendarEvent/builder.ts (1 hunks)
  • packages/core/builders/CalendarEvent/class.ts (1 hunks)
  • packages/emails/src/templates/BrokenIntegrationEmail.tsx (1 hunks)
  • packages/features/bookings/lib/handleCancelBooking.ts (3 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (8 hunks)
  • packages/features/ee/payments/api/paypal-webhook.ts (1 hunks)
  • packages/features/ee/payments/api/webhook.ts (4 hunks)
  • packages/lib/CalendarService.ts (2 hunks)
  • packages/trpc/server/routers/loggedInViewer/deleteCredential.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/confirm.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/create.handler.ts (1 hunks)
  • packages/types/Calendar.d.ts (4 hunks)
  • packages/types/EventManager.d.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
packages/trpc/server/routers/viewer/organizations/create.handler.ts (1)
packages/lib/constants.ts (1)
  • IS_TEAM_BILLING_ENABLED (88-88)
packages/core/CalendarManager.ts (1)
packages/types/Calendar.d.ts (1)
  • CalendarEvent (146-190)
packages/core/EventManager.ts (4)
packages/app-store/locations.ts (1)
  • MeetLocationType (54-54)
packages/prisma/index.ts (1)
  • prisma (56-56)
packages/core/CalendarManager.ts (2)
  • createEvent (218-270)
  • updateEvent (272-323)
packages/types/EventManager.d.ts (2)
  • PartialReference (3-12)
  • EventResult (14-27)
packages/features/bookings/lib/handleNewBooking.ts (3)
packages/prisma/index.ts (1)
  • prisma (56-56)
packages/prisma/selects/user.ts (1)
  • userSelect (36-51)
packages/lib/server/i18n.ts (1)
  • getTranslation (5-18)
packages/features/bookings/lib/handleCancelBooking.ts (2)
packages/prisma/index.ts (1)
  • prisma (56-56)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (26-48)
packages/app-store/googlecalendar/lib/CalendarService.ts (1)
packages/types/Calendar.d.ts (2)
  • CalendarEvent (146-190)
  • NewCalendarEventType (62-70)
🔇 Additional comments (34)
packages/core/builders/CalendarEvent/builder.ts (1)

1-2: LGTM! Good TypeScript import optimization.

The conversion to type-only import for Booking and the separation of Prisma import improves type safety and import clarity.

packages/types/EventManager.d.ts (1)

26-26: LGTM! Appropriate addition for multi-calendar support.

The optional externalId property enhances the EventResult<T> interface to support external calendar identification in multi-calendar workflows while maintaining backward compatibility.

apps/web/playwright/webhook.e2e.ts (1)

249-249: No changes needed: tests correctly reflect distinct destinationCalendar behavior
The webhook payload builder intentionally returns null for newly created/requested bookings (via handleNewBooking) and an empty array [] for rejected bookings (via handleCancelBooking). The existing assertions in BOOKING_CREATED, BOOKING_REQUESTED, and BOOKING_REJECTED accurately mirror this logic—no update required.

Likely an incorrect or invalid review comment.

packages/core/builders/CalendarEvent/class.ts (1)

26-26: LGTM! Critical foundational change for multi-calendar support.

The type change from DestinationCalendar | null to DestinationCalendar[] | null is essential for supporting multiple destination calendars per event. This foundational update enables the system-wide transition to multi-calendar handling while maintaining backward compatibility by keeping the property nullable.

packages/trpc/server/routers/loggedInViewer/deleteCredential.handler.ts (1)

290-294: LGTM! Proper migration to array-based destinationCalendar.

The logic correctly handles the transition to array-based destinationCalendar by:

  1. Wrapping booking.destinationCalendar in an array if it exists
  2. Falling back to booking.user?.destinationCalendar wrapped in an array
  3. Providing an empty array as the final fallback

This ensures consistent array handling while maintaining the original logic flow for calendar selection.

packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts (1)

240-242: LGTM! Consistent with the destinationCalendar array refactor.

The change correctly wraps the destinationCalendar in an array when it exists, or provides an empty array otherwise. This aligns with the systematic refactor to standardize destinationCalendar as an array throughout the codebase.

packages/trpc/server/routers/viewer/bookings/confirm.handler.ts (1)

175-179: LGTM! Well-structured fallback logic for destinationCalendar array.

The implementation correctly prioritizes booking.destinationCalendar over user.destinationCalendar and ensures the result is always an array. The fallback chain maintains backwards compatibility while conforming to the new array structure.

apps/web/pages/api/cron/bookingReminder.ts (1)

107-107: LGTM! Clean approach using intermediate variable.

The use of selectedDestinationCalendar as an intermediate variable makes the logic more readable while maintaining the same fallback behavior (booking → user → empty array). This approach aligns well with the destinationCalendar array refactor.

Also applies to: 130-130

packages/features/ee/payments/api/paypal-webhook.ts (1)

152-156: LGTM! Consistent with the destinationCalendar array standardization.

The fallback logic correctly prioritizes booking.destinationCalendar over user.destinationCalendar and ensures the result is always an array, maintaining consistency with the broader refactor across the codebase.

packages/features/ee/payments/api/webhook.ts (2)

101-101: LGTM! Clean implementation in getBooking function.

The use of selectedDestinationCalendar intermediate variable and array wrapping follows the established pattern for the destinationCalendar refactor, making the code readable and consistent.

Also applies to: 119-119


207-207: LGTM! Consistent implementation in handlePaymentSuccess function.

The same clean pattern is applied here using the intermediate variable approach. The implementation maintains consistency with the getBooking function and the broader destinationCalendar array refactor.

Also applies to: 229-229

packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts (1)

85-89: LGTM! Proper array conversion for destinationCalendar.

The implementation correctly handles the conversion from single object to array format with proper null safety and fallback logic.

packages/app-store/larkcalendar/lib/CalendarService.ts (4)

128-129: LGTM! Consistent array destructuring pattern.

The destructuring of the first destination calendar from the array is implemented correctly with proper fallback handling.


164-165: LGTM! Consistent implementation.

Same correct pattern applied in the createAttendees method.


192-193: LGTM! Consistent implementation.

Same correct pattern applied in the updateEvent method.


237-238: LGTM! Consistent implementation.

Same correct pattern applied in the deleteEvent method.

packages/emails/src/templates/BrokenIntegrationEmail.tsx (1)

88-91: LGTM! Proper array handling for calendar integration name.

The destructuring pattern correctly extracts the first destination calendar and handles the undefined case with appropriate fallback logic.

packages/lib/CalendarService.ts (2)

156-164: LGTM! Proper calendar filtering logic.

The destructuring pattern correctly extracts the first destination calendar for filtering, with appropriate fallback when no specific calendar is specified.


509-516: LGTM! Correct primary calendar determination.

The implementation properly uses the first destination calendar's externalId to determine which calendar should be marked as primary.

packages/app-store/office365calendar/lib/CalendarService.ts (1)

73-77: LGTM! Proper calendar URL construction.

The destructuring pattern correctly extracts the first destination calendar and constructs the appropriate Office365 API endpoint with proper fallback to the default calendar.

packages/types/Calendar.d.ts (3)

11-11: LGTM! Necessary import for the new schedulingType property.


171-171: Breaking change: destinationCalendar is now an array.

This change from a single DestinationCalendar object to an array enables support for multiple destination calendars per event. The addition of the schedulingType property provides scheduling context for events.

Ensure all consumers of the CalendarEvent interface have been updated to handle the array type.

Also applies to: 182-182


221-221: Interface change: createEvent now requires credentialId parameter.

This addition enables calendar services to select the appropriate destination calendar based on the credential ID when multiple calendars are available.

packages/core/CalendarManager.ts (4)

220-221: Good addition of externalId parameter for tracking purposes.

The optional parameter maintains backward compatibility while enabling external ID tracking.


235-236: Correct implementation of the new createEvent signature.

Properly passes credential.id as required by the updated interface.


237-254: Improved error handling with specific 404 case handling.

Good approach to handle mismatched calendar/credential pairs gracefully by returning undefined for 404 errors. The calError extraction provides better error visibility.


267-268: Good addition of tracking fields to the return object.

Including both externalId and credentialId in the result enables better event tracking and debugging.

packages/features/bookings/lib/handleCancelBooking.ts (1)

251-255: Correct implementation of destinationCalendar as array.

The nested ternary properly handles backward compatibility by checking both booking-level and user-level destination calendars, wrapping single objects in arrays.

packages/features/bookings/lib/handleNewBooking.ts (4)

1-1: LGTM!

The import of DestinationCalendar type is necessary for proper typing of the teamDestinationCalendars array introduced in this change.


724-763: Well-structured error handling improvements!

The enhanced validation and error handling in loadUsers provides better debugging context and proper HTTP status code propagation. The specific handling of HttpError and Prisma.PrismaClientKnownRequestError ensures appropriate error responses.


1874-1879: Correct handling of array to single calendar connection

The implementation properly connects the first destination calendar from the array, maintaining backward compatibility with the database schema while supporting the new multiple calendar format.


987-1006: Null Safety for destinationCalendar Confirmed

The Prisma schema shows destinationCalendar is nullable, but the existing guard at lines 990–994:

if (isTeamEventType &&
    eventType.schedulingType === "COLLECTIVE" &&
    user.destinationCalendar) {
  teamDestinationCalendars.push(user.destinationCalendar);
}

ensures only non-null destinationCalendar values are collected. No further null checks are needed.

packages/core/EventManager.ts (2)

169-170: Good addition of calendar metadata to references

Adding externalCalendarId and credentialId to references enables proper tracking of which specific calendar and credential were used for each event, supporting the multi-calendar architecture.

Also applies to: 208-209


117-121: Verify Google Calendar prioritization in MeetLocationType events

It looks like destinationCalendar is still being handled by only inspecting its first element, and there’s no existing test or documentation enforcing a Google Calendar preference when multiple calendars are present. If your intent is to fall back to “integrations:daily” only when none of the calendars are Google, please confirm and update accordingly.

• Location: packages/core/EventManager.ts (around lines 117–121)
• Current code:

    // @NOTE: destinationCalendar it's an array now so as a fallback we will only check the first one
    const [mainHostDestinationCalendar] = evt.destinationCalendar ?? [];
    if (evt.location === MeetLocationType && mainHostDestinationCalendar.integration !== "google_calendar") {
      evt["location"] = "integrations:daily";
    }

• Suggested change if we need to consider all calendars:

- const [mainHostDestinationCalendar] = evt.destinationCalendar ?? [];
- if (evt.location === MeetLocationType && mainHostDestinationCalendar.integration !== "google_calendar") {
+ const hasGoogleCalendar = evt.destinationCalendar?.some(
+   (cal) => cal.integration === "google_calendar"
+ );
+ if (evt.location === MeetLocationType && !hasGoogleCalendar) {
    evt["location"] = "integrations:daily";
  }

Please verify whether multi-calendar events require this adjustment, and add or update tests/docs to reflect the intended behavior.

Comment on lines +198 to +199
const [mainHostDestinationCalendar] =
event?.destinationCalendar && event?.destinationCalendar.length > 0 ? event.destinationCalendar : [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Logic error in calendar selection for updateEvent.

The calendar extraction for organizer email is correct, but there's a logic issue at line 256:

The current logic will never find a match because you're searching for a calendar where cal.externalId === externalCalendarId when externalCalendarId is already provided. This appears to be attempting to validate the externalCalendarId, but the logic is circular.

Consider removing this redundant check:

      const selectedCalendar = externalCalendarId
        ? externalCalendarId
-       : event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
+       : undefined;

Or if you need to select a default calendar when externalCalendarId is not provided:

      const selectedCalendar = externalCalendarId
-       ? externalCalendarId
-       : event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
+       || mainHostDestinationCalendar?.externalId
+       || "primary";

Also applies to: 228-230, 254-256

🤖 Prompt for AI Agents
In packages/app-store/googlecalendar/lib/CalendarService.ts around lines
228-230, 254-256, and 198-199, the logic for selecting the calendar using
externalCalendarId is flawed because it redundantly checks if a calendar's
externalId matches the already provided externalCalendarId, which is circular
and ineffective. To fix this, remove the redundant matching condition and
instead directly use the externalCalendarId if provided; if not provided,
implement logic to select a default calendar from the available calendars.
Adjust the code to handle both cases clearly without unnecessary or circular
checks.

// Check if the disabledNotes flag is set to true
if (calEvent.hideCalendarNotes) {
calEvent.additionalNotes = "Notes have been hidden by the organiser"; // TODO: i18n this string?
calEvent.additionalNotes = "Notes have been hidden by the organizer"; // TODO: i18n this string?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address the TODO: Internationalize this string.

The spelling change to American English is fine, but as the TODO indicates, this string should be properly internationalized.

Would you like me to help implement the internationalization for this string or create an issue to track it?

🤖 Prompt for AI Agents
In packages/core/CalendarManager.ts at line 230, the string assigned to
calEvent.additionalNotes is currently hardcoded and marked with a TODO for
internationalization. Replace this hardcoded string with a call to the
appropriate internationalization function or method used in the project, passing
the string key or default message to enable translation. If the i18n setup is
not yet in place, create a new issue to track adding internationalization
support for this string.

newBookingId?: number
): Promise<Array<EventResult<NewCalendarEventType>>> {
let calendarReference: PartialReference | undefined = undefined,
let calendarReference: PartialReference[] | undefined = undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix variable declaration syntax

Multiple variables declared with single let statement can cause confusion and potential bugs.

-    let calendarReference: PartialReference[] | undefined = undefined,
-      credential;
+    let calendarReference: PartialReference[] | undefined = undefined;
+    let credential;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let calendarReference: PartialReference[] | undefined = undefined,
let calendarReference: PartialReference[] | undefined = undefined;
let credential;
🤖 Prompt for AI Agents
In packages/core/EventManager.ts at line 479, the variable calendarReference is
declared using a single let statement alongside other variables, which can cause
confusion. Refactor the code to declare calendarReference in its own separate
let statement to improve clarity and reduce potential bugs.

Comment on lines +574 to +583
if (!calendarReference) {
return {
appName: cred.appName,
type: cred.type,
success: false,
uid: "",
originalEvent: event,
credentialId: cred.id,
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent error handling for missing calendar reference

When a calendar reference is missing for "other_calendar" types, the method returns a partial error result. However, this might not be the desired behavior - it could silently fail without proper error propagation.

Consider logging the error or throwing an exception:

  if (!calendarReference) {
+   console.error(`Missing calendar reference for ${cred.type}`);
    return {
      appName: cred.appName,
      type: cred.type,
      success: false,
      uid: "",
      originalEvent: event,
      credentialId: cred.id,
    };
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!calendarReference) {
return {
appName: cred.appName,
type: cred.type,
success: false,
uid: "",
originalEvent: event,
credentialId: cred.id,
};
}
if (!calendarReference) {
console.error(`Missing calendar reference for ${cred.type}`);
return {
appName: cred.appName,
type: cred.type,
success: false,
uid: "",
originalEvent: event,
credentialId: cred.id,
};
}
🤖 Prompt for AI Agents
In packages/core/EventManager.ts around lines 574 to 583, the code returns a
partial error result when a calendar reference is missing, which may cause
silent failures. Instead, modify the code to either log a clear error message
indicating the missing calendar reference or throw an exception to ensure the
error is properly propagated and handled by the caller.

Comment on lines +418 to 484
const bookingCalendarReference = bookingToDelete.references.filter((reference) =>
reference.type.includes("_calendar")
);

if (bookingCalendarReference) {
const { credentialId, uid, externalCalendarId } = bookingCalendarReference;
// If the booking calendar reference contains a credentialId
if (credentialId) {
// Find the correct calendar credential under user credentials
const calendarCredential = bookingToDelete.user.credentials.find(
(credential) => credential.id === credentialId
);
if (calendarCredential) {
const calendar = await getCalendar(calendarCredential);
if (
bookingToDelete.eventType?.recurringEvent &&
bookingToDelete.recurringEventId &&
allRemainingBookings
) {
const promises = bookingToDelete.user.credentials
.filter((credential) => credential.type.endsWith("_calendar"))
.map(async (credential) => {
const calendar = await getCalendar(credential);
for (const updBooking of updatedBookings) {
const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
if (bookingRef) {
const { uid, externalCalendarId } = bookingRef;
const deletedEvent = await calendar?.deleteEvent(uid, evt, externalCalendarId);
apiDeletes.push(deletedEvent);
if (bookingCalendarReference.length > 0) {
for (const reference of bookingCalendarReference) {
const { credentialId, uid, externalCalendarId } = reference;
// If the booking calendar reference contains a credentialId
if (credentialId) {
// Find the correct calendar credential under user credentials
let calendarCredential = bookingToDelete.user.credentials.find(
(credential) => credential.id === credentialId
);
if (!calendarCredential) {
// get credential from DB
const foundCalendarCredential = await prisma.credential.findUnique({
where: {
id: credentialId,
},
});
if (foundCalendarCredential) {
calendarCredential = foundCalendarCredential;
}
}
if (calendarCredential) {
const calendar = await getCalendar(calendarCredential);
if (
bookingToDelete.eventType?.recurringEvent &&
bookingToDelete.recurringEventId &&
allRemainingBookings
) {
const promises = bookingToDelete.user.credentials
.filter((credential) => credential.type.endsWith("_calendar"))
.map(async (credential) => {
const calendar = await getCalendar(credential);
for (const updBooking of updatedBookings) {
const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
if (bookingRef) {
const { uid, externalCalendarId } = bookingRef;
const deletedEvent = await calendar?.deleteEvent(uid, evt, externalCalendarId);
apiDeletes.push(deletedEvent);
}
}
});
try {
await Promise.all(promises);
} catch (error) {
if (error instanceof Error) {
logger.error(error.message);
}
});
try {
await Promise.all(promises);
} catch (error) {
if (error instanceof Error) {
logger.error(error.message);
}
} else {
apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
}
} else {
}
} else {
// For bookings made before the refactor we go through the old behavior of running through each calendar credential
const calendarCredentials = bookingToDelete.user.credentials.filter((credential) =>
credential.type.endsWith("_calendar")
);
for (const credential of calendarCredentials) {
const calendar = await getCalendar(credential);
apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
}
}
} else {
// For bookings made before the refactor we go through the old behaviour of running through each calendar credential
const calendarCredentials = bookingToDelete.user.credentials.filter((credential) =>
credential.type.endsWith("_calendar")
);
for (const credential of calendarCredentials) {
const calendar = await getCalendar(credential);
apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Comprehensive refactor for multi-calendar support with good error handling.

The implementation correctly:

  • Handles multiple calendar references
  • Fetches missing credentials from the database
  • Maintains backward compatibility for legacy bookings
  • Includes proper error handling

However, there's a potential issue at line 449-453 where you're filtering credentials again inside the recurring event block, which seems redundant since you already have the calendar instance.

Consider simplifying the recurring event deletion logic:

-            const promises = bookingToDelete.user.credentials
-              .filter((credential) => credential.type.endsWith("_calendar"))
-              .map(async (credential) => {
-                const calendar = await getCalendar(credential);
+            const promises = updatedBookings.map(async (updBooking) => {
                for (const updBooking of updatedBookings) {
                  const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
                  if (bookingRef) {
                    const { uid, externalCalendarId } = bookingRef;
                    const deletedEvent = await calendar?.deleteEvent(uid, evt, externalCalendarId);
                    apiDeletes.push(deletedEvent);
                  }
                }
              });

This would use the already obtained calendar instance instead of creating new ones.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/features/bookings/lib/handleCancelBooking.ts between lines 418 and
484, the recurring event deletion logic redundantly filters user credentials and
creates new calendar instances inside the Promise.all block, despite already
having a calendar instance from the outer scope. To fix this, refactor the code
to use the existing calendar instance for deleting events in updatedBookings
instead of filtering credentials and creating new calendar instances again. This
will simplify the logic and avoid unnecessary operations.

Comment on lines +1063 to +1067
destinationCalendar: eventType.destinationCalendar
? [eventType.destinationCalendar]
: organizerUser.destinationCalendar
? [organizerUser.destinationCalendar]
: null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify nested ternary and ensure consistent array type

The nested ternary operator reduces readability. Also, based on the EventManager changes, destinationCalendar should always be an array (possibly empty) rather than null.

-    destinationCalendar: eventType.destinationCalendar
-      ? [eventType.destinationCalendar]
-      : organizerUser.destinationCalendar
-      ? [organizerUser.destinationCalendar]
-      : null,
+    destinationCalendar: eventType.destinationCalendar
+      ? [eventType.destinationCalendar]
+      : organizerUser.destinationCalendar
+      ? [organizerUser.destinationCalendar]
+      : [],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
destinationCalendar: eventType.destinationCalendar
? [eventType.destinationCalendar]
: organizerUser.destinationCalendar
? [organizerUser.destinationCalendar]
: null,
destinationCalendar: eventType.destinationCalendar
? [eventType.destinationCalendar]
: organizerUser.destinationCalendar
? [organizerUser.destinationCalendar]
: [],
🤖 Prompt for AI Agents
In packages/features/bookings/lib/handleNewBooking.ts around lines 1063 to 1067,
simplify the nested ternary operator used to assign destinationCalendar by
replacing it with a clearer conditional structure. Ensure destinationCalendar is
always assigned an array type, using an empty array if neither
eventType.destinationCalendar nor organizerUser.destinationCalendar is present,
to maintain consistent typing and improve readability.

Comment on lines +1077 to +1079
if (isTeamEventType && eventType.schedulingType === "COLLECTIVE") {
evt.destinationCalendar?.push(...teamDestinationCalendars);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue with null destinationCalendar

If evt.destinationCalendar is null (as it can be from lines 1063-1067), the optional chaining will prevent team calendars from being added.

  if (isTeamEventType && eventType.schedulingType === "COLLECTIVE") {
-    evt.destinationCalendar?.push(...teamDestinationCalendars);
+    if (!evt.destinationCalendar) {
+      evt.destinationCalendar = [];
+    }
+    evt.destinationCalendar.push(...teamDestinationCalendars);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isTeamEventType && eventType.schedulingType === "COLLECTIVE") {
evt.destinationCalendar?.push(...teamDestinationCalendars);
}
if (isTeamEventType && eventType.schedulingType === "COLLECTIVE") {
if (!evt.destinationCalendar) {
evt.destinationCalendar = [];
}
evt.destinationCalendar.push(...teamDestinationCalendars);
}
🤖 Prompt for AI Agents
In packages/features/bookings/lib/handleNewBooking.ts around lines 1077 to 1079,
the code uses optional chaining on evt.destinationCalendar which can be null,
preventing teamDestinationCalendars from being added. To fix this, check if
evt.destinationCalendar is null and if so, initialize it as an empty array
before pushing teamDestinationCalendars. This ensures the calendars are always
added regardless of the initial state.

Comment on lines +151 to +153
...(IS_TEAM_BILLING_ENABLED ? { slug } : {}),
metadata: {
...(IS_TEAM_BILLING_ENABLED && { requestedSlug: slug }),
...(IS_TEAM_BILLING_ENABLED ? { requestedSlug: slug } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the inverted conditional logic for slug handling.

The conditional logic for slug and requestedSlug has been inverted. Now when IS_TEAM_BILLING_ENABLED is true, both the slug property and requestedSlug metadata are included, whereas when false, neither is included.

This represents a significant behavioral change:

  • Before: slug included when billing disabled, requestedSlug in metadata when billing enabled
  • After: Both slug and requestedSlug included when billing enabled, neither when billing disabled

Please confirm this change aligns with the intended organization creation workflow, particularly regarding how organizations without billing handle slug assignment.


🏁 Script executed:

#!/bin/bash
# Description: Search for usages of organization slug and requestedSlug to understand the impact
# Expected: Find how downstream code handles these properties

echo "=== Searching for organization slug usage ==="
rg -A 3 -B 3 "organization.*slug" --type ts

echo -e "\n=== Searching for requestedSlug usage ==="
rg -A 3 -B 3 "requestedSlug" --type ts

echo -e "\n=== Searching for IS_TEAM_BILLING_ENABLED usage in organization context ==="
rg -A 5 -B 5 "IS_TEAM_BILLING_ENABLED.*organization|organization.*IS_TEAM_BILLING_ENABLED" --type ts

Length of output: 40608


Revert slug inclusion for billing-enabled orgs

When IS_TEAM_BILLING_ENABLED is true, we should only set requestedSlug in metadata (deferring the real slug until publish/payment). The current change adds both slug and requestedSlug on creation, which bypasses the payment flow and will break routing for unpaid orgs.

• File: packages/trpc/server/routers/viewer/organizations/create.handler.ts (around line 151)
• Invert the top-level slug spread so it only applies when billing is disabled:

-            ...(IS_TEAM_BILLING_ENABLED ? { slug } : {}),
+            ...(IS_TEAM_BILLING_ENABLED ? {} : { slug }),

The metadata block stays the same:

-              ...(IS_TEAM_BILLING_ENABLED ? { requestedSlug: slug } : {}),
+              ...(IS_TEAM_BILLING_ENABLED ? { requestedSlug: slug } : {}),
🤖 Prompt for AI Agents
In packages/trpc/server/routers/viewer/organizations/create.handler.ts around
lines 151 to 153, the current code incorrectly includes the top-level slug when
IS_TEAM_BILLING_ENABLED is true, bypassing the payment flow. To fix this, invert
the condition so that the top-level slug is only included when
IS_TEAM_BILLING_ENABLED is false, while keeping the requestedSlug inside
metadata unchanged.

@github-actions

Copy link
Copy Markdown
Contributor

This PR is being marked as stale due to inactivity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants