Skip to content

Conversation

@edulelis
Copy link
Collaborator

@edulelis edulelis commented Oct 9, 2025

Summary by CodeRabbit

  • New Features

    • Microsoft/Outlook calendar integration (OAuth, token refresh, auto calendar sync).
    • Unified connect UI for Google and Microsoft with provider-specific names/icons and “Connecting…” feedback.
    • Unified calendar availability across all connected calendars.
  • Style

    • Calendars page made responsive for small and large screens.
    • Empty-state text updated to invite connecting Google or Microsoft calendars.
  • Documentation

    • README updated with Microsoft Calendar setup and guidance.
  • Bug Fixes

    • Improved OAuth callback and error/redirect handling.

@vercel
Copy link

vercel bot commented Oct 9, 2025

@edulelis is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds Microsoft/Outlook calendar integration, provider-aware calendar UI and ConnectCalendar client, centralized OAuth callback handling and helpers, new provider implementations and availability providers for Google/Microsoft, unified availability aggregator, Outlook OAuth routes and client utilities, README updates, and a version bump.

Changes

Cohort / File(s) Summary
Provider-aware UI & pages
apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx, apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx, apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx, apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
CalendarConnectionCard derives provider info (name/icon/alt) from connection.provider. Empty-state copy now mentions Google or Microsoft. Added ConnectCalendar client with Google and Microsoft buttons, per-provider loading and auth-url→redirect flow. Page layout made responsive and now uses ConnectCalendar.
Removed component
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendarButton.tsx
Deleted the Google-only ConnectCalendarButton client component.
Unified callback & helpers
apps/web/utils/calendar/handle-calendar-callback.ts, apps/web/utils/calendar/oauth-callback-helpers.ts, apps/web/utils/calendar/oauth-types.ts
New centralized handler and helpers for calendar OAuth callbacks (validation, state parsing, ownership checks, connection creation, redirects). New types and RedirectError for redirect-based error handling.
Calendar providers
apps/web/utils/calendar/providers/google.ts, apps/web/utils/calendar/providers/microsoft.ts
Added googleCalendarProvider and microsoftCalendarProvider implementing CalendarOAuthProvider (exchangeCodeForTokens, syncCalendars) and using calendar client helpers and Prisma upserts.
Availability providers & unified availability
apps/web/utils/calendar/providers/google-availability.ts, apps/web/utils/calendar/providers/microsoft-availability.ts, apps/web/utils/calendar/unified-availability.ts, apps/web/utils/calendar/availability-types.ts
Added Google and Microsoft availability providers implementing CalendarAvailabilityProvider, new BusyPeriod type and interface, and getUnifiedCalendarAvailability which aggregates busy periods across enabled connections. Removed old single-provider availability module.
Outlook OAuth routes & client
apps/web/app/api/outlook/calendar/auth-url/route.ts, apps/web/app/api/outlook/calendar/callback/route.ts, apps/web/utils/outlook/calendar-client.ts, apps/web/utils/outlook/scopes.ts
New GET route to return Microsoft OAuth URL (sets state cookie) and callback route delegating to centralized handler. New Outlook client with token refresh/persistence, calendar fetch, and CALENDAR_SCOPES.
Google callback refactor
apps/web/app/api/google/calendar/callback/route.ts
Replaced inline Google callback implementation with delegation to handleCalendarCallback(..., googleCalendarProvider, logger).
AI/calendar availability integration
apps/web/utils/ai/calendar/availability.ts
Replaced multi-connection per-connection availability calls with unified availability call; updated tool description and error handling.
Docs & meta
README.md, version.txt
README updated with Microsoft Calendar setup and CONTRIBUTING/ARCHITECTURE links; version bumped to v2.18.0.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Calendars UI
  participant AuthAPI as /api/outlook/calendar/auth-url
  participant MS_OAuth as Microsoft OAuth
  participant Callback as /api/.../calendar/callback
  participant Provider as microsoftCalendarProvider
  participant Graph as Microsoft Graph
  participant DB as Database

  User->>UI: Click "Connect Microsoft"
  UI->>AuthAPI: GET auth-url (with account context)
  AuthAPI->>AuthAPI: Create OAuth state (emailAccountId,type=calendar)
  AuthAPI-->>UI: { url } + Set CALENDAR_STATE cookie
  UI-->>User: Redirect to Microsoft OAuth
  User->>MS_OAuth: Authorize app
  MS_OAuth->>Callback: Redirect with code & state
  Callback->>Callback: validate state & ownership
  Callback->>Provider: exchangeCodeForTokens(code)
  Provider->>Graph: Token exchange & profile fetch
  Provider-->>Callback: tokens + email
  Callback->>DB: Create/Update calendar connection (tokens, expiresAt)
  Callback->>Provider: syncCalendars(connectionId, tokens..., emailAccountId)
  Provider->>Graph: Fetch calendars
  Provider->>DB: Upsert calendars
  Callback-->>User: Redirect to /calendars (success or error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Release calendar feature #835 — Overlaps calendar UI and page changes including CalendarConnectionCard and calendars page.
  • Add calendar connections #775 — Implements calendar integration pieces (OAuth routes, providers, UI) that closely mirror these changes.
  • Outlook support #537 — Adds Outlook/Microsoft provider support and related backend/frontend integration, strongly related to Outlook additions here.

Suggested reviewers

  • anakarentorosserrano-star

Poem

hop-hop, two portals open wide,
Google nods and Microsoft strides.
I fetch a link, set state, then run —
tokens bloom, calendars synced in sun.
rabbit cheers: connections done! 🐇📅

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Add Outlook Calendar integration" accurately describes the primary objective of the changeset. The summary shows extensive changes that add Microsoft/Outlook calendar support throughout the codebase, including new OAuth endpoints (outlook/calendar/auth-url, outlook/calendar/callback), a new calendar client utility (outlook/calendar-client.ts), provider implementations, and UI components to support connecting Outlook calendars. While the PR also refactors existing Google Calendar code to use a provider pattern and adds unified availability utilities, the main new feature being introduced is Outlook Calendar integration, which the title clearly and specifically captures. The title is concise, non-misleading, and would allow teammates scanning history to understand the primary change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 115c390 and 7b84ffb.

⛔ Files ignored due to path filters (1)
  • apps/web/public/images/product/outlook-calendar.svg is excluded by !**/*.svg
📒 Files selected for processing (9)
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx (3 hunks)
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendarButton.tsx (0 hunks)
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx (1 hunks)
  • apps/web/app/api/outlook/calendar/auth-url/route.ts (1 hunks)
  • apps/web/app/api/outlook/calendar/callback/route.ts (1 hunks)
  • apps/web/utils/outlook/calendar-client.ts (1 hunks)
  • apps/web/utils/outlook/scopes.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendarButton.tsx
🧰 Additional context used
📓 Path-based instructions (20)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/utils/outlook/calendar-client.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/utils/outlook/calendar-client.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/outlook/scopes.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
  • apps/web/utils/outlook/calendar-client.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
  • apps/web/app/api/outlook/calendar/callback/route.ts
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
apps/web/app/(app)/*/**

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
apps/web/app/(app)/*/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
apps/web/app/(app)/*/**/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnections.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
  • apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/api/outlook/calendar/callback/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/api/outlook/calendar/callback/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/outlook/calendar/auth-url/route.ts
  • apps/web/app/api/outlook/calendar/callback/route.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/app/api/**/route.ts : Wrap all GET API route handlers with `withAuth` or `withEmailAccount` middleware for authentication and authorization.

Applied to files:

  • apps/web/app/api/outlook/calendar/auth-url/route.ts
🧬 Code graph analysis (5)
apps/web/app/api/outlook/calendar/auth-url/route.ts (5)
apps/web/utils/oauth/state.ts (2)
  • generateOAuthState (10-18)
  • oauthStateCookieOptions (34-40)
apps/web/utils/outlook/calendar-client.ts (1)
  • getCalendarOAuth2Url (25-42)
apps/web/app/api/outlook/calendar/callback/route.ts (1)
  • GET (16-176)
apps/web/utils/middleware.ts (1)
  • withEmailAccount (293-298)
apps/web/utils/calendar/constants.ts (1)
  • CALENDAR_STATE_COOKIE_NAME (1-1)
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (3)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (72-82)
apps/web/utils/fetch.ts (1)
  • fetchWithAccount (7-25)
apps/web/app/api/outlook/calendar/auth-url/route.ts (1)
  • GetCalendarAuthUrlResponse (10-10)
apps/web/app/(app)/[emailAccountId]/calendars/page.tsx (3)
apps/web/components/PageWrapper.tsx (1)
  • PageWrapper (3-20)
apps/web/components/PageHeader.tsx (1)
  • PageHeader (14-34)
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (1)
  • ConnectCalendar (11-101)
apps/web/app/api/outlook/calendar/callback/route.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/constants.ts (1)
  • CALENDAR_STATE_COOKIE_NAME (1-1)
apps/web/utils/oauth/state.ts (1)
  • parseOAuthState (25-29)
apps/web/utils/auth.ts (1)
  • auth (485-486)
apps/web/utils/outlook/calendar-client.ts (2)
  • getCalendarClientWithRefresh (44-131)
  • fetchMicrosoftCalendars (133-149)
apps/web/utils/outlook/calendar-client.ts (5)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-242)
apps/web/utils/outlook/scopes.ts (1)
  • CALENDAR_SCOPES (20-28)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
⏰ 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). (3)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security

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.

1 issue found across 10 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/utils/outlook/calendar-client.ts">

<violation number="1" location="apps/web/utils/outlook/calendar-client.ts:58">
Only return a client when a valid access token exists; otherwise fall back to the refresh flow to avoid initializing Graph with an empty bearer and causing downstream 401s.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (1)

7-7: Import the Outlook auth URL response type.

This component now calls /api/outlook/calendar/auth-url, but still reuses the Google route’s response type. Pull the type from the Outlook route (or a shared module) so the typing tracks the correct contract and we don’t silently miss future divergences between providers.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b84ffb and ec6ab2f.

📒 Files selected for processing (2)
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (1 hunks)
  • apps/web/utils/outlook/calendar-client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (17)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/outlook/calendar-client.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/outlook/calendar-client.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/outlook/calendar-client.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/outlook/calendar-client.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/outlook/calendar-client.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/outlook/calendar-client.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/outlook/calendar-client.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/outlook/calendar-client.ts
  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/app/(app)/*/**

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/app/(app)/*/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/app/(app)/*/**/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
🧬 Code graph analysis (2)
apps/web/utils/outlook/calendar-client.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-242)
apps/web/utils/outlook/scopes.ts (1)
  • CALENDAR_SCOPES (20-28)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (4)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (72-82)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/fetch.ts (1)
  • fetchWithAccount (7-25)
apps/web/app/api/outlook/calendar/auth-url/route.ts (1)
  • GetCalendarAuthUrlResponse (10-10)
⏰ 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: Static Code Analysis Js
  • GitHub Check: Jit Security

@edulelis edulelis changed the title WIP: Add Outlook Calendar integration Add Outlook Calendar integration Oct 16, 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: 1

🧹 Nitpick comments (3)
apps/web/utils/outlook/calendar-client.ts (3)

44-56: Consider validating for empty refresh token.

An empty string refreshToken would pass the truthy check on line 55 but fail during the token refresh API call. While this is a minor edge case, validating for a non-empty string would provide clearer error messaging upfront.

Apply this diff to validate for non-empty strings:

-  if (!refreshToken) throw new SafeError("No refresh token");
+  if (!refreshToken || refreshToken.trim() === "") {
+    throw new SafeError("No refresh token");
+  }

63-90: Consider adding type validation for the token response.

The tokens object from the Microsoft endpoint is untyped (any). Adding Zod validation would ensure the response contains expected fields (access_token, refresh_token, expires_in) and provide better error messages if the API response structure changes.

Example Zod schema:

import { z } from "zod";

const TokenResponseSchema = z.object({
  access_token: z.string(),
  refresh_token: z.string().optional(),
  expires_in: z.union([z.string(), z.number()]),
  error: z.string().optional(),
  error_description: z.string().optional(),
});

Then validate after line 86:

const tokens = TokenResponseSchema.parse(await response.json());

153-188: Consider the silent return behavior.

The function silently returns when access_token is missing (lines 164-169), logging a warning. While this provides graceful handling, it means the caller has no indication that the save operation was skipped. This is acceptable for an internal function, but consider whether the caller should be notified (e.g., via a return boolean) if this pattern is used elsewhere.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec6ab2f and d2665f9.

📒 Files selected for processing (1)
  • apps/web/utils/outlook/calendar-client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/outlook/calendar-client.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/outlook/calendar-client.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/outlook/calendar-client.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/outlook/calendar-client.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/outlook/calendar-client.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/outlook/calendar-client.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/outlook/calendar-client.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/outlook/calendar-client.ts
🧬 Code graph analysis (1)
apps/web/utils/outlook/calendar-client.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-242)
apps/web/utils/outlook/scopes.ts (1)
  • CALENDAR_SCOPES (20-28)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
⏰ 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). (3)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
🔇 Additional comments (6)
apps/web/utils/outlook/calendar-client.ts (6)

1-11: LGTM! Proper imports and logger initialization.

The imports follow the path alias convention, and the scoped logger is correctly initialized at the top of the file as per coding guidelines.


13-23: LGTM! Clean authentication provider implementation.

The CalendarAuthProvider correctly implements the AuthenticationProvider interface from the Microsoft Graph client library.


25-42: LGTM! Correct OAuth2 URL generation.

The function properly validates the client ID, constructs the authorization URL with all required parameters including state for CSRF protection, and uses the appropriate scopes.


58-61: LGTM! Token validation correctly addressed.

The condition now properly checks for accessToken presence in addition to expiry validation, ensuring we only use the cached token when it's both valid and present. This addresses the previous review concern.


118-132: LGTM! Appropriate error handling.

The error handling properly distinguishes invalid_grant errors (which indicate the refresh token is no longer valid) from other failures, and logs them appropriately before re-throwing.


135-151: LGTM! Clean calendar fetch implementation.

The function properly queries the Graph API, handles the response, and provides appropriate error handling with logging.

Comment on lines +103 to +108
tokens: {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
expires_at: Math.floor(
Date.now() / 1000 + Number(tokens.expires_in ?? 0),
),
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 | 🟠 Major

Reconsider the fallback value for missing expires_in.

While the Number() coercion correctly addresses the string-to-number issue from the previous review, the fallback to 0 when expires_in is missing creates a token that's immediately expired. This could cause continuous refresh attempts or immediate failures on the next calendar operation.

Microsoft's token endpoint typically provides expires_in (usually 3600 seconds). If it's missing, this indicates an API contract violation that should fail explicitly rather than silently creating an expired token.

Apply this diff to throw an error when expires_in is missing:

-          expires_at: Math.floor(
-            Date.now() / 1000 + Number(tokens.expires_in ?? 0),
-          ),
+          expires_at: tokens.expires_in
+            ? Math.floor(Date.now() / 1000 + Number(tokens.expires_in))
+            : null,

Alternatively, if you want to maintain a numeric expires_at:

+          if (!tokens.expires_in) {
+            throw new Error("Token response missing expires_in field");
+          }
           expires_at: Math.floor(
-            Date.now() / 1000 + Number(tokens.expires_in ?? 0),
+            Date.now() / 1000 + Number(tokens.expires_in),
           ),

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

🤖 Prompt for AI Agents
In apps/web/utils/outlook/calendar-client.ts around lines 103 to 108, the code
currently falls back to 0 when tokens.expires_in is missing, producing an
immediately expired token; instead, validate that tokens.expires_in exists and
is a valid number: if missing or not a finite number, throw an explicit error
(including context such as the token response) so the failure surfaces
immediately; otherwise compute expires_at using Number(tokens.expires_in) (or
parseInt) added to Date.now()/1000.

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.

No issues found across 2 files

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on November 30

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

…dling

Fix calendar OAuth callbacks to redirect to /{emailAccountId}/calendars
and standardize error handling between Google and Microsoft providers.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/app/api/outlook/calendar/callback/route.ts (1)

67-71: Good fix: redirect now scopes to emailAccountId.

This addresses the earlier bug report about missing account scoping in the redirect URL.

🧹 Nitpick comments (5)
apps/web/app/api/outlook/calendar/callback/route.ts (5)

27-31: Make cookie deletion deterministic by constructing the redirect response at return-time (avoid reusing headers from an earlier NextResponse).

Current pattern creates a NextResponse up-front, deletes the cookie on it, then reuses its headers for later redirects. Prefer creating a fresh NextResponse for each branch and deleting the cookie on that response to avoid subtle header/cookie propagation issues and to improve clarity.

Apply this refactor:

-  // We'll set the proper redirect URL after we decode the state and get emailAccountId
-  let redirectUrl = new URL("/calendars", request.nextUrl.origin);
-  const response = NextResponse.redirect(redirectUrl);
-
-  response.cookies.delete(CALENDAR_STATE_COOKIE_NAME);
+  // We'll set the proper redirect URL after we decode the state and get emailAccountId
+  let redirectUrl = new URL("/calendars", request.nextUrl.origin);
+
+  const redirectWithCookie = (url: URL) => {
+    const res = NextResponse.redirect(url);
+    res.cookies.delete(CALENDAR_STATE_COOKIE_NAME);
+    return res;
+  };
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-      return NextResponse.redirect(redirectUrl, { headers: response.headers });
+      return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
@@
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);

Also applies to: 33-37, 44-46, 54-55, 62-63, 78-79, 95-96, 157-158, 188-189, 192-193


100-115: Add timeouts to external fetch calls (token and profile).

Unbounded fetches can hang request threads. Add AbortSignal timeouts.

-    const tokenResponse = await fetch(
+    const tokenResponse = await fetch(
       "https://login.microsoftonline.com/common/oauth2/v2.0/token",
       {
         method: "POST",
         headers: {
           "Content-Type": "application/x-www-form-urlencoded",
         },
         body: new URLSearchParams({
           client_id: env.MICROSOFT_CLIENT_ID,
           client_secret: env.MICROSOFT_CLIENT_SECRET,
           code,
           grant_type: "authorization_code",
           redirect_uri: `${env.NEXT_PUBLIC_BASE_URL}/api/outlook/calendar/callback`,
         }),
+        // 10s timeout
+        signal: AbortSignal.timeout(10_000),
       },
     );
@@
-    const profileResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
+    const profileResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
       headers: {
         Authorization: `Bearer ${tokens.access_token}`,
       },
+      signal: AbortSignal.timeout(10_000),
     });

Also applies to: 126-131


174-180: Avoid immediate token refresh; pass fresh token expiry to the client.

You pass expiresAt: null, forcing an unnecessary refresh right after obtaining tokens.

-    await syncMicrosoftCalendars(
-      connection.id,
-      tokens.access_token,
-      tokens.refresh_token,
-      emailAccountId,
-    );
+    await syncMicrosoftCalendars(
+      connection.id,
+      tokens.access_token,
+      tokens.refresh_token,
+      emailAccountId,
+      tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : null,
+    );

And update the function signature:

-async function syncMicrosoftCalendars(
+async function syncMicrosoftCalendars(
   connectionId: string,
   accessToken: string,
   refreshToken: string,
-  emailAccountId: string,
+  emailAccountId: string,
+  expiresAtMs: number | null = null,
 ) {
@@
-    const calendarClient = await getCalendarClientWithRefresh({
-      accessToken,
-      refreshToken,
-      expiresAt: null,
-      emailAccountId,
-    });
+    const calendarClient = await getCalendarClientWithRefresh({
+      accessToken,
+      refreshToken,
+      expiresAt: expiresAtMs,
+      emailAccountId,
+    });

Also applies to: 196-208


210-236: Verify Graph Calendar fields: description/timeZone may be undefined.

Microsoft Graph Calendar typically exposes name, (hex) color, owner, etc. Confirm that description and timeZone exist on /me/calendars responses; otherwise map to supported fields or drop them.


174-180: Don’t block the callback on full calendar sync.

Running a full sync inline can slow redirects and increase error surface. Prefer deferring the sync (e.g., queue or Next.js unstable_after) and show a “syncing…” toast on the calendars page.

Also applies to: 196-245

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9769179 and 016ff9a.

📒 Files selected for processing (2)
  • apps/web/app/api/google/calendar/callback/route.ts (0 hunks)
  • apps/web/app/api/outlook/calendar/callback/route.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/app/api/google/calendar/callback/route.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/api/outlook/calendar/callback/route.ts
🧬 Code graph analysis (1)
apps/web/app/api/outlook/calendar/callback/route.ts (5)
apps/web/utils/middleware.ts (1)
  • withError (283-288)
apps/web/utils/calendar/constants.ts (1)
  • CALENDAR_STATE_COOKIE_NAME (1-1)
apps/web/utils/oauth/state.ts (1)
  • parseOAuthState (25-29)
apps/web/utils/auth.ts (1)
  • auth (499-500)
apps/web/utils/outlook/calendar-client.ts (2)
  • getCalendarClientWithRefresh (44-133)
  • fetchMicrosoftCalendars (135-151)
⏰ 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). (3)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (2)
apps/web/app/api/outlook/calendar/callback/route.ts (2)

81-88: Ensure the authorized Microsoft account matches the email account being connected.

Currently, any Microsoft identity can be connected to the provided emailAccountId after ownership check. Verify whether product requirements mandate matching the Microsoft email to the email account’s address; if so, enforce it and fail with a specific error.

If applicable, extend the emailAccount select to include its address and compare case-insensitively before creating the connection.

Also applies to: 136-141


17-20: Middleware choice: withError vs withAuth for GET route.

Guidelines say wrap GET routes with withAuth/withEmailAccount; here you use withError and perform custom auth. For OAuth callbacks this can be acceptable; please confirm this exception is intentional for consistency.

Also applies to: 73-80

Comment on lines 48 to 55
let decodedState: { emailAccountId: string; type: string; nonce: string };
try {
decodedState = parseOAuthState(storedState);
} catch (error) {
logger.error("Failed to decode state", { error });
redirectUrl.searchParams.set("error", "invalid_state_format");
return NextResponse.redirect(redirectUrl, { headers: response.headers });
}
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 | 🟠 Major

Validate OAuth state and params with Zod (MUST as per guidelines).

State shape and emailAccountId format aren’t validated. Add a Zod schema to enforce type: "calendar" and a UUID/ID format for emailAccountId before use.

Suggested change:

+import { z } from "zod";
@@
-  let decodedState: { emailAccountId: string; type: string; nonce: string };
+  const StateSchema = z.object({
+    nonce: z.string().min(8).max(128),
+    type: z.literal("calendar"),
+    emailAccountId: z.string().min(1).max(64), // replace with .uuid() if UUID
+  });
+  let decodedState: z.infer<typeof StateSchema>;
   try {
-    decodedState = parseOAuthState(storedState);
+    decodedState = StateSchema.parse(parseOAuthState(storedState));
   } catch (error) {
     logger.error("Failed to decode state", { error });
     redirectUrl.searchParams.set("error", "invalid_state_format");
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
+    return redirectWithCookie(redirectUrl);
   }
@@
-  if (decodedState.type !== "calendar") {
-    logger.error("Invalid state type for calendar callback", {
-      type: decodedState.type,
-    });
-    redirectUrl.searchParams.set("error", "invalid_state_type");
-    return NextResponse.redirect(redirectUrl, { headers: response.headers });
-  }
+  // type already validated by Zod

Also consider validating code length > 10 before use.

Also applies to: 57-66

🤖 Prompt for AI Agents
In apps/web/app/api/outlook/calendar/callback/route.ts around lines 48-55 (and
also apply same validation to lines 57-66), add a Zod schema for the OAuth state
such as an object with emailAccountId (validate as UUID or the app's ID
pattern), type (literal "calendar") and nonce (string), then replace the untyped
parseOAuthState usage with schema.safeParse; on failure log the validation error
and redirect with error=invalid_state_format. Also validate the incoming `code`
param (e.g., ensure it exists and length > 10) before using it; if invalid, log
and redirect with an appropriate error query param. Ensure all early returns
preserve response.headers like the existing redirects.

@elie222
Copy link
Owner

elie222 commented Oct 22, 2025

Hey, this isn't integrated into the drafting system. The point of adding the Goop Outlook calendar is so that the AI can then use it to draft responses.

Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

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

❌ Jit has detected 1 important finding in this PR that you should review.
The finding is detailed below as a comment.
It’s highly recommended that you fix this security issue before merge.

const storedState = request.cookies.get(CALENDAR_STATE_COOKIE_NAME)?.value;

const redirectUrl = new URL("/calendars", request.nextUrl.origin);
const response = NextResponse.redirect(redirectUrl);
Copy link

Choose a reason for hiding this comment

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

Security control: Static Code Analysis Js

Open Redirect Vulnerability In Express Redirect() Method

Untrusted user input in redirect() can result in Open Redirect vulnerability.

Severity: HIGH


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "Open Redirect Vulnerability in Express redirect() Method" in apps/web/utils/calendar/oauth-callback-helpers.ts; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command

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

🧹 Nitpick comments (8)
apps/web/app/api/google/calendar/callback/route.ts (1)

3-10: Thin wrapper LGTM; export a response type for client usage.

Delegating to handleCalendarCallback is clean and matches middleware guidance. Add an explicit exported response type for type‑safe client imports.

+// Optional: export response type for clients
+export type GetGoogleCalendarCallbackResponse = Awaited<ReturnType<typeof GET>>;
apps/web/utils/calendar/handle-calendar-callback.ts (1)

102-113: Wire calendars to the drafting system post‑connect.

After successful connection, enqueue an events sync and update the drafting context (e.g., availability cache) so the AI can use Outlook/Google data when drafting.

  • Publish a background job: sync last 60 days and next 60 days of events for the connection.
  • Update the drafting pipeline to read from a provider‑agnostic Events table and expose “getAvailability(emailAccountId, window)” to the composer.
  • Emit a “calendar_connected” domain event for downstream consumers (drafting indexer).

I can sketch the job interface and the context fetch adapter if helpful.

apps/web/utils/calendar/providers/google.ts (1)

64-87: Upsert looks solid; consider persisting color/accessRole for UX.

Optional: store Google calendar color and accessRole to improve selection UI and permissions handling later.

apps/web/utils/calendar/providers/microsoft.ts (1)

21-36: Network calls lack timeouts; add AbortController for resilience.

Production OAuth/Graph calls should have timeouts/retries to avoid hanging requests.

-    const tokenResponse = await fetch(
+    const tokenAbort = new AbortController();
+    const tokenTimeout = setTimeout(() => tokenAbort.abort(), 15_000);
+    const tokenResponse = await fetch(
       "https://login.microsoftonline.com/common/oauth2/v2.0/token",
       {
         method: "POST",
         headers: {
           "Content-Type": "application/x-www-form-urlencoded",
         },
         body: new URLSearchParams({
           client_id: env.MICROSOFT_CLIENT_ID,
           client_secret: env.MICROSOFT_CLIENT_SECRET,
           code,
           grant_type: "authorization_code",
           redirect_uri: `${env.NEXT_PUBLIC_BASE_URL}/api/outlook/calendar/callback`,
         }),
+        signal: tokenAbort.signal,
       },
     );
+    clearTimeout(tokenTimeout);

Apply similar timeout to the profile fetch.

apps/web/utils/calendar/oauth-types.ts (2)

35-39: Narrow the OAuth state type for stronger guarantees.

Use a literal type to enforce only "calendar" at compile time (you still keep the runtime check).

 export interface CalendarOAuthState {
   emailAccountId: string;
-  type: string;
+  type: "calendar";
   nonce: string;
 }

10-27: Consider using export type aliases for consistency.

Guidelines prefer export type over interfaces for shared types. Optional, non‑blocking.

apps/web/utils/calendar/oauth-callback-helpers.ts (2)

126-138: Add explicit return type annotation.

The function is missing an explicit return type annotation. While TypeScript can infer it, explicit types improve code clarity and catch unintended changes.

Apply this diff to add the return type:

 export async function checkExistingConnection(
   emailAccountId: string,
   provider: "google" | "microsoft",
   email: string,
-) {
+): Promise<CalendarConnection | null> {
   return await prisma.calendarConnection.findFirst({

Additionally, consider extracting the "google" | "microsoft" provider type to a shared type definition to avoid duplication across multiple functions in this file.


143-162: Add explicit return type and verify isConnected logic.

Two concerns:

  1. The function is missing an explicit return type annotation.
  2. Line 159 hardcodes isConnected: true without verifying the connection is actually functional. Should this be validated before marking the connection as connected?

Apply this diff to add the return type:

 export async function createCalendarConnection(params: {
   provider: "google" | "microsoft";
   email: string;
   emailAccountId: string;
   accessToken: string;
   refreshToken: string;
   expiresAt: Date | null;
-}) {
+}): Promise<CalendarConnection> {
   return await prisma.calendarConnection.create({

Regarding isConnected: true - verify whether the connection should be tested before marking it as connected, or if the presence of valid tokens is sufficient.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 016ff9a and f6dbad4.

📒 Files selected for processing (8)
  • apps/web/app/api/google/calendar/callback/route.ts (1 hunks)
  • apps/web/app/api/outlook/calendar/callback/route.ts (1 hunks)
  • apps/web/utils/calendar/handle-calendar-callback.ts (1 hunks)
  • apps/web/utils/calendar/oauth-callback-helpers.ts (1 hunks)
  • apps/web/utils/calendar/oauth-types.ts (1 hunks)
  • apps/web/utils/calendar/providers/google.ts (1 hunks)
  • apps/web/utils/calendar/providers/microsoft.ts (1 hunks)
  • version.txt (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • version.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/outlook/calendar/callback/route.ts
🧰 Additional context used
📓 Path-based instructions (12)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/calendar/providers/google.ts
  • apps/web/utils/calendar/oauth-types.ts
  • apps/web/app/api/google/calendar/callback/route.ts
  • apps/web/utils/calendar/handle-calendar-callback.ts
  • apps/web/utils/calendar/providers/microsoft.ts
  • apps/web/utils/calendar/oauth-callback-helpers.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/google/calendar/callback/route.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/google/calendar/callback/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/google/calendar/callback/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/google/calendar/callback/route.ts
🧬 Code graph analysis (5)
apps/web/utils/calendar/providers/google.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/oauth-types.ts (2)
  • CalendarOAuthProvider (10-27)
  • CalendarTokens (3-8)
apps/web/utils/calendar/client.ts (2)
  • getCalendarOAuth2Client (31-37)
  • fetchGoogleCalendars (114-124)
apps/web/env.ts (1)
  • env (16-242)
apps/web/app/api/google/calendar/callback/route.ts (2)
apps/web/utils/calendar/handle-calendar-callback.ts (1)
  • handleCalendarCallback (19-135)
apps/web/utils/calendar/providers/google.ts (1)
  • googleCalendarProvider (13-98)
apps/web/utils/calendar/handle-calendar-callback.ts (3)
apps/web/utils/calendar/oauth-types.ts (1)
  • CalendarOAuthProvider (10-27)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/calendar/oauth-callback-helpers.ts (9)
  • validateOAuthCallback (17-47)
  • parseAndValidateCalendarState (52-77)
  • buildCalendarRedirectUrl (82-87)
  • verifyEmailAccountAccess (92-121)
  • checkExistingConnection (126-138)
  • redirectWithMessage (167-174)
  • createCalendarConnection (143-162)
  • RedirectError (191-201)
  • redirectWithError (179-186)
apps/web/utils/calendar/providers/microsoft.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/oauth-types.ts (2)
  • CalendarOAuthProvider (10-27)
  • CalendarTokens (3-8)
apps/web/env.ts (1)
  • env (16-242)
apps/web/utils/outlook/calendar-client.ts (1)
  • fetchMicrosoftCalendars (135-151)
apps/web/utils/calendar/oauth-callback-helpers.ts (5)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/calendar/oauth-types.ts (2)
  • OAuthCallbackValidation (29-33)
  • CalendarOAuthState (35-39)
apps/web/utils/calendar/constants.ts (1)
  • CALENDAR_STATE_COOKIE_NAME (1-1)
apps/web/utils/oauth/state.ts (1)
  • parseOAuthState (25-29)
apps/web/utils/auth.ts (1)
  • auth (499-500)
⏰ 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). (3)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (3)
apps/web/utils/calendar/providers/google.ts (1)

26-28: No changes needed; guard and upstream OAuth setup are correct.

Verified that the calendar auth flow at apps/web/app/api/google/calendar/auth-url/route.ts includes both access_type: "offline" (line 22) and prompt: "consent" (line 25), which guarantees Google will issue a refresh token. The guard clause properly validates its presence.

apps/web/utils/calendar/providers/microsoft.ts (2)

74-87: expiresAt: null semantics verified—behavior is correct and intentional.

Both implementations handle expiresAt: null correctly. When null is passed:

  • Outlook client checks if (expiresAt && expiresAt > Date.now() && accessToken), which evaluates to false, triggering token refresh
  • Google client checks if (expiresAt && expiresAt > Date.now()), which also evaluates to false, triggering token refresh

Passing expiresAt: null in both the Microsoft and Google syncCalendars methods is intentional—it forces immediate token refresh, which is the desired behavior during sync operations.


20-44: No action required—offline_access and calendar scopes are already correctly included.

The authorization flow already requests offline_access and both Calendars.Read and Calendars.ReadWrite scopes, which are defined in apps/web/utils/outlook/scopes.ts and passed to the Microsoft authorize endpoint via getCalendarOAuth2Url(). The scopes are properly included in both the initial authorization request and subsequent token refresh requests.

Comment on lines +31 to +43
const storedState = request.cookies.get("calendar_state")?.value;
if (!storedState) {
throw new Error("Missing stored state");
}

// Step 2: Parse and validate the OAuth state
const decodedState = parseAndValidateCalendarState(
storedState,
logger,
redirectUrl,
response.headers,
);

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 | 🟠 Major

Avoid hardcoded cookie name and redundant state read; decode the received state.

You already validated code/state in validateOAuthCallback. Re-reading a literal "calendar_state" cookie is brittle and unnecessary. Decode the validated state from the request query instead.

-    const storedState = request.cookies.get("calendar_state")?.value;
-    if (!storedState) {
-      throw new Error("Missing stored state");
-    }
+    // `validateOAuthCallback` already checked code/state and deleted the cookie.
+    // Decode the received state directly to avoid cookie name coupling.
+    const receivedState = request.nextUrl.searchParams.get("state")!;
 
     // Step 2: Parse and validate the OAuth state
     const decodedState = parseAndValidateCalendarState(
-      storedState,
+      receivedState,
       logger,
       redirectUrl,
       response.headers,
     );

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

🤖 Prompt for AI Agents
In apps/web/utils/calendar/handle-calendar-callback.ts around lines 31 to 43,
remove the brittle hardcoded cookie read of "calendar_state" and the redundant
storedState check; instead use the already-validated OAuth state from
validateOAuthCallback (the decoded/validated state parsed from the request
query) when calling parseAndValidateCalendarState. Replace the storedState
variable usage with the validated state value passed into
parseAndValidateCalendarState, and drop the throw for missing cookie; also
remove any direct dependency on a literal cookie name so the state source is the
validated callback payload.

Comment on lines +124 to +133
// Handle all other errors
logger.error("Error in calendar callback", { error });

// Try to build a redirect URL, fallback to /calendars
const errorRedirectUrl = new URL("/calendars", request.nextUrl.origin);
return redirectWithError(
errorRedirectUrl,
"connection_failed",
new Headers(),
);
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 | 🟠 Major

Ensure the state cookie is deleted even on generic errors.

When errors are not RedirectError, you return with new Headers(), dropping the cookie deletion set in validateOAuthCallback. Preserve those headers.

-export async function handleCalendarCallback(
+export async function handleCalendarCallback(
   request: NextRequest,
   provider: CalendarOAuthProvider,
   logger: Logger,
 ): Promise<NextResponse> {
-  try {
+  // Headers to carry cookie deletions from validation through all exit paths
+  let redirectHeaders = new Headers();
+  try {
     // Step 1: Validate OAuth callback parameters
     const { code, redirectUrl, response } = await validateOAuthCallback(
       request,
       logger,
     );
+    redirectHeaders = response.headers;
@@
-      return redirectWithMessage(
-        finalRedirectUrl,
-        "calendar_already_connected",
-        response.headers,
-      );
+      return redirectWithMessage(finalRedirectUrl, "calendar_already_connected", redirectHeaders);
@@
-    return redirectWithMessage(
-      finalRedirectUrl,
-      "calendar_connected",
-      response.headers,
-    );
+    return redirectWithMessage(finalRedirectUrl, "calendar_connected", redirectHeaders);
   } catch (error) {
@@
-    const errorRedirectUrl = new URL("/calendars", request.nextUrl.origin);
-    return redirectWithError(
-      errorRedirectUrl,
-      "connection_failed",
-      new Headers(),
-    );
+    const errorRedirectUrl = new URL("/calendars", request.nextUrl.origin);
+    return redirectWithError(errorRedirectUrl, "connection_failed", redirectHeaders);
   }
 }

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

🤖 Prompt for AI Agents
In apps/web/utils/calendar/handle-calendar-callback.ts around lines 124-133, the
error path creates a new Headers() losing the cookie-deletion headers set
earlier; update this return to pass the existing headers (the headers object
populated in validateOAuthCallback or the responseHeaders variable) into
redirectWithError instead of new Headers() so the state cookie is removed on
generic errors as well.

Comment on lines +55 to +61
const calendarClient = await getCalendarClientWithRefresh({
accessToken,
refreshToken,
expiresAt: null,
emailAccountId,
});

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

🧩 Analysis chain

Passing expiresAt: null may cause unnecessary refreshes; verify client expectations.

If getCalendarClientWithRefresh uses expiresAt to decide refresh, passing null could force immediate refresh or skip needed refresh logic.


🏁 Script executed:

#!/bin/bash
# Inspect getCalendarClientWithRefresh signature and logic
rg -n -C3 --type=ts 'function getCalendarClientWithRefresh|getCalendarClientWithRefresh\s*\(' apps/web/utils

Length of output: 1549


I need to search within the codebase for the getCalendarClientWithRefresh function definition. Let me look for imports and the function itself.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for imports of getCalendarClientWithRefresh in the google.ts file
rg -n 'import.*getCalendarClientWithRefresh|from.*getCalendarClientWithRefresh' apps/web/utils/calendar/providers/google.ts

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for the function definition of getCalendarClientWithRefresh across calendar utils
rg -n 'function getCalendarClientWithRefresh|export.*getCalendarClientWithRefresh|const getCalendarClientWithRefresh' apps/web/utils/calendar --type=ts

Length of output: 89



---

🏁 Script executed:

```shell
#!/bin/bash
# View the getCalendarClientWithRefresh function implementation
cat apps/web/utils/calendar/client.ts | head -100 | tail -70

Length of output: 2125



---

🏁 Script executed:

```shell
#!/bin/bash
# View the function in google.ts that contains the getCalendarClientWithRefresh call (around line 55)
sed -n '40,70p' apps/web/utils/calendar/providers/google.ts

Length of output: 807



---

🏁 Script executed:

```shell
#!/bin/bash
# View the availability.ts function signature and context around line 81
sed -n '65,95p' apps/web/utils/calendar/availability.ts

Length of output: 822



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for calendarConnection queries to see what fields are stored/retrieved
rg -n 'prisma\.calendarConnection' apps/web/utils/calendar --type=ts -A 3 -B 1 | head -50

Length of output: 2394



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for saveCalendarTokens function to see what token data is stored
rg -n 'saveCalendarTokens|function saveCalendarTokens|export.*saveCalendarTokens' apps/web/utils/calendar --type=ts -A 10

Length of output: 1390



---

🏁 Script executed:

```shell
#!/bin/bash
# View the full saveCalendarTokens function implementation
sed -n '126,160p' apps/web/utils/calendar/client.ts

Length of output: 826


Pass actual token expiry from database instead of hardcoded null to avoid unnecessary refreshes.

The calendarConnection record stores expiresAt (persisted in saveCalendarTokens), but this function hardcodes expiresAt: null, bypassing the freshness check in getCalendarClientWithRefresh that returns early when expiresAt > Date.now(). Query the stored expiresAt value and pass it to prevent forcing a refresh on every call:

// Before calling getCalendarClientWithRefresh, query the connection:
const connection = await prisma.calendarConnection.findFirst({
  where: { id: connectionId, provider: "google" },
  select: { expiresAt: true },
});

const calendarClient = await getCalendarClientWithRefresh({
  accessToken,
  refreshToken,
  expiresAt: connection?.expiresAt?.getTime() ?? null,
  emailAccountId,
});
🤖 Prompt for AI Agents
In apps/web/utils/calendar/providers/google.ts around lines 55 to 61, the call
to getCalendarClientWithRefresh passes expiresAt: null which forces unnecessary
token refreshes; query the stored calendarConnection by connectionId (selecting
expiresAt), convert the resulting Date to a timestamp (or use null if missing),
and pass that value as expiresAt to getCalendarClientWithRefresh so the function
can short-circuit when the token is still fresh; also handle the case where the
connection is not found (use null) and keep
accessToken/refreshToken/emailAccountId as before.

Comment on lines +64 to +71
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: tokens.expires_in
? new Date(Date.now() + tokens.expires_in * 1000)
: null,
email: microsoftEmail,
};
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 | 🟠 Major

Validate presence of refresh_token before proceeding.

Without offline_access or if the app is misconfigured, Microsoft won’t return a refresh_token. Proceeding would create a brittle connection.

   return {
-      accessToken: tokens.access_token,
-      refreshToken: tokens.refresh_token,
+      accessToken: tokens.access_token,
+      refreshToken: tokens.refresh_token,
       expiresAt: tokens.expires_in
         ? new Date(Date.now() + tokens.expires_in * 1000)
         : null,
       email: microsoftEmail,
   };

Add validation:

+    if (!tokens.refresh_token) {
+      throw new Error("No refresh_token returned from Microsoft (ensure offline_access scope and correct app type)");
+    }
📝 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
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: tokens.expires_in
? new Date(Date.now() + tokens.expires_in * 1000)
: null,
email: microsoftEmail,
};
if (!tokens.refresh_token) {
throw new Error("No refresh_token returned from Microsoft (ensure offline_access scope and correct app type)");
}
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: tokens.expires_in
? new Date(Date.now() + tokens.expires_in * 1000)
: null,
email: microsoftEmail,
};
🤖 Prompt for AI Agents
In apps/web/utils/calendar/providers/microsoft.ts around lines 64 to 71, the
code returns tokens.refresh_token without verifying it exists — add a check that
tokens.refresh_token is present and valid before returning; if it's missing,
fail fast by throwing a clear error or returning a null/Result indicating the
missing refresh token (include a message instructing to ensure offline_access
scope and correct app configuration), and update any callers to handle the
error/nullable response.

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

🧹 Nitpick comments (1)
apps/web/utils/calendar/providers/microsoft-availability.ts (1)

11-66: Consider defensive property access for API response data.

The code checks item.start && item.end at line 44, but then directly accesses item.start.dateTime and item.end.dateTime at lines 46-47 without verifying the dateTime property exists. While the Microsoft Graph API contract likely guarantees these properties, defensive coding would be safer.

Consider using optional chaining or explicit checks:

-            if (item.status !== "free" && item.start && item.end) {
+            if (item.status !== "free" && item.start?.dateTime && item.end?.dateTime) {
               busyPeriods.push({
                 start: item.start.dateTime,
                 end: item.end.dateTime,
               });
             }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6dbad4 and 6545499.

📒 Files selected for processing (6)
  • apps/web/utils/ai/calendar/availability.ts (3 hunks)
  • apps/web/utils/calendar/availability-types.ts (1 hunks)
  • apps/web/utils/calendar/availability.ts (0 hunks)
  • apps/web/utils/calendar/providers/google-availability.ts (1 hunks)
  • apps/web/utils/calendar/providers/microsoft-availability.ts (1 hunks)
  • apps/web/utils/calendar/unified-availability.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/utils/calendar/availability.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/calendar/unified-availability.ts
  • apps/web/utils/calendar/providers/google-availability.ts
  • apps/web/utils/calendar/providers/microsoft-availability.ts
  • apps/web/utils/ai/calendar/availability.ts
  • apps/web/utils/calendar/availability-types.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/calendar/availability.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/calendar/availability.ts
🧬 Code graph analysis (4)
apps/web/utils/calendar/unified-availability.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/availability-types.ts (1)
  • BusyPeriod (1-4)
apps/web/utils/calendar/providers/google-availability.ts (1)
  • googleAvailabilityProvider (63-89)
apps/web/utils/calendar/providers/microsoft-availability.ts (1)
  • microsoftAvailabilityProvider (68-94)
apps/web/utils/calendar/providers/google-availability.ts (2)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/availability-types.ts (2)
  • BusyPeriod (1-4)
  • CalendarAvailabilityProvider (6-21)
apps/web/utils/calendar/providers/microsoft-availability.ts (2)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/calendar/availability-types.ts (2)
  • BusyPeriod (1-4)
  • CalendarAvailabilityProvider (6-21)
apps/web/utils/ai/calendar/availability.ts (1)
apps/web/utils/calendar/unified-availability.ts (1)
  • getUnifiedCalendarAvailability (14-137)
🔇 Additional comments (11)
apps/web/utils/ai/calendar/availability.ts (2)

6-6: LGTM! Clear refactoring to unified availability.

The import and tool description accurately reflect the new unified calendar availability approach that aggregates across all connected Google and Microsoft calendars.

Also applies to: 112-113


126-142: Excellent error handling for calendar availability.

The try-catch wrapper ensures that calendar fetch errors don't break the AI drafting flow. Returning an empty busyPeriods array on error allows the AI to continue processing without calendar data, which is the right fallback behavior.

apps/web/utils/calendar/providers/microsoft-availability.ts (2)

68-94: LGTM! Clean provider implementation.

The Microsoft availability provider correctly implements the CalendarAvailabilityProvider interface and properly delegates token refresh to getCalendarClientWithRefresh. The structure mirrors the Google provider for consistency.


1-10: Proper logging setup following guidelines.

Logger initialization follows the coding guidelines correctly: uses createScopedLogger at the top of the file with an appropriate scope name.

Based on learnings

apps/web/utils/calendar/unified-availability.ts (4)

25-39: Excellent timezone handling.

The use of TZDate with startOfDay and endOfDay ensures proper day boundary calculation in the user's timezone before converting to UTC strings for API calls. This prevents timezone-related edge cases.


42-60: Efficient database query with proper filtering.

The Prisma query correctly filters for connected calendars and enabled calendars only, fetching just the needed fields. The early return when no connections exist prevents unnecessary processing.


62-122: Outstanding resilient error handling.

The error handling strategy is excellent: each provider fetch has its own .catch block that logs the error and returns an empty array. This ensures that if one calendar connection fails (e.g., expired tokens, API issues), the others can still succeed. The use of Promise.all for concurrent fetching is appropriate and efficient.


124-137: Clean result aggregation.

The use of Promise.all followed by flat() efficiently aggregates busy periods from all providers. The final trace log with connection counts provides useful observability.

apps/web/utils/calendar/availability-types.ts (1)

1-21: Clean type definitions establishing provider contract.

The types are well-designed: BusyPeriod is simple and focused, while CalendarAvailabilityProvider provides a clear interface for implementations. The parameter types appropriately handle the OAuth token refresh flow (optional accessToken, required refreshToken).

apps/web/utils/calendar/providers/google-availability.ts (2)

11-61: LGTM! Solid Google Calendar integration.

The implementation correctly uses the Google Calendar freebusy API and properly validates that period.start and period.end exist before creating BusyPeriod objects. The structure is consistent with the Microsoft provider for maintainability.


63-89: Well-structured provider implementation.

The Google availability provider correctly implements the CalendarAvailabilityProvider interface with proper token refresh handling. The consistent structure with the Microsoft provider makes the codebase more maintainable.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants