Skip to content

Comments

feat: outlook cache and change notifications webhook#21071

Closed
ouwargui wants to merge 24 commits intocalcom:mainfrom
ouwargui:feat/outlook-cache
Closed

feat: outlook cache and change notifications webhook#21071
ouwargui wants to merge 24 commits intocalcom:mainfrom
ouwargui:feat/outlook-cache

Conversation

@ouwargui
Copy link
Contributor

@ouwargui ouwargui commented May 2, 2025

What does this PR do?

Issue

The core issue is that fetching availability data for Outlook calendars currently involves querying the Microsoft Graph API every time someone views a public booking page that uses an Outlook calendar.

How it was fixed

Used the Google Calendar code as base to implement caching to the Microsoft Graph response and added a webhook to handle calendar change notifications and update the cache.

Also allowed office365_calendar integration to be watched and unwatched on the calendar-cache cronjob.

Next steps

We should refactor some duplicate code between googlecalendar.CalendarService and office365calendar.CalendarService.

Visual Demo (For contributors especially)

https://cap.link/e6g6jgd2t1nztd9

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • 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?
    • OUTLOOK_WEBHOOK_TOKEN: Token to verify incoming webhooks from Microsoft Outlook (Required)
    • OUTLOOK_WEBHOOK_URL: Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
  • What are the minimal test data to have?
    • Setup two or more outlook calendars, create a team on cal.com and enable the calendar-cache feature flag for your team.
  • What is expected (happy path) to have (input and output)?
    • If all the above is meet, then:
      1. Go to the public page of one of your events, this should create an entry on SelectedCalendar table
      2. Run the calendar-cache cronjob manually or wait for it to run automatically
      3. The cronjob will subscribe to change notifications
      4. Add, update or delete an event in one of your calendars
      5. The webhook should be called and an entry should be added to CalendarCache table
  • Any other important info that could help to test that PR
    • Microsoft Graph requires the webhook callback url to be https

Summary by mrge

Added support for caching Outlook calendar availability and handling Microsoft Graph webhooks to keep cache updated.

  • New Features
    • Added webhook endpoint for Outlook calendar event changes.
    • Cached Outlook calendar availability to reduce API calls.
    • Updated database schema to store Outlook subscription info.
    • Extended calendar-cache logic to support Outlook calendars.

@ouwargui ouwargui requested a review from a team as a code owner May 2, 2025 03:57
@vercel
Copy link

vercel bot commented May 2, 2025

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

A member of the Team first needs to authorize it.

@graphite-app graphite-app bot requested a review from a team May 2, 2025 03:58
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label May 2, 2025
@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 ❗️ migrations contains migration files ❗️ .env changes contains changes to env variables labels May 2, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 2, 2025

Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted.

Details:

No release type found in pull request title "Feat/outlook cache". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

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

@ouwargui ouwargui changed the title Feat/outlook cache feat: outlook cache May 2, 2025
@graphite-app
Copy link

graphite-app bot commented May 2, 2025

Graphite Automations

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

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

"Add community label" took an action on this PR • (05/02/25)

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

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

mrge found 4 issues across 10 files. View them in mrge.io

GOOGLE_WEBHOOK_URL=

# Token to verify incoming webhooks from Microsoft Outlook
OUTLOOK_WEBHOOK_TOKEN=
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing guidance on token generation requirements

const webhookBodyParseRes = changeNotificationWebhookPayloadSchema.safeParse(req.body);

if (!webhookBodyParseRes.success) {
log.error("postHandler", safeStringify(webhookBodyParseRes.error));
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule violated: Avoid Logging Sensitive Information

  Logging validation errors may expose sensitive information

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can remove this or change to something else if wanted

Comment on lines +34 to +39
const validationToken = getValidationToken(req);
if (validationToken) {
res.setHeader("Content-Type", "text/plain");
return res.status(200).send(validationToken);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When you create a subscription, microsoft send a validation request to the webhook url with a validationToken on the search params, we have to return it in plain text.

More info here: https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks?tabs=http#notificationurl-validation

const webhookBodyParseRes = changeNotificationWebhookPayloadSchema.safeParse(req.body);

if (!webhookBodyParseRes.success) {
log.error("postHandler", safeStringify(webhookBodyParseRes.error));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can remove this or change to something else if wanted

Comment on lines +98 to +106
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") return postHandler(req, res);

res.setHeader("Allow", "POST");
return res.status(405).json({});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't use the defaultHandler and defaultResponder here because I need to be able to return text/plain when Microsoft sends a validation challenge

Comment on lines +328 to +357
const selectedCalendarIds = args.items.map((item) => item.id);

const ids = await (selectedCalendarIds.length === 0
? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || [])
: Promise.resolve(selectedCalendarIds));
const requestsPromises = ids.map(async (calendarId, id) => ({
id,
method: "GET",
url: `${await this.getUserEndpoint()}/calendars/${calendarId}/calendarView${filter}&${calendarSelectParams}`,
}));
const requests = await Promise.all(requestsPromises);
const response = await this.apiGraphBatchCall(requests);
const responseBody = await this.handleErrorJsonOffice365Calendar(response);
let responseBatchApi: IBatchResponse = { responses: [] };
if (typeof responseBody === "string") {
responseBatchApi = this.handleTextJsonResponseWithHtmlInBody(responseBody);
}
let alreadySuccessResponse = [] as ISettledResponse[];

// Validate if any 429 status Retry-After is present
const retryAfter =
!!responseBatchApi?.responses && this.findRetryAfterResponse(responseBatchApi.responses);

if (retryAfter && responseBatchApi.responses) {
responseBatchApi = await this.fetchRequestWithRetryAfter(requests, responseBatchApi.responses, 2);
}

// Recursively fetch nextLink responses
alreadySuccessResponse = await this.fetchResponsesWithNextLink(responseBatchApi.responses);
return alreadySuccessResponse ? alreadySuccessResponse : [];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is exactly the same code as before, just moved to a different function

);
}

async watchCalendar({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is very similar to google calendar's as well, we should extract it

);
}

async unwatchCalendar({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is very similar to google calendar's, we should extract it

Comment on lines +35 to +41
/**
* https://learn.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=http#response-1
*/
export const startWatchingCalendarResponseSchema = z.object({
id: z.string(),
expirationDateTime: z.string(),
});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's more fields, but I think they're not relevant for now

Comment on lines +6 to +12
*/
-- AlterTable
ALTER TABLE "SelectedCalendar" ADD COLUMN "outlookSubscriptionExpiration" TEXT,
ADD COLUMN "outlookSubscriptionId" TEXT;

-- CreateIndex
CREATE UNIQUE INDEX "SelectedCalendar_outlookSubscriptionId_eventTypeId_key" ON "SelectedCalendar"("outlookSubscriptionId", "eventTypeId");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added outlookSubscriptionExpiration and outlookSubscriptionId fields to the SelectedCalendar table. Also added outlookSubscriptionId as unique.

@ouwargui ouwargui changed the title feat: outlook cache feat: outlook cache and change notifications webhook May 2, 2025
@ouwargui ouwargui requested a review from a team May 2, 2025 05:23
@ouwargui ouwargui force-pushed the feat/outlook-cache branch from 671597a to 6620546 Compare May 2, 2025 14:56
Comment on lines +50 to +55
const events = webhookBodyParseRes.data.value;
const body: WebhookResponse = {};

const uniqueEvents = uniqueBy(events, ["subscriptionId"]);

const promises = uniqueEvents.map(async (event) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Microsoft send a value array on the payload which may contain one or more events for different subscriptionId.

When many changes occur, Microsoft Graph may send multiple notifications that correspond to different subscriptions in the same POST request.

I'm removing duplicate subscriptionIds to avoid fetching availability multiple times for the same calendar.

I'm not sure if the way I'm handling each event and adding the result to a body object is the best, but let me know if you have a different idea.

@ouwargui ouwargui force-pushed the feat/outlook-cache branch 2 times, most recently from 02e9837 to 3ac5fef Compare May 2, 2025 20:01
@ouwargui
Copy link
Contributor Author

ouwargui commented May 2, 2025

@hbjORbj hey, could you review my pr as well? i'm almost finished adding unit tests and i'd appreciate if i could get a review before

also, i tried to record a video but my mac air couldn't handle the server running + recording - i'll see if i can do it later on my windows pc

Copy link
Contributor

@zomars zomars left a comment

Choose a reason for hiding this comment

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

Due to the complexity of these changes we require a recorded demo and proper integration tests in order for us to being able to approve and merge

@ouwargui ouwargui force-pushed the feat/outlook-cache branch from 6eaddc9 to 4b60453 Compare May 3, 2025 02:31
@ouwargui ouwargui requested a review from zomars May 3, 2025 05:07
@ouwargui
Copy link
Contributor Author

ouwargui commented May 3, 2025

@zomars added some tests and a video, let me know if you need anything else - thanks :)

@ouwargui ouwargui force-pushed the feat/outlook-cache branch 2 times, most recently from 3e161ea to 391873a Compare May 5, 2025 14:02
@ouwargui ouwargui force-pushed the feat/outlook-cache branch from fec4642 to 27876cb Compare May 16, 2025 02:35
@ouwargui
Copy link
Contributor Author

@zomars @PeerRich i don't have time to keep maintaining this PR, so let me know if you have any updates and i'll make sure to update the branch and address reviews

@github-actions
Copy link
Contributor

github-actions bot commented Jun 7, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Jun 7, 2025
@github-actions github-actions bot removed the Stale label Jun 13, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Jul 3, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Jul 3, 2025
@ouwargui ouwargui requested a review from a team July 24, 2025 22:51
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 24, 2025

Walkthrough

This change set introduces comprehensive support for Outlook calendar webhook subscriptions and caching, similar to existing Google calendar functionality. Database schema and migration scripts add outlookSubscriptionId and outlookSubscriptionExpiration fields to the SelectedCalendar model, with a unique constraint. Environment variables and documentation are updated to reflect new Outlook webhook configuration. The Office365 calendar integration is significantly refactored to support webhook-driven cache updates, including new methods for managing Outlook subscriptions, cache population, and batch availability fetching. New Zod schemas validate webhook payloads. Extensive tests and mocks are added for the enhanced calendar service, and repository methods are updated to support Outlook subscription logic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Complexity rationale:
This change set spans database schema/migration, environment/configuration, core business logic, webhook handling, repository updates, and extensive new and modified tests. It introduces new public methods, alters data flow, and adds new validation schemas, requiring careful review of integration points, side effects, and backward compatibility. The scope is broad but focused, with high-complexity changes in the Office365 calendar service and repository, and significant new test coverage.

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.

apps/api/v1/test/lib/selected-calendars/_post.test.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/app-store/office365calendar/api/webhook.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/app-store/office365calendar/api/index.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.

  • 5 others

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

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

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: 4

♻️ Duplicate comments (2)
packages/app-store/office365calendar/api/webhook.ts (1)

44-44: Consider removing or sanitizing error logging

Logging validation errors may expose sensitive webhook payload information.

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

758-777: Consider making subscription expiration configurable

The hardcoded 10,000 minutes (~7 days) could be made configurable for flexibility.

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

152-156: Good to see Outlook variables added
Including placeholders for OUTLOOK_WEBHOOK_TOKEN and OUTLOOK_WEBHOOK_URL keeps the sample env complete and avoids onboarding friction.

It could be helpful to add an inline comment (like the Google section) mentioning that the token must match the value configured in Azure App Registration ↦ Subscriptions panel.

packages/features/calendar-cache/CalendarCache.md (2)

6-12: Fix list-item indentation & keep Markdown lint-clean

Markdown-lint (MD007) flags the sub-lists here because they are indented by 4 spaces instead of the expected 2/4/6 nesting pattern. Re-indent to avoid noisy failures in CI or local pre-commit hooks.

-    - Identifies selectedCalendars …
+  - Identifies selectedCalendars …

1-1: Doc still warns only about Google expiration being a string

Line 1 mentions the googleChannelExpiration string pitfall but the newly introduced outlookSubscriptionExpiration will suffer from the same issue. Either extend the warning or, even better, consider switching both fields to DateTime in a follow-up migration to enable proper numeric comparisons in Prisma.

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

9-38: Guard against undefined calendarIds & tighten return type

config.calendarIds?.map returns undefined when calendarIds is absent, violating the declared ISettledResponse[] return type and possibly crashing consumers that spread or iterate over the result.

-function getEventsBatchMockResponse(config: IGetEventsBatchMockResponse): ISettledResponse[] {
-  return config.calendarIds?.map((calId, index) => ({
+function getEventsBatchMockResponse(
+  config: IGetEventsBatchMockResponse
+): ISettledResponse[] {
+  if (!config.calendarIds?.length) return [];
+  return config.calendarIds.map((calId, index) => ({
packages/app-store/office365calendar/lib/CalendarService.test.ts (1)

890-891: Complete the delegation credential error handling tests

These tests are marked as skipped but would be valuable for ensuring proper error handling in delegation scenarios.

Would you like me to help implement these delegation credential error handling tests?

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

472-476: Consider using native Date or Day.js .utc() for better performance

The code uses Day.js with timezone operations which can be slow in performance-critical code. Consider using .utc() or native Date methods.

  start: {
-   dateTime: dayjs(event.startTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
+   dateTime: dayjs.utc(event.startTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
    timeZone: event.organizer.timeZone,
  },
  end: {
-   dateTime: dayjs(event.endTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
+   dateTime: dayjs.utc(event.endTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
    timeZone: event.organizer.timeZone,
  },
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1d28215 and 8af0a3a.

📒 Files selected for processing (13)
  • .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.test.ts (1 hunks)
  • packages/app-store/office365calendar/lib/CalendarService.ts (7 hunks)
  • packages/app-store/office365calendar/lib/__mocks__/office365apis.ts (1 hunks)
  • packages/app-store/office365calendar/zod.ts (1 hunks)
  • packages/features/calendar-cache/CalendarCache.md (2 hunks)
  • packages/lib/server/repository/selectedCalendar.ts (5 hunks)
  • packages/prisma/migrations/20250502011606_add_outlook_subscription_id_field/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • turbo.json (1 hunks)
🧰 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/app-store/office365calendar/api/index.ts
  • apps/api/v1/test/lib/selected-calendars/_post.test.ts
  • packages/app-store/office365calendar/lib/__mocks__/office365apis.ts
  • packages/app-store/office365calendar/api/webhook.ts
  • packages/app-store/office365calendar/lib/CalendarService.test.ts
  • packages/app-store/office365calendar/zod.ts
  • packages/app-store/office365calendar/lib/CalendarService.ts
  • packages/lib/server/repository/selectedCalendar.ts
🧠 Learnings (13)
📓 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.
Learnt from: vijayraghav-io
PR: calcom/cal.com#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.
packages/app-store/office365calendar/api/index.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.

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

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

.env.example (2)

Learnt from: vijayraghav-io
PR: #21072
File: .env.example:354-357
Timestamp: 2025-07-18T08:49:18.779Z
Learning: For E2E integration tests that require real third-party service credentials (like Outlook calendar), it's acceptable to temporarily include actual test account credentials in .env.example during feature development and validation, provided there's a clear plan to replace them with placeholder values before final release. Test credentials should be for dedicated test tenants/accounts, not production systems.

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/features/calendar-cache/CalendarCache.md (3)

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.

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

packages/prisma/migrations/20250502011606_add_outlook_subscription_id_field/migration.sql (2)

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

packages/app-store/office365calendar/lib/__mocks__/office365apis.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/prisma/schema.prisma (2)

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

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

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: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:50-60
Timestamp: 2025-07-18T17:54:04.613Z
Learning: For Microsoft Graph webhook handlers, when dealing with internal configuration errors (like missing MICROSOFT_WEBHOOK_TOKEN), it's better to return 200 OK with errors tracked in the response body rather than throwing 5xx errors. This prevents retry storms from Microsoft Graph and maintains webhook subscription health, while still providing visibility through error logs and structured error responses.

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

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

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: vijayraghav-io
PR: #21072
File: .env.example:354-357
Timestamp: 2025-07-18T08:49:18.779Z
Learning: For E2E integration tests that require real third-party service credentials (like Outlook calendar), it's acceptable to temporarily include actual test account credentials in .env.example during feature development and validation, provided there's a clear plan to replace them with placeholder values before final release. Test credentials should be for dedicated test tenants/accounts, not production systems.

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

packages/app-store/office365calendar/zod.ts (2)

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: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:50-60
Timestamp: 2025-07-18T17:54:04.613Z
Learning: For Microsoft Graph webhook handlers, when dealing with internal configuration errors (like missing MICROSOFT_WEBHOOK_TOKEN), it's better to return 200 OK with errors tracked in the response body rather than throwing 5xx errors. This prevents retry storms from Microsoft Graph and maintains webhook subscription health, while still providing visibility through error logs and structured error responses.

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

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

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.

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

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

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.

🧬 Code Graph Analysis (2)
packages/app-store/office365calendar/lib/__mocks__/office365apis.ts (1)
packages/app-store/office365calendar/lib/CalendarService.ts (1)
  • ISettledResponse (43-51)
packages/lib/server/repository/selectedCalendar.ts (3)
packages/prisma/selects/credential.ts (1)
  • credentialForCalendarServiceSelect (3-17)
packages/platform/libraries/index.ts (1)
  • credentialForCalendarServiceSelect (97-97)
packages/types/Calendar.d.ts (1)
  • SelectedCalendarEventTypeIds (259-259)
🪛 markdownlint-cli2 (0.17.2)
packages/features/calendar-cache/CalendarCache.md

6-6: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


7-7: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


8-8: Unordered list indentation
Expected: 6; Actual: 8

(MD007, ul-indent)


9-9: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


10-10: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

⏰ 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). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (15)
apps/api/v1/test/lib/selected-calendars/_post.test.ts (1)

92-93: Mocks updated correctly for new Outlook fields – nice catch
The additional outlookSubscriptionExpiration and outlookSubscriptionId keys keep the test fixtures in sync with the schema change and avoid unexpected undefined issues in the future.

Also applies to: 139-140

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

3-3: Export looks fine
Adding the webhook re-export cleanly exposes the new handler without side effects.

turbo.json (1)

380-381: Add OUTLOOK_WEBHOOK_URL alongside the token
Office365CalendarService builds the subscription callback URL from OUTLOOK_WEBHOOK_URL when present (mirrors the Google path). Only OUTLOOK_WEBHOOK_TOKEN was added to globalEnv; forgetting the URL will cause mismatches in CI/CD where NEXT_PUBLIC_WEBAPP_URL differs from the production domain.

   "OUTLOOK_WEBHOOK_TOKEN",
+  "OUTLOOK_WEBHOOK_URL",
packages/app-store/office365calendar/lib/__mocks__/office365apis.ts (1)

1-1: Import path may break in Jest without TS path-alias

import type { ISettledResponse } from "office365calendar/lib/CalendarService";

If tests run with plain Node resolution (no TS-path mapping), this absolute path can fail. Consider using a relative path (../CalendarService) or ensure moduleNameMapper / paths mapping exists in jest.config.ts.

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

25-39: LGTM! Proper webhook validation handling

The validation token handling correctly implements Microsoft's webhook verification requirements.


50-54: Good optimization for handling duplicate events

Using uniqueBy to process each subscription only once is efficient given Microsoft can send multiple events for the same subscription.


55-94: Well-structured event processing with proper error handling

The implementation correctly:

  • Validates client state for security
  • Returns 200 OK with error details to prevent Microsoft Graph retry storms
  • Fetches and updates calendar cache appropriately
packages/app-store/office365calendar/zod.ts (1)

17-34: Well-structured schema matching Microsoft's specifications

The schema correctly implements Microsoft's change notification structure with proper field types and optionality.

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

304-384: Comprehensive cache testing with proper grouping validation

The test correctly validates that calendars are grouped by eventTypeId for efficient batch fetching, which is crucial for performance.

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

31-36: Consistent integration with existing Google Calendar pattern

The outlookSubscriptionId filter follows the same pattern as googleChannelId, maintaining consistency.


174-195: Good integration support for both calendar types

The OR clause properly handles both Google and Outlook calendars with appropriate expiration checks.


316-334: Method follows consistent pattern with Google Calendar

The implementation correctly mirrors findFirstByGoogleChannelId with appropriate naming and structure.

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

316-356: Well-structured batch API implementation

The refactoring properly handles:

  • Batch requests for multiple calendars
  • Retry-after headers for rate limiting
  • Pagination through nextLink responses

758-758: Method name uses singular form correctly

Despite the past comment, the method name startWatchingCalendarInOutlook is correct as it operates on a single calendar.


867-942: Robust subscription lifecycle management

The implementation correctly handles:

  • Shared subscriptions between event types
  • Cache cleanup and repopulation
  • Partial unwatching scenarios

Comment on lines +417 to +445
static async updateManyForEventTypeIds({
data,
eventTypeIds,
}: {
data: Prisma.SelectedCalendarUncheckedCreateInput;
eventTypeIds: SelectedCalendarEventTypeIds;
}) {
const userId = data.userId;
return await Promise.allSettled(
eventTypeIds.map((eventTypeId) => {
SelectedCalendarRepository.update({
where: {
userId: data.userId,
integration: data.integration,
externalId: data.externalId,
eventTypeId: data.eventTypeId || null,
},
data: {
...data,
eventTypeId,
userId,
integration: data.integration,
externalId: data.externalId,
credentialId: data.credentialId,
},
});
})
);
}
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

Critical bug in eventTypeId assignment

The method uses data.eventTypeId on line 432 instead of the mapped eventTypeId, which will cause incorrect updates.

  return await Promise.allSettled(
    eventTypeIds.map((eventTypeId) => {
      SelectedCalendarRepository.update({
        where: {
          userId: data.userId,
          integration: data.integration,
          externalId: data.externalId,
-         eventTypeId: data.eventTypeId || null,
+         eventTypeId: eventTypeId || null,
        },
        data: {
          ...data,
          eventTypeId,
          userId,
          integration: data.integration,
          externalId: data.externalId,
          credentialId: data.credentialId,
        },
      });
    })
  );
📝 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
static async updateManyForEventTypeIds({
data,
eventTypeIds,
}: {
data: Prisma.SelectedCalendarUncheckedCreateInput;
eventTypeIds: SelectedCalendarEventTypeIds;
}) {
const userId = data.userId;
return await Promise.allSettled(
eventTypeIds.map((eventTypeId) => {
SelectedCalendarRepository.update({
where: {
userId: data.userId,
integration: data.integration,
externalId: data.externalId,
eventTypeId: data.eventTypeId || null,
},
data: {
...data,
eventTypeId,
userId,
integration: data.integration,
externalId: data.externalId,
credentialId: data.credentialId,
},
});
})
);
}
static async updateManyForEventTypeIds({
data,
eventTypeIds,
}: {
data: Prisma.SelectedCalendarUncheckedCreateInput;
eventTypeIds: SelectedCalendarEventTypeIds;
}) {
const userId = data.userId;
return await Promise.allSettled(
eventTypeIds.map((eventTypeId) => {
return SelectedCalendarRepository.update({
where: {
userId: data.userId,
integration: data.integration,
externalId: data.externalId,
- eventTypeId: data.eventTypeId || null,
+ eventTypeId: eventTypeId || null,
},
data: {
...data,
eventTypeId,
userId,
integration: data.integration,
externalId: data.externalId,
credentialId: data.credentialId,
},
});
})
);
}
🤖 Prompt for AI Agents
In packages/lib/server/repository/selectedCalendar.ts lines 417 to 445, the
updateManyForEventTypeIds method incorrectly uses data.eventTypeId in the where
clause instead of the current eventTypeId from the map iteration. To fix this,
replace data.eventTypeId with the eventTypeId variable in the where condition to
ensure the correct eventTypeId is used for each update call.

Comment on lines +4 to +12
- A unique constraint covering the columns `[outlookSubscriptionId,eventTypeId]` on the table `SelectedCalendar` will be added. If there are existing duplicate values, this will fail.

*/
-- AlterTable
ALTER TABLE "SelectedCalendar" ADD COLUMN "outlookSubscriptionExpiration" TEXT,
ADD COLUMN "outlookSubscriptionId" TEXT;

-- CreateIndex
CREATE UNIQUE INDEX "SelectedCalendar_outlookSubscriptionId_eventTypeId_key" ON "SelectedCalendar"("outlookSubscriptionId", "eventTypeId");
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

Unique index contradicts the intended subscription-sharing model
Historical design (see learning notes) deliberately re-uses the same Outlook subscription across many eventTypeIds and even allows multiple SelectedCalendar rows for a single (subscriptionId,eventTypeId) pair during retries. The composite unique constraint will block that pattern and may also fail the migration on existing data.

--- CreateIndex
-CREATE UNIQUE INDEX "SelectedCalendar_outlookSubscriptionId_eventTypeId_key"
-  ON "SelectedCalendar"("outlookSubscriptionId", "eventTypeId");
+-- (drop the unique index – leave the columns without a uniqueness constraint)

Consider adding a non-unique index instead if query performance is required.

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

🤖 Prompt for AI Agents
In
packages/prisma/migrations/20250502011606_add_outlook_subscription_id_field/migration.sql
around lines 4 to 12, the unique index on (outlookSubscriptionId, eventTypeId)
conflicts with the intended design that allows multiple rows with the same
subscriptionId and eventTypeId. To fix this, change the unique index to a
non-unique index by removing the UNIQUE keyword so it does not enforce
uniqueness but still improves query performance.

Comment on lines +868 to +871
// Used to identify a watched calendar in Outlook
outlookSubscriptionId String?
outlookSubscriptionExpiration String?

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Store expiration as DateTime, not String

outlookSubscriptionExpiration mirrors the Google field and is typed as String.
This prevents using efficient range queries (<, >, between) and forces string-to-date casting in application code.

-  outlookSubscriptionExpiration String?
+  ///  RFC3339 timestamp e.g. "2025-08-30T23:59:59Z"
+  outlookSubscriptionExpiration DateTime?

If changing the type is too risky for this PR, at least document the ISO format expectation and add a TODO for a migration.

📝 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
// Used to identify a watched calendar in Outlook
outlookSubscriptionId String?
outlookSubscriptionExpiration String?
// Used to identify a watched calendar in Outlook
outlookSubscriptionId String?
/// RFC3339 timestamp e.g. "2025-08-30T23:59:59Z"
outlookSubscriptionExpiration DateTime?
🤖 Prompt for AI Agents
In packages/prisma/schema.prisma at lines 868 to 871, the field
outlookSubscriptionExpiration is currently typed as String, which limits
efficient date queries and requires casting in application code. Change the type
of outlookSubscriptionExpiration from String to DateTime to enable native date
operations. If this change is too risky for now, add a comment documenting that
the field expects an ISO date string and include a TODO note to migrate this
field to DateTime in the future.

// Think about introducing a generated unique key ${userId}_${integration}_${externalId}_${eventTypeId}
@@unique([userId, integration, externalId, eventTypeId])
@@unique([googleChannelId, eventTypeId])
@@unique([outlookSubscriptionId, eventTypeId])
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

Remove the unique constraint on (outlookSubscriptionId, eventTypeId)

The Outlook integration intentionally re-uses one subscription across many event types (see past discussions & retrieved learnings).
@@unique([outlookSubscriptionId, eventTypeId]) will break that pattern by disallowing duplicates and cause Unique constraint failed errors when multiple event types share the same subscription.

-  @@unique([outlookSubscriptionId, eventTypeId])
+  // DO NOT make this unique – one subscription can belong to many event types.
+  @@index([outlookSubscriptionId, eventTypeId])
🤖 Prompt for AI Agents
In packages/prisma/schema.prisma at line 892, remove the unique constraint
declaration @@unique([outlookSubscriptionId, eventTypeId]) because it prevents
reusing one subscription across multiple event types. Deleting this constraint
will allow multiple event types to share the same outlookSubscriptionId without
causing unique constraint errors.

@keithwillcode
Copy link
Contributor

Please see comment here #21050 (comment)

@keithwillcode
Copy link
Contributor

/tip 100 @ouwargui

@algora-pbc
Copy link

algora-pbc bot commented Aug 27, 2025

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

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 Stale 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

3 participants