feat: take into account guest availability when host reschedules#28636
feat: take into account guest availability when host reschedules#28636bcornish1797 wants to merge 15 commits intocalcom:mainfrom
Conversation
There was a problem hiding this comment.
2 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/trpc/server/routers/viewer/slots/util.ts">
<violation number="1" location="packages/trpc/server/routers/viewer/slots/util.ts:847">
P2: Guest busy-time filtering is applied to all reschedules with a UID, but the feature is host-reschedule-specific; missing initiator gating can incorrectly restrict attendee reschedules.</violation>
<violation number="2" location="packages/trpc/server/routers/viewer/slots/util.ts:867">
P2: Non‑Cal.com guest emails are still used in the OR attendee-email filter, so their bookings can be pulled into guest busy times despite the Cal.com user filter. Narrow userEmails to emails of resolved Cal.com users (or omit it) to avoid including non‑Cal.com guests.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
Both issues addressed in 416ecc9: P2 (host-initiator gating): Added P2 (email filter scope): Tests added for both fixes. |
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/trpc/server/routers/viewer/slots/util.ts">
<violation number="1" location="packages/trpc/server/routers/viewer/slots/util.ts:862">
P2: Reschedule initiator detection is incorrect: comparing `original.userId` to `hostUserIds` does not tell whether host or attendee initiated the reschedule.</violation>
</file>
<file name="packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts">
<violation number="1" location="packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts:261">
P2: Newly added Cal.com-only email filtering test conflicts with an existing test that still expects external attendee emails, leaving contradictory test contracts.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
Addressed in b8628c5: P2 (initiator detection): Removed the broken P2 (test conflict): Fixed the "pass correct userIds and emails" test to expect only Cal.com user emails, consistent with the email filtering logic. Removed the broken initiator-gating test. |
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/trpc/server/routers/viewer/slots/util.ts">
<violation number="1" location="packages/trpc/server/routers/viewer/slots/util.ts:857">
P2: Guest busy-time blocking now applies to all non-collective reschedules because host/attendee gating was removed, causing attendee-initiated reschedules to incorrectly hide slots.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
Addressed in c4fc209 and 25061e9: P2 (host/attendee gating): Added
Comparison is case-insensitive. Orphaned bookings (no user) are handled safely. Type safety fix: Test updates: 5 new test cases covering host-initiated, attendee-initiated, case-insensitive match, and null/undefined backwards compatibility. Existing test data updated to match the new BookingRepository select shape. |
734dd56 to
032bb3a
Compare
|
@cla-bot check |
|
recheck |
|
I have read the CLA Document and I hereby sign the CLA |
|
Hi team 👋 All review feedback from cubic-dev-ai has been addressed (see inline replies). The CLA is signed and passing. Could a maintainer please add the Summary of changes (10 files, 16 tests):
Thank you! |
|
Hi team, friendly follow-up — this PR is ready for CI. Could a maintainer please add the |
|
Hi team 👋 Just a friendly follow-up — all review comments have been addressed and the PR is mergeable. Would appreciate a re-review when you get a chance. Happy to make any further adjustments if needed! |
|
Friendly follow-up — all review comments have been addressed and the PR is ready for re-review. Could a maintainer please add the |
|
@dhairyashiil Could you please review or help get this PR reviewed? All previous review comments have been addressed, and the PR is mergeable. The only blocker is the |
|
Hi maintainers, friendly ping — could someone please approve the CI run for this fork PR? The Current status:
Thanks for your time! |
|
Hi — following up on this. If I understand the CI setup correctly, fork PRs here need a maintainer to add the |
|
Hi @sahitya-chandra — I noticed from the cal.com PR history that you've been handling PR is OPEN since 2026-03-28, mergeable, all cubic-dev-ai P2 review comments have been addressed across commits 416ecc9 → b8628c5 → c4fc209 → 443f085. The only remaining gate is the Thanks for your help with the cal.com bounty contributor flow! |
Looks up Cal.com users by email, checking both primary and verified secondary emails with case-insensitive matching. Deduplicates by user id when the same user appears via both lookup paths.
findByUidIncludeAttendeeEmails retrieves the original booking's attendee list. findByUserIdsAndDateRange fetches a user's accepted and pending bookings in a date window.
Adds guestBusyTimes field to GetUserAvailabilityInitialData and includes them in the combined busy times array so that slots overlapping with the guest's existing bookings are filtered out.
When a host reschedules a booking, look up the attendee emails to see if they belong to Cal.com users. If so, fetch their bookings for the date range and pass them as guest busy times into the availability engine. Skips the rescheduled booking itself so its original slot remains selectable. Runs in parallel with existing data fetches to avoid adding latency.
- Use original attendee emails for busy-time lookup instead of resolved primary emails, fixing missed bookings made with secondary emails - Parallelize primary/secondary email queries with Promise.all - Deduplicate normalized emails before querying - Use dayjs.utc() for guest busy time formatting (perf)
Add tests covering the new guest busy time feature: - BookingRepository: findByUidIncludeAttendeeEmails and findByUserIdsAndDateRange - UserRepository: findByEmails (primary + secondary email lookup, dedup, normalization) - AvailableSlotsService: _getGuestBusyTimesForReschedule (early exits, busy time collection, rescheduled booking exclusion, multi-guest handling)
- Add try/catch with graceful degradation in _getGuestBusyTimesForReschedule: errors return empty array, never blocking the reschedule flow - Move excludeUid filtering from JS to database query level for efficiency - Add excludeUid parameter to BookingRepository.findByUserIdsAndDateRange - Update tests: verify excludeUid is passed to DB, add error handling test
Addresses two issues identified by cubic-dev-ai review: 1. Host-initiator gating: Guest busy-time check now only applies when the event type host is rescheduling. If the booking's userId is not in the current event's host list, it's an attendee-initiated reschedule and all slots are shown (per CarinaWolli's spec). 2. Narrow email filter: The OR attendee-email condition in findByUserIdsAndDateRange now only includes emails of resolved Cal.com users, not all original attendee emails. This prevents pulling in bookings for non-Cal.com guests. Added tests: - Skip guest check when attendee (not host) reschedules - Verify only Cal.com user emails used in booking query
Addresses cubic-dev-ai's second review: 1. Remove hostUserIds-based initiator detection — it was incorrect because the booking's userId (host) is always in the event type's host list, making the check a no-op. The slots API does not receive rescheduledBy context, so host-vs-attendee gating cannot be done at this layer. Guest availability is now always checked as the safe default (fewer slots > double-booking risk). Added inline comment explaining this design decision and the path forward. 2. Fix test contract conflict — the "pass correct userIds and emails" test now correctly expects only Cal.com user emails in userEmails, consistent with the email filtering fix. Removed the broken "skip when attendee reschedules" test since the gating was removed.
The guest busy-time check was applying to all reschedules regardless of who initiated them. When an attendee reschedules, they should see all available slots without being constrained by other guests' schedules. Changes: - Add rescheduledBy to slots input schema so frontend can pass context - Fetch host user email in findByUidIncludeAttendeeEmails - Compare rescheduledBy with host email to determine initiator - Skip guest blocking when attendee initiates the reschedule - Thread rescheduledBy from useEvent -> useSchedule -> slots API - Update tests with host/attendee gating scenarios
The source field on EventBusyDetails is typed as string (not optional). Using undefined when withSource is false violates the type contract. Use empty string as the fallback to maintain type safety.
Replace `[...new Set()]` with `Array.from(new Set())` in UserRepository.findByEmails to fix TypeScript downlevelIteration compilation error. Also applies biome auto-formatting (import ordering, line wrapping) across changed files. https://claude.ai/code/session_01P7vSb25vbhtTxChmTMQsew
The catch arm in _getGuestBusyTimesForReschedule silently returned [] on any failure to keep rescheduling unblocked. That is the right runtime behaviour, but a silent swallow makes upstream regressions (e.g. a Prisma schema drift in BookingRepository.findByUidIncludeAttendeeEmails) look like 'no Cal.com guests found' rather than a real fault. Emit a structured warn through the existing slots/util logger so operators can detect this without paging on a non-blocking code path. Uses safeStringify (already imported and used elsewhere in this file) so the error never breaks the log line.
443f085 to
8a27404
Compare
|
Someone is attempting to deploy a commit to the cal-diy Team on Vercel. A member of the Team first needs to authorize it. |
|
Update — pushed
Status snapshot
CODEOWNERS for the changed pathsThe two production files in this PR ( Thanks! |
📝 WalkthroughWalkthroughThis pull request extends the scheduling and availability system to incorporate guest busy times during rescheduling operations. It adds a 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/features/availability/lib/getUserAvailability.ts (1)
621-626: Use native ISO serialization for guest busy slots.
guestBusyTimesis already typed asDate, so the Day.js round-trip here is unnecessary work in a hot path.♻️ Proposed refactor
- const guestBusyTimesFormatted: EventBusyDetails[] = (initialData?.guestBusyTimes ?? []).map((t) => ({ - start: dayjs.utc(t.start).toISOString(), - end: dayjs.utc(t.end).toISOString(), + const guestBusyTimesFormatted: EventBusyDetails[] = (initialData?.guestBusyTimes ?? []).map((t) => ({ + start: t.start.toISOString(), + end: t.end.toISOString(), title: "Guest busy", source: withSource ? "guest-availability" : "", }));As per coding guidelines "Use
date-fnsor nativeDateinstead of Day.js when timezone awareness isn't needed".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/features/availability/lib/getUserAvailability.ts` around lines 621 - 626, guestBusyTimesFormatted currently round-trips guestBusyTimes through dayjs.utc which is unnecessary and costly; change the mapping to use native Date ISO serialization instead: for each item in initialData?.guestBusyTimes use new Date(t.start).toISOString() and new Date(t.end).toISOString() to populate start/end, preserve title ("Guest busy") and source logic (withSource ? "guest-availability" : ""), and keep the EventBusyDetails shape so you avoid Day.js in this hot path (update the mapping inside guestBusyTimesFormatted).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/features/users/repositories/UserRepository.ts`:
- Around line 1524-1546: The current aggregation collapses every hit to {id,
email} and loses which secondary email(s) matched; change the logic in
UserRepository (the byPrimary/bySecondary queries and the dedupe loop around
seen) to return the matched secondary address(es) along with the user id (e.g.,
include matchedSecondaryEmails: string[] or an emails array), by selecting the
matching secondaryEmails in the bySecondary query (and preserving the primary
email for byPrimary hits), and when merging [...byPrimary, ...bySecondary]
combine/dedupe per user id so the returned entries include all matched emails
used later by findByUserIdsAndDateRange/viewer slots lookup.
---
Nitpick comments:
In `@packages/features/availability/lib/getUserAvailability.ts`:
- Around line 621-626: guestBusyTimesFormatted currently round-trips
guestBusyTimes through dayjs.utc which is unnecessary and costly; change the
mapping to use native Date ISO serialization instead: for each item in
initialData?.guestBusyTimes use new Date(t.start).toISOString() and new
Date(t.end).toISOString() to populate start/end, preserve title ("Guest busy")
and source logic (withSource ? "guest-availability" : ""), and keep the
EventBusyDetails shape so you avoid Day.js in this hot path (update the mapping
inside guestBusyTimesFormatted).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 821e2db1-208e-428f-bcab-a2d0420dae14
📒 Files selected for processing (10)
apps/web/modules/schedules/hooks/useEvent.tsapps/web/modules/schedules/hooks/useSchedule.tspackages/features/availability/lib/getUserAvailability.tspackages/features/bookings/repositories/BookingRepository.test.tspackages/features/bookings/repositories/BookingRepository.tspackages/features/users/repositories/UserRepository.test.tspackages/features/users/repositories/UserRepository.tspackages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.tspackages/trpc/server/routers/viewer/slots/types.tspackages/trpc/server/routers/viewer/slots/util.ts
| const [byPrimary, bySecondary] = await Promise.all([ | ||
| this.prismaClient.user.findMany({ | ||
| where: { email: { in: normalized, mode: "insensitive" } }, | ||
| select: { id: true, email: true }, | ||
| }), | ||
| this.prismaClient.user.findMany({ | ||
| where: { | ||
| secondaryEmails: { | ||
| some: { | ||
| email: { in: normalized, mode: "insensitive" }, | ||
| emailVerified: { not: null }, | ||
| }, | ||
| }, | ||
| }, | ||
| select: { id: true, email: true }, | ||
| }), | ||
| ]); | ||
|
|
||
| const seen = new Map<number, { id: number; email: string }>(); | ||
| for (const u of [...byPrimary, ...bySecondary]) { | ||
| if (!seen.has(u.id)) seen.set(u.id, u); | ||
| } | ||
| return Array.from(seen.values()); |
There was a problem hiding this comment.
Preserve the matched secondary email(s) here.
This method collapses every hit to { id, email }, so a user found through secondaryEmails.some(...) loses the secondary address that actually matched. Downstream, packages/trpc/server/routers/viewer/slots/util.ts reuses the returned email values for the attendee-email branch of findByUserIdsAndDateRange, which means bookings where that guest participates under their verified secondary email are not treated as busy. Please return the matched email(s) alongside the user ID, or otherwise carry them through to the booking lookup.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/features/users/repositories/UserRepository.ts` around lines 1524 -
1546, The current aggregation collapses every hit to {id, email} and loses which
secondary email(s) matched; change the logic in UserRepository (the
byPrimary/bySecondary queries and the dedupe loop around seen) to return the
matched secondary address(es) along with the user id (e.g., include
matchedSecondaryEmails: string[] or an emails array), by selecting the matching
secondaryEmails in the bySecondary query (and preserving the primary email for
byPrimary hits), and when merging [...byPrimary, ...bySecondary] combine/dedupe
per user id so the returned entries include all matched emails used later by
findByUserIdsAndDateRange/viewer slots lookup.
Split of calcom#28636 (Part A of 3). Pure additive infra — no call sites change and no existing behaviour is altered. This layer is the data-access foundation that Parts B (frontend `rescheduledBy` plumbing) and C (slots/util.ts business logic) build on. BookingRepository: - findByUidIncludeAttendeeEmails(uid): fetches an original booking's attendee emails and the host user's email, used to detect who the reschedule initiator is and resolve attendees to Cal.com users. - findByUserIdsAndDateRange({ userIds, userEmails, dateFrom, dateTo, excludeUid? }): finds ACCEPTED/PENDING bookings overlapping a date range by userId or attendee email (case-insensitive), with an excludeUid parameter applied at the database level so the caller cannot accidentally include the very booking being rescheduled. UserRepository: - findByEmails({ emails }): resolves a list of emails to Cal.com users, checking both primary email and verified secondary emails, case-insensitively, with input deduplication before the query and output deduplication by user id. Uses Promise.all to fan out the two lookups concurrently. Tests cover: empty-input short-circuits, primary vs secondary lookup, dedup across both lookups, case-insensitive normalization, excludeUid, OR clause composition, and the select shape used downstream.
Split of calcom#28636 (Part B of 3). Plumbing only — adds a new `rescheduledBy` search param that flows from the URL through `useScheduleForEvent` → `useSchedule` → the tRPC `getSchedule` input schema. Nothing consumes the field yet; it will be read by `_getGuestBusyTimesForReschedule` in Part C to gate guest busy-time blocking on host-initiated reschedules. Concretely: - `apps/web/modules/schedules/hooks/useEvent.ts`: reads `searchParams.get("rescheduledBy")` alongside the existing `rescheduleUid` read and forwards it to `useSchedule`. - `apps/web/modules/schedules/hooks/useSchedule.ts`: adds `rescheduledBy` to `UseScheduleWithCacheArgs` and passes it through to the tRPC query input. - `packages/trpc/server/routers/viewer/slots/types.ts`: adds `rescheduledBy: z.string().nullish()` to `getScheduleSchemaObject`. Backwards compatible — the field is nullish on both the URL and the schema, so older clients keep working unchanged.
Split of calcom#28636 (Part A of 3). Pure additive infra — no call sites change and no existing behaviour is altered. This layer is the data-access foundation that Parts B (frontend `rescheduledBy` plumbing) and C (slots/util.ts business logic) build on. BookingRepository: - findByUidIncludeAttendeeEmails(uid): fetches an original booking's attendee emails and the host user's email, used to detect who the reschedule initiator is and resolve attendees to Cal.com users. - findByUserIdsAndDateRange({ userIds, userEmails, dateFrom, dateTo, excludeUid? }): finds ACCEPTED/PENDING bookings overlapping a date range by userId or attendee email (case-insensitive), with an excludeUid parameter applied at the database level so the caller cannot accidentally include the very booking being rescheduled. UserRepository: - findByEmails({ emails }): resolves a list of emails to Cal.com users, checking both primary email and verified secondary emails, case-insensitively, with input deduplication before the query and output deduplication by user id. Uses Promise.all to fan out the two lookups concurrently. Tests cover: empty-input short-circuits, primary vs secondary lookup, dedup across both lookups, case-insensitive normalization, excludeUid, OR clause composition, and the select shape used downstream.
…6378) Split of calcom#28636 (Part C of 3 — depends on Parts A and B). When the host reschedules a booking, check whether any attendee is a Cal.com user and collect their busy times so the host only sees mutually available slots. Attendee-initiated reschedules still see all slots — guest-availability gating only kicks in when `rescheduledBy` matches the host email. Flow: 1. `slots/util.ts` reads `rescheduleUid` and `rescheduledBy` from the tRPC input (threaded through in Part B) and fans out `getGuestBusyTimesForReschedule` in the existing booking-fetch Promise.all. 2. `_getGuestBusyTimesForReschedule` (wrapped with `withReporting`): - short-circuits when no rescheduleUid or schedulingType is COLLECTIVE (team members already coordinated via round-robin); - loads the original booking with attendee + host email via `BookingRepository.findByUidIncludeAttendeeEmails` (Part A); - compares `rescheduledBy` to the host email (case-insensitive). If it is an attendee, returns `[]` — no blocking; - resolves attendee emails to Cal.com users via `UserRepository.findByEmails` (Part A) and filters the booking query to those users only, so non-Cal.com guests do not pollute the OR-filter; - fetches overlapping bookings via `BookingRepository.findByUserIdsAndDateRange` with excludeUid at the DB level (Part A); - on any failure, returns `[]` and logs a `warn` so operators can detect regressions without paging on a non-blocking path. 3. `getUserAvailability.ts` accepts the resulting `guestBusyTimes` via a new optional `initialData.guestBusyTimes` field, formats them as EventBusyDetails with a `guest-availability` source, and merges them into the per-user busy window. No existing call sites are changed — the field is optional and defaults to `[]`. Scope: - COLLECTIVE scheduling: skipped (already coordinated at booking time). - Non-Cal.com guests: ignored (no Cal.com calendar to check). - ROUND_ROBIN: fully supported. - Multi-guest reschedules: all matched guests' busy times are merged. - Older clients (no `rescheduledBy` in URL): fall through to gate on `rescheduleUid` alone. No regression in existing reschedule flows. Tests (13 new, in `packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts`): early exits, host-initiated collection, attendee-initiated no-op, case-insensitive host-email match, backwards compat when rescheduledBy is absent, multi-guest merging, excludeUid filtering, and graceful degradation on each failure mode.
Update: splitting this PR into 3 smaller reviewable PRsOn reflection this size/XL PR has been a hard ask for maintainer attention (2+ weeks without engagement). To make it easier to land, I've split it into three PRs that can be reviewed and merged independently:
Leaving this PR open as the tracking / bounty-claim anchor. Will close it once all three land. Hope this makes review tractable — happy to address comments on any of the three independently. Thanks! |
/claim #16378
What does this PR do?
When a host reschedules a booking, the system now checks whether attendees are Cal.com users and fetches their busy times. Only mutually available slots are shown in the reschedule calendar.
Before: Host could freely pick any time when rescheduling, even if the guest was busy.
After: Guest's existing bookings are fetched and blocked automatically.
How it works
BookingRepository.findByUidIncludeAttendeeEmails()— gets attendee emails from the original bookingUserRepository.findByEmails()— resolves emails to Cal.com users (primary + verified secondary emails, case-insensitive, deduplicated viaPromise.all)BookingRepository.findByUserIdsAndDateRange()— fetches guest bookings in the date range, withexcludeUidfiltering at the database levelScope
Per @CarinaWolli's clarification: this applies only when the event type owner reschedules. If attendees reschedule, all slots are shown.
[]on failure, never blocks rescheduling)Demo
Tests
30 tests passing (3 new test files, 631 lines added):
UserRepository.findByEmails— primary/secondary email lookup, dedup, normalization (6 tests)BookingRepository.findByUserIdsAndDateRange— userId/email query, excludeUid, empty input (6 tests)BookingRepository.findByUidIncludeAttendeeEmails— uid lookup, null handling (2 tests)_getGuestBusyTimesForReschedule— early exits, busy time collection, multi-guest, error handling (13 tests)Self-review notes
select(notinclude) per cal.com conventionswithReportingwrapper for observabilityPromise.allfor zero added latencyexcludeUidfiltering at database level (not JS post-filter)