Skip to content

Conversation

sidequestdeveloper
Copy link

@sidequestdeveloper sidequestdeveloper commented Jul 17, 2025

What does this PR do?

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

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. If N/A, write N/A here and check the checkbox. N/A
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
    Yes, you must set OFFICE365_WEBHOOK_CLIENT_STATE in .env, this is used to validate webhook notifications from Microsoft Graph about calendar changes.

  • What are the minimal test data to have?
    One user with Microsoft Outlook calendar connected and an event created to view availability page.

  • What is expected (happy path) to have (input and output)?

    • Input: View availability on booking page
    • Expected Output:
      • Before cron job runs: Shows availability fetched directly from API (logs show "Cache Miss")
      • After cron job runs: ALL requests served from cache (logs show "Cache Hit")
      • Calendar changes in Outlook trigger webhook → cache updates immediately → next UI request shows updated availability from cache
    1. Check Webhook Subscription:
      • In database: SelectedCalendar table should have office365SubscriptionId populated after cron run
      • Subscription expires after 3 days and auto-renews via cron
    2. Verify Cache Performance:
      • Enable debug logs: NEXT_PUBLIC_LOGGER_LEVEL=2 in .env
      • Expected logs after cron has run:
        [Cache Hit] Returning cached availability result
        
      • You should NOT see "Cache Miss" after the cron job has populated the cache
    3. Test Real-time Updates:
      • Create/modify/delete an event in Outlook
      • Webhook updates cache immediately
      • Refresh Cal.com availability page
      • Still shows "[Cache Hit]" but with updated availability
  • Any other important info that could help to test that PR

    1. Enable Feature Flags in the Feature table:
      • Set calendar-cache to true
      • Set calendar-cache-serve to true
    2. Connect Microsoft Outlook Calendar.
    3. Initialize Calendar Cache by calling the cron API (/api/calendar-cache/cron) either manually or have it setup to run automatically and wait for it to complete.

Note:

  • Currently the cache for the Google calendar integration is used only if the user is in a team and the team has the team feature flag enabled. For this Outlook calendar integration, the user doesn't need to be part of a team and only the global calendar-cache and calendar-cache-serve flags need to be enabled. This was a concious design decision and can be adjusted based on the requirement.

@sidequestdeveloper sidequestdeveloper requested review from a team as code owners July 17, 2025 18:48
Copy link

vercel bot commented Jul 17, 2025

@sidequestdeveloper is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Jul 17, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

coderabbitai bot commented Jul 17, 2025

Walkthrough

The changes introduce webhook-based caching support for Office365 calendar integrations. The database schema is extended with new fields and indexes to track Office365 webhook subscription IDs, expiration, and client state. A new API route handles Office365 webhook GET validation and POST notification events, validating client state and updating cached calendar availability asynchronously. The Office365 calendar service is enhanced with methods to manage webhook subscriptions (watch/unwatch), fetch availability with caching support, and populate initial cache data. Repository methods are updated to query Office365 subscription data and support batch processing. Environment variable configuration and test mocks are updated to include the Office365 webhook client state token.

Estimated code review effort

4 (~90 minutes)

Assessment against linked issues

Objective Addressed Explanation
Implement webhook-based caching for Outlook/Office365 calendars (#21050) Webhook subscription management and caching logic added to Office365 calendar service.
Store and manage Office365 webhook subscription metadata in the database (#21050) Database schema extended with subscription ID, expiration, and client state fields plus indexes.
Validate and process Office365 webhook notifications, updating cached availability (#21050) New API route validates webhook requests and updates cache asynchronously on notifications.
Add environment variable for Office365 webhook client state validation (#21050) Added OFFICE365_WEBHOOK_CLIENT_STATE environment variable and updated related configuration and tests.

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes were found. All modifications are directly related to implementing Office365 webhook-based calendar caching and its supporting infrastructure.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/app-store/office365calendar/lib/CalendarService.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

packages/features/calendar-cache/lib/getShouldServeCache.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5207def and 00b25a5.

📒 Files selected for processing (2)
  • packages/app-store/office365calendar/lib/CalendarService.ts (5 hunks)
  • packages/features/calendar-cache/lib/getShouldServeCache.ts (1 hunks)
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

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

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/features/calendar-cache/lib/getShouldServeCache.ts
  • packages/app-store/office365calendar/lib/CalendarService.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
packages/features/calendar-cache/lib/getShouldServeCache.ts (1)

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

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

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Learnt from: Anshumancanrock
PR: #22570
File: apps/web/modules/signup-view.tsx:253-253
Timestamp: 2025-07-21T21:33:23.343Z
Learning: In signup-view.tsx, when checking if redirectUrl contains certain strings, using explicit && checks (redirectUrl && redirectUrl.includes()) is preferred over optional chaining (redirectUrl?.includes()) to ensure the result is always a boolean rather than potentially undefined. This approach provides cleaner boolean contracts for downstream conditional logic.

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The upsertSelectedCalendarsForEventTypeIds method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be applied as they would prevent this intended functionality.

Learnt from: hariombalhara
PR: #22547
File: packages/embeds/embed-core/src/lib/eventHandlers/scrollByDistanceEventHandler.ts:11-14
Timestamp: 2025-07-16T11:46:28.759Z
Learning: In Cal.com's embed system, internal events like "__scrollByDistance" are fired by Cal.com's own code, so runtime validation of event data structure is unnecessary since TypeScript type system guarantees type safety for internal events.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be added as they would break this subscription sharing functionality.

🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

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

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/features/calendar-cache/lib/getShouldServeCache.ts
  • packages/app-store/office365calendar/lib/CalendarService.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The `upsertSelectedCalendarsForEventTypeIds` method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like `@@unique([outlookSubscriptionId, eventTypeId])` should not be applied as they would prevent this intended functionality.
packages/features/calendar-cache/lib/getShouldServeCache.ts (1)

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

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

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Learnt from: Anshumancanrock
PR: #22570
File: apps/web/modules/signup-view.tsx:253-253
Timestamp: 2025-07-21T21:33:23.343Z
Learning: In signup-view.tsx, when checking if redirectUrl contains certain strings, using explicit && checks (redirectUrl && redirectUrl.includes()) is preferred over optional chaining (redirectUrl?.includes()) to ensure the result is always a boolean rather than potentially undefined. This approach provides cleaner boolean contracts for downstream conditional logic.

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The upsertSelectedCalendarsForEventTypeIds method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be applied as they would prevent this intended functionality.

Learnt from: hariombalhara
PR: #22547
File: packages/embeds/embed-core/src/lib/eventHandlers/scrollByDistanceEventHandler.ts:11-14
Timestamp: 2025-07-16T11:46:28.759Z
Learning: In Cal.com's embed system, internal events like "__scrollByDistance" are fired by Cal.com's own code, so runtime validation of event data structure is unnecessary since TypeScript type system guarantees type safety for internal events.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be added as they would break this subscription sharing functionality.

⏰ 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: Security Check
🔇 Additional comments (13)
packages/features/calendar-cache/lib/getShouldServeCache.ts (1)

6-14: LGTM! Improved feature flag handling.

The enhanced logic properly handles both team-level and global feature flag scenarios, making the function more robust and easier to understand.

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

5-18: LGTM! All necessary imports added for caching functionality.

The new imports are well-organized and directly support the webhook subscription management and caching features being implemented.

Also applies to: 26-26


306-361: LGTM! Well-implemented caching integration.

The enhanced getAvailability method properly integrates caching while maintaining backward compatibility. The smart use of expanded time ranges for better cache hits and graceful fallback to direct API calls is well-designed.


363-415: LGTM! Clean extraction of direct API functionality.

The fetchAvailability method properly encapsulates direct Office365 API calls with all the necessary batch processing, pagination, and retry logic. Good separation of concerns from the main availability method.


680-680: LGTM! Improved error logging safety.

Using safeStringify prevents potential issues with circular references in debug logging.


687-807: LGTM! Comprehensive webhook subscription management.

The watchCalendar method is well-implemented with:

  • Smart subscription reuse logic for efficiency
  • Proper Microsoft Graph API integration
  • Comprehensive error handling and logging
  • Database metadata management
  • Initial cache population for new subscriptions

The implementation aligns with the retrieved learning about subscription sharing patterns in Cal.com.


809-915: LGTM! Proper subscription cleanup logic.

The unwatchCalendar method correctly handles the complexity of subscription removal:

  • Checks for shared subscriptions before deletion
  • Appropriately scoped cache cleanup
  • Database metadata cleanup
  • Comprehensive error handling

The logic prevents accidental deletion of subscriptions still in use by other calendars.


917-958: LGTM! Core caching logic well-implemented.

The getFreeBusyResult method effectively implements the caching layer with:

  • Smart time range expansion for better cache hits
  • Proper cache miss handling with fallback to direct API calls
  • Good debug logging for cache performance monitoring
  • Clean separation between cached and direct data access

960-971: LGTM! Clean data transformation adapter.

The getCacheOrFetchAvailability method provides a clean interface for transforming cached data to the expected format while delegating the core logic appropriately.


973-987: LGTM! Proper database helper method.

The upsertSelectedCalendar method includes appropriate validation and error handling for database operations.


989-1008: LGTM! Consistent batch database operations.

The upsertSelectedCalendarsForEventTypeIds method properly extends the single calendar upsert pattern to handle multiple event types, maintaining consistency with the subscription sharing design.


1010-1040: LGTM! Proper initial cache population for Office365.

The populateInitialCacheForNewSubscription method addresses the Office365-specific behavior where initial sync webhooks aren't sent. The error handling is appropriate - it logs issues but doesn't prevent successful subscription creation.


1042-1087: LGTM! Efficient cache population implementation.

The fetchAvailabilityAndSetCache method efficiently groups calendars by event type and uses expanded time ranges for optimal cache performance. The cache data structure properly includes all necessary metadata.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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 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 added $500 calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar teams area: teams, round robin, collective, managed event-types ✨ feature New feature or request 💎 Bounty A bounty on Algora.io and removed ✨ feature New feature or request teams area: teams, round robin, collective, managed event-types labels Jul 17, 2025
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jul 17, 2025
@github-actions github-actions bot removed 💎 Bounty A bounty on Algora.io calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar labels Jul 17, 2025
@graphite-app graphite-app bot requested a review from a team July 17, 2025 18:48
@github-actions github-actions bot added ❗️ migrations contains migration files ❗️ .env changes contains changes to env variables and removed $500 labels Jul 17, 2025
Copy link

graphite-app bot commented Jul 17, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (07/17/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (07/17/25)

1 label was added to this PR based on Keith Williams's automation.

@dosubot dosubot bot added calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar ✨ feature New feature or request labels Jul 17, 2025
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: 6

♻️ Duplicate comments (1)
packages/app-store/office365calendar/lib/CalendarService.ts (1)

976-995: Apply the same error handling improvement here

This method has the same silent failure pattern as upsertSelectedCalendar. Apply the same error handling improvement for consistency.

🧹 Nitpick comments (5)
.env.example (1)

465-465: Add missing blank line at end of file.

The static analysis tool correctly identified that the file should end with a blank line, which is a common convention in many codebases.

Apply this diff to add the missing blank line:

 OFFICE365_WEBHOOK_CLIENT_STATE=
+
packages/app-store/office365calendar/api/webhook.ts (1)

51-88: Consider parallel processing for better performance.

Processing notifications sequentially could lead to timeouts with large batches. Consider parallel processing with concurrency limits.

-  for (const notification of notifications) {
-    try {
-      const { subscriptionId, resource } = notification;
-      // ... rest of the processing
-    } catch (error) {
-      log.error(
-        "Error processing notification",
-        safeStringify({ error, subscriptionId: notification.subscriptionId })
-      );
-    }
-  }
+  // Process notifications in parallel with concurrency limit
+  const CONCURRENCY_LIMIT = 5;
+  const chunks = [];
+  for (let i = 0; i < notifications.length; i += CONCURRENCY_LIMIT) {
+    chunks.push(notifications.slice(i, i + CONCURRENCY_LIMIT));
+  }
+
+  for (const chunk of chunks) {
+    await Promise.allSettled(
+      chunk.map(async (notification) => {
+        try {
+          const { subscriptionId, resource } = notification;
+          // ... rest of the processing
+        } catch (error) {
+          log.error(
+            "Error processing notification",
+            safeStringify({ error, subscriptionId: notification.subscriptionId })
+          );
+        }
+      })
+    );
+  }
packages/lib/server/repository/selectedCalendar.ts (1)

161-189: Consider extracting complex OR conditions for better readability.

The nested OR conditions make the query hard to understand and maintain. Consider extracting them into helper functions.

+  private static getGoogleCalendarWatchConditions(tomorrowTimestamp: string) {
+    return {
+      integration: "google_calendar",
+      user: {
+        teams: {
+          some: {
+            team: {
+              features: {
+                some: {
+                  featureId: "calendar-cache",
+                },
+              },
+            },
+          },
+        },
+      },
+      OR: [{ googleChannelExpiration: null }, { googleChannelExpiration: { lt: tomorrowTimestamp } }],
+    };
+  }
+
+  private static getOffice365WatchConditions(tomorrowTimestamp: string) {
+    return {
+      integration: "office365_calendar",
+      OR: [
+        { office365SubscriptionExpiration: null },
+        { office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp)) } },
+      ],
+    };
+  }
+
   static async getNextBatchToWatch(limit = 100) {
     const oneDayInMS = 24 * 60 * 60 * 1000;
     const tomorrowTimestamp = String(new Date().getTime() + oneDayInMS);
     const nextBatch = await prisma.selectedCalendar.findMany({
       take: limit,
       where: {
         OR: [
-          // Google Calendar - requires team with calendar-cache feature
-          {
-            integration: "google_calendar",
-            // ... existing conditions
-          },
-          // Office365 Calendar - no team requirement
-          {
-            integration: "office365_calendar",
-            // ... existing conditions
-          },
+          this.getGoogleCalendarWatchConditions(tomorrowTimestamp),
+          this.getOffice365WatchConditions(tomorrowTimestamp),
         ],
         // Common conditions for both calendar types
         AND: [
           // ... existing conditions
         ],
       },
     });
     return nextBatch;
   }
packages/app-store/office365calendar/lib/CalendarService.ts (2)

684-804: Consider making subscription expiration configurable

The implementation is comprehensive with good subscription reuse logic. However, the 3-day expiration is hardcoded on line 743. Consider making this configurable through an environment variable for flexibility.

Also, ensure that NEXT_PUBLIC_WEBAPP_URL is properly validated and ends without a trailing slash to avoid double slashes in the webhook URL.

-      const expirationDateTime = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(); // 3 Days
+      const expirationDays = process.env.OFFICE365_WEBHOOK_EXPIRATION_DAYS ? parseInt(process.env.OFFICE365_WEBHOOK_EXPIRATION_DAYS) : 3;
+      const expirationDateTime = new Date(Date.now() + expirationDays * 24 * 60 * 60 * 1000).toISOString();

960-974: Consider throwing an error instead of silent failure

While the userId validation is good, consider throwing an error instead of silently returning when userId is missing. This would make debugging easier and ensure calling code is aware of the failure.

     if (!this.credential.userId) {
-      logger.error("upsertSelectedCalendar failed. userId is missing.");
-      return;
+      const error = new Error("upsertSelectedCalendar failed. userId is missing.");
+      logger.error(error);
+      throw error;
     }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4dc2260 and d703a7f.

📒 Files selected for processing (9)
  • .env.example (1 hunks)
  • apps/api/v1/test/lib/selected-calendars/_post.test.ts (2 hunks)
  • packages/app-store/office365calendar/api/index.ts (1 hunks)
  • packages/app-store/office365calendar/api/webhook.ts (1 hunks)
  • packages/app-store/office365calendar/lib/CalendarService.ts (5 hunks)
  • packages/lib/server/repository/selectedCalendar.ts (4 hunks)
  • packages/prisma/migrations/20250715074701_add_office365_calendar_webhook_support/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • turbo.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
packages/app-store/office365calendar/lib/CalendarService.ts (1)
Learnt from: hariombalhara
PR: calcom/cal.com#22547
File: packages/embeds/embed-core/src/lib/eventHandlers/scrollByDistanceEventHandler.ts:11-14
Timestamp: 2025-07-16T11:46:28.738Z
Learning: In Cal.com's embed system, internal events like "__scrollByDistance" are fired by Cal.com's own code, so runtime validation of event data structure is unnecessary since TypeScript type system guarantees type safety for internal events.
🧬 Code Graph Analysis (1)
packages/lib/server/repository/selectedCalendar.ts (2)
packages/prisma/selects/credential.ts (1)
  • credentialForCalendarServiceSelect (3-17)
packages/platform/libraries/index.ts (1)
  • credentialForCalendarServiceSelect (97-97)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 465-465: [EndingBlankLine] No blank line at the end of the file

🪛 Biome (1.9.4)
packages/app-store/office365calendar/lib/CalendarService.ts

[error] 935-935: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ 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: Security Check
🔇 Additional comments (20)
.env.example (1)

463-465: LGTM! Well-documented environment variable addition.

The new OFFICE365_WEBHOOK_CLIENT_STATE environment variable is properly documented with clear instructions for generating a secure random string. This aligns well with the webhook validation pattern used by other calendar integrations in the codebase.

turbo.json (1)

378-378: LGTM! Proper global environment configuration.

The OFFICE365_WEBHOOK_CLIENT_STATE environment variable is correctly added to the globalEnv array in alphabetical order, ensuring it's available across all build and runtime environments.

packages/app-store/office365calendar/api/index.ts (1)

3-3: LGTM! Consistent API export pattern.

The new webhook export follows the same pattern as the existing add and callback exports, properly exposing the webhook API endpoint.

apps/api/v1/test/lib/selected-calendars/_post.test.ts (2)

91-93: LGTM! Test mock data properly updated for Office365 subscription fields.

The addition of office365SubscriptionId, office365SubscriptionExpiration, and office365SubscriptionClientState fields with null values correctly reflects the new database schema for Office365 webhook subscription tracking.


136-138: LGTM! Consistent mock data structure maintained.

The Office365 subscription fields are consistently added to both admin and non-admin test scenarios, maintaining proper test coverage.

packages/prisma/schema.prisma (3)

868-872: LGTM! Office365 subscription fields follow established patterns.

The new fields for Office365 subscription tracking are well-structured and consistent with the existing Google Calendar webhook implementation.


893-893: Appropriate unique constraint for Office365 subscriptions.

The unique constraint on [office365SubscriptionId, eventTypeId] ensures data integrity by preventing duplicate subscriptions.


901-903: Well-designed indexes for Office365 webhook management.

The composite indexes on [integration, office365SubscriptionExpiration, error, watchAttempts/unwatchAttempts, maxAttempts] will optimize queries for managing webhook subscription lifecycle.

packages/app-store/office365calendar/api/webhook.ts (1)

15-23: Ignore origin validation for the GET validation request.

Microsoft Graph’s subscription validation GET only provides the validationToken and does not include any header or signature you can check against an IP range—so adding origin validation here isn’t feasible. Your handler is correct as-is for GET; the recommended security control is to validate the OFFICE365_WEBHOOK_CLIENT_STATE in your POST notification handler instead.

Likely an incorrect or invalid review comment.

packages/prisma/migrations/20250715074701_add_office365_calendar_webhook_support/migration.sql (1)

1-13: Migration correctly implements Office365 webhook support.

The SQL migration properly adds the required columns and indexes for Office365 calendar webhook subscription tracking.

packages/lib/server/repository/selectedCalendar.ts (3)

31-36: Type definition correctly extends support for Office365 subscriptions.

The addition of office365SubscriptionId filter follows the established pattern for googleChannelId.


309-330: New method properly implements Office365 subscription lookup.

The findFirstByOffice365SubscriptionId method correctly filters selected calendars by integration type and includes necessary credential data.


180-187: Confirm Office365 Calendar Feature Flag Omission

The Google Calendar integration in packages/lib/server/repository/selectedCalendar.ts includes a user.teams.some.team.features.some.featureId === "calendar-cache" check, but the Office365 block at lines 180–187 omits any calendar-cache feature filter. Please verify that this difference is intentional and that Office365 calendar caching should not be gated by the calendar-cache team feature.

• File: packages/lib/server/repository/selectedCalendar.ts
• Lines: ~180–187 (Office365 snippet) vs. ~163–174 (Google snippet)
• Google requires calendar-cache feature; Office365 does not

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

5-7: LGTM!

The new imports are appropriate for the caching and webhook functionality being added.

Also applies to: 15-18, 26-26


306-358: Well-structured cache integration!

The modified getAvailability method properly handles both cached and non-cached scenarios while maintaining backward compatibility with the optional shouldServeCache parameter.


360-412: Good separation of concerns!

The extraction of fetchAvailability from the original getAvailability method improves code organization and reusability. The implementation maintains the original logic while making it accessible for both cached and non-cached scenarios.


677-677: Good practice for safe logging!

Using safeStringify prevents potential circular reference errors when logging response data.


950-958: Clean adapter implementation!

The getCacheOrFetchAvailability method provides a clean interface for the caching functionality while maintaining the expected return type.


997-1027: Well-documented cache initialization!

The method clearly explains why initial cache population is necessary for Office365 (unlike Google which sends initial sync webhooks). The error handling is appropriate as cache population failure shouldn't break the subscription creation flow.


1029-1074: Default date range behavior verified
Both getTimeMin() and getTimeMax() intentionally default to a sliding two-month cache window when called without arguments (start of the current month and start of the month two months out, respectively), as documented in packages/features/calendar-cache/lib/datesForCache.ts. No changes needed.

Comment on lines +25 to +91
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const validationToken = req.query.validationToken;
if (validationToken && typeof validationToken === "string") {
res.setHeader("Content-Type", "text/plain");
res.status(200).send(validationToken);
return;
}

const { value: notifications } = req.body;

if (!notifications || !Array.isArray(notifications)) {
throw new HttpError({ statusCode: 400, message: "Invalid notification payload" });
}

const expectedClientState = process.env.OFFICE365_WEBHOOK_CLIENT_STATE;
if (!expectedClientState) {
log.error("OFFICE365_WEBHOOK_CLIENT_STATE not configured");
throw new HttpError({ statusCode: 500, message: "Webhook not configured" });
}

for (const notification of notifications) {
if (notification.clientState !== expectedClientState) {
throw new HttpError({ statusCode: 403, message: "Invalid client state" });
}
}

for (const notification of notifications) {
try {
const { subscriptionId, resource } = notification;

if (!subscriptionId || !resource) {
log.warn("Notification missing required fields");
continue;
}

log.debug("Processing notification", { subscriptionId });

const selectedCalendar = await SelectedCalendarRepository.findFirstByOffice365SubscriptionId(
subscriptionId
);

if (!selectedCalendar) {
log.debug("No selected calendar found for subscription", { subscriptionId });
continue;
}

const { credential } = selectedCalendar;
if (!credential) {
log.debug("No credential found for selected calendar", { subscriptionId });
continue;
}

const { selectedCalendars } = credential;
const credentialForCalendarCache = await getCredentialForCalendarCache({ credentialId: credential.id });
const calendarServiceForCalendarCache = await getCalendar(credentialForCalendarCache);
await calendarServiceForCalendarCache?.fetchAvailabilityAndSetCache?.(selectedCalendars);
log.debug("Successfully updated calendar cache", { subscriptionId });
} catch (error) {
log.error(
"Error processing notification",
safeStringify({ error, subscriptionId: notification.subscriptionId })
);
}
}

return { message: "ok" };
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add request size and rate limiting protection.

The webhook endpoint should validate request size and implement rate limiting to prevent abuse.

Consider implementing:

  1. Request body size validation
  2. Rate limiting per subscription or IP
  3. Request timeout handling
  4. Dead letter queue for failed notifications
🤖 Prompt for AI Agents
In packages/app-store/office365calendar/api/webhook.ts between lines 25 and 91,
the webhook handler lacks protections against large request bodies and excessive
request rates, which can lead to abuse or service degradation. Add request body
size validation to reject overly large payloads early. Implement rate limiting
based on subscriptionId or client IP to control request frequency. Include
request timeout handling to avoid hanging requests. Consider adding a dead
letter queue mechanism to capture and retry or analyze failed notification
processing asynchronously.

Comment on lines +45 to +49
for (const notification of notifications) {
if (notification.clientState !== expectedClientState) {
throw new HttpError({ statusCode: 403, message: "Invalid client state" });
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add protection against timing attacks in client state validation.

The client state comparison should use constant-time comparison to prevent timing attacks.

+import crypto from "crypto";
+
 for (const notification of notifications) {
-    if (notification.clientState !== expectedClientState) {
+    // Use constant-time comparison to prevent timing attacks
+    if (!crypto.timingSafeEqual(
+      Buffer.from(notification.clientState || ''),
+      Buffer.from(expectedClientState)
+    )) {
       throw new HttpError({ statusCode: 403, message: "Invalid client state" });
     }
 }
📝 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
for (const notification of notifications) {
if (notification.clientState !== expectedClientState) {
throw new HttpError({ statusCode: 403, message: "Invalid client state" });
}
}
import crypto from "crypto";
for (const notification of notifications) {
// Use constant-time comparison to prevent timing attacks
if (!crypto.timingSafeEqual(
Buffer.from(notification.clientState || ''),
Buffer.from(expectedClientState)
)) {
throw new HttpError({ statusCode: 403, message: "Invalid client state" });
}
}
🤖 Prompt for AI Agents
In packages/app-store/office365calendar/api/webhook.ts around lines 45 to 49,
the clientState comparison uses a direct equality check which is vulnerable to
timing attacks. Replace the direct comparison with a constant-time comparison
function to securely compare notification.clientState and expectedClientState,
preventing timing attack vulnerabilities.

Comment on lines +184 to +186
{ office365SubscriptionExpiration: null },
{ office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp)) } },
],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix date parsing inconsistency.

The Office365 condition parses tomorrowTimestamp as an integer, but it's already a string representation of milliseconds.

-              { office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp)) } },
+              { office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp, 10)) } },
📝 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
{ office365SubscriptionExpiration: null },
{ office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp)) } },
],
{ office365SubscriptionExpiration: null },
{ office365SubscriptionExpiration: { lt: new Date(parseInt(tomorrowTimestamp, 10)) } },
],
🤖 Prompt for AI Agents
In packages/lib/server/repository/selectedCalendar.ts around lines 184 to 186,
the code incorrectly parses tomorrowTimestamp using parseInt before passing it
to the Date constructor, but tomorrowTimestamp is already a string representing
milliseconds. Remove the parseInt call and directly pass tomorrowTimestamp to
the Date constructor to fix the date parsing inconsistency.

…and fixed incorrect unwatch query for global calendar-cache flag
@github-actions github-actions bot added $500 teams area: teams, round robin, collective, managed event-types 💎 Bounty A bounty on Algora.io labels Jul 17, 2025
@keithwillcode
Copy link
Contributor

/tip 100 @sidequestdeveloper

Copy link

algora-pbc bot commented Jul 22, 2025

@sidequestdeveloper: You've been awarded a $100 by Cal.com, Inc.! 👉 Complete your Algora onboarding to collect the tip.

Copy link
Contributor

github-actions bot commented Aug 6, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Aug 6, 2025
Copy link
Contributor

@Devanshusharma2005 Devanshusharma2005 left a comment

Choose a reason for hiding this comment

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

Could you please address the coderabbit suggestions. They are right.

@github-actions github-actions bot removed the Stale label Aug 23, 2025
@keithwillcode
Copy link
Contributor

Please see comment here #21050 (comment). Thanks again for this contribution and apologies for the confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙋 Bounty claim 💎 Bounty A bounty on Algora.io calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar community Created by Linear-GitHub Sync ❗️ .env changes contains changes to env variables ✨ feature New feature or request ❗️ migrations contains migration files teams area: teams, round robin, collective, managed event-types $500
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Outlook Cache – Bounty-to-Hire
4 participants