Skip to content

feat: implement Office365 calendar cache and subscription services #22967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: devin/1753300938-calendar-cache-sql-system
Choose a base branch
from

Conversation

zomars
Copy link
Member

@zomars zomars commented Aug 7, 2025

Refactor CalendarCacheSqlService provider logic using factory pattern

Summary

This PR refactors the CalendarCacheSqlService to improve separation of concerns by extracting provider-specific webhook processing logic into dedicated services. The main CalendarCacheSqlService previously contained mixed Google Calendar and Office365 logic in a single class, making it difficult to maintain and extend.

Key Changes:

  • Created CalendarWebhookServiceFactory for dynamic service instantiation based on credential type
  • Extracted Google Calendar webhook processing into GoogleCalendarWebhookService
  • Extracted Office365 Calendar webhook processing into Office365CalendarWebhookService
  • Defined ICalendarWebhookService interface for consistency across providers
  • Refactored main service to act as orchestrator that delegates to provider-specific services

The refactoring maintains all existing functionality while improving code organization and making it easier to add new calendar providers.

Review & Testing Checklist for Human

⚠️ HIGH PRIORITY - This is a significant architectural change that requires thorough testing:

  • Test Google Calendar webhook processing end-to-end - Verify that Google webhook events are correctly processed and events are saved to database with proper field mappings
  • Test Office365 Calendar webhook processing end-to-end - Verify that Office365 webhook events are correctly processed, especially the event field mappings (subjectsummary, @odata.etagetag, etc.)
  • Verify factory pattern instantiation - Confirm that CalendarWebhookServiceFactory.createService() correctly instantiates the right service for both google_calendar and office365_calendar credential types
  • Test subscription lookup logic - Ensure both findByChannelId (Google) and findByOffice365SubscriptionId (Office365) work correctly in the refactored processWebhookEvents method
  • Compare event parsing output - Manually verify that the extracted parsing methods (parseCalendarEvents and parseOffice365Events) produce identical database records to the original implementation

Diagram

%%{ init : { "theme" : "default" }}%%
graph TB
    CalendarCacheSqlService["CalendarCacheSqlService.ts<br/>(Major Refactor)"]:::major-edit
    WebhookServiceFactory["CalendarWebhookServiceFactory.ts<br/>(New Factory)"]:::major-edit
    GoogleWebhookService["googlecalendar/lib/<br/>CalendarWebhookService.ts<br/>(New Service)"]:::major-edit
    Office365WebhookService["office365calendar/lib/<br/>CalendarWebhookService.ts<br/>(New Service)"]:::major-edit
    IWebhookService["ICalendarWebhookService<br/>(New Interface)"]:::major-edit
    SubscriptionRepo["CalendarSubscription<br/>Repository"]:::context
    EventRepo["CalendarEvent<br/>Repository"]:::context

    CalendarCacheSqlService -->|"delegates to"| WebhookServiceFactory
    WebhookServiceFactory -->|"creates Google service"| GoogleWebhookService
    WebhookServiceFactory -->|"creates Office365 service"| Office365WebhookService
    GoogleWebhookService -.->|"implements"| IWebhookService
    Office365WebhookService -.->|"implements"| IWebhookService
    GoogleWebhookService -->|"uses"| SubscriptionRepo
    GoogleWebhookService -->|"uses"| EventRepo
    Office365WebhookService -->|"uses"| SubscriptionRepo
    Office365WebhookService -->|"uses"| EventRepo

    subgraph Legend
        L1[Major Edit]:::major-edit
        L2[Minor Edit]:::minor-edit
        L3[Context/No Edit]:::context
    end

    classDef major-edit fill:#90EE90
    classDef minor-edit fill:#87CEEB
    classDef context fill:#FFFFFF
Loading

Notes

  • Risk Level: HIGH - This is a significant architectural refactoring that moves complex webhook processing logic across multiple files
  • Testing Gap: The refactored webhook services could not be tested end-to-end with real calendar provider webhooks during development
  • Event Parsing Critical: The event parsing logic for both providers is complex and handles different API response formats - any errors here could result in data loss or corruption
  • Dynamic Imports: The factory uses dynamic imports that could fail at runtime if module paths are incorrect

Session Details:

devin-ai-integration bot and others added 2 commits August 7, 2025 21:25
- Add Office365-specific fields to CalendarSubscription model
- Create Office365CalendarSubscriptionService following Google patterns
- Add Office365CalendarWebhookService for webhook processing
- Update CalendarCacheSqlService to handle Office365 events
- Add webhook API endpoint for Office365 calendar notifications
- Update repository interfaces to support both Google and Office365
- Add required environment variables to turbo.json
- Fix TypeScript warnings and improve type safety

Co-Authored-By: zomars@cal.com <zomars@me.com>
- Fix subscription parameter types in processGoogleWebhookEvents and processOffice365WebhookEvents
- Add proper type annotations for selectedCalendar relation
- Ensure compatibility with repository return types

Co-Authored-By: zomars@cal.com <zomars@me.com>
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Contributor

coderabbitai bot commented Aug 7, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/outlook-calendar-cache-1754599639

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • 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.
  • 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.

Support

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@github-actions github-actions bot added the ❗️ migrations contains migration files label Aug 7, 2025
@keithwillcode keithwillcode added core area: core, team members only foundation labels Aug 7, 2025
Copy link
Member Author

Choose a reason for hiding this comment

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

DevinAIm where is this file being used?

Copy link
Contributor

The is used through a dispatcher pattern in the main calendar subscription system:

Usage Points:

1. Main Dispatcher Service:

  • File:
  • Lines 58-62 (watchCalendar method)
  • Lines 88-92 (unwatchCalendar method)

2. Dynamic Import Pattern:
When the system detects an Office365 credential (credential.type === "office365_calendar"), it dynamically imports this service:

const { CalendarSubscriptionService } = await import(
  "@calcom/app-store/office365calendar/lib/CalendarSubscriptionService"
);
const subscriptionService = new CalendarSubscriptionService(credentialWithTenantId);
return await subscriptionService.watchCalendar(calendarId);

3. Integration Flow:

  1. Cron Job (apps/web/app/api/cron/calendar-subscriptions/route.ts) triggers subscription management
  2. Main Dispatcher (CalendarSubscriptionService) detects credential type
  3. Office365 Service (this file) handles Microsoft Graph API subscription creation
  4. Webhook Endpoint (/api/webhook/office365-calendar-sql) processes incoming events

This follows the same pattern as Google Calendar's implementation, keeping provider-specific code isolated in their respective app-store directories while using a common interface.

- Create CalendarSubscriptionServiceFactory with proper interface segregation
- Extract ICalendarSubscriptionService interface for provider implementations
- Remove provider-specific logic from main CalendarSubscriptionService
- Standardize unwatchCalendar method signatures across providers
- Improve separation of concerns following existing getCalendar patterns
- Maintain backward compatibility with existing integrations

Co-Authored-By: zomars@cal.com <zomars@me.com>
Copy link

vercel bot commented Aug 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
cal ⬜️ Ignored (Inspect) Aug 8, 2025 3:10pm
cal-eu ⬜️ Ignored (Inspect) Aug 8, 2025 3:10pm

…pattern

- Create CalendarWebhookServiceFactory for dynamic service instantiation
- Move Google Calendar webhook logic to GoogleCalendarWebhookService
- Move Office365 Calendar webhook logic to Office365CalendarWebhookService
- Remove provider-specific methods from CalendarCacheSqlService
- Implement ICalendarWebhookService interface for consistency
- Improve separation of concerns and maintainability

Co-Authored-By: zomars@cal.com <zomars@me.com>
- Remove unused logger imports from webhook services
- Fix non-null assertion in CalendarCacheSqlService
- Improve type safety in CalendarWebhookServiceFactory
- Update interface definitions to use proper types

Co-Authored-By: zomars@cal.com <zomars@me.com>
- Update ICalendarWebhookService interface to use proper types
- Replace any with CredentialPayload in Google and Office365 webhook services
- Resolves remaining lint warnings for Codacy Static Code Analysis

Co-Authored-By: zomars@cal.com <zomars@me.com>
async watchCalendar(
calendarId: string,
credential: CredentialForCalendarService
): Promise<GoogleChannelProps | undefined> {
): Promise<GoogleChannelProps | Office365SubscriptionProps | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird to support either type of props here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core area: core, team members only foundation ❗️ migrations contains migration files
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants