Skip to content

feat(reschedule): check guest availability when host reschedules#28229

Closed
sirrodgepodge wants to merge 1 commit intocalcom:mainfrom
sirrodgepodge:feat/reschedule-guest-availability
Closed

feat(reschedule): check guest availability when host reschedules#28229
sirrodgepodge wants to merge 1 commit intocalcom:mainfrom
sirrodgepodge:feat/reschedule-guest-availability

Conversation

@sirrodgepodge
Copy link

When a host reschedules a booking, the slot picker now filters out time slots that conflict with the guests' (attendees') existing accepted bookings — preventing double-booking guests.

Changes

packages/features/bookings/repositories/BookingRepository.ts

  • Add getAcceptedBookingsByAttendeeEmails() to query bookings where any attendee email appears in a given list, within a date range, excluding the booking being rescheduled
  • Uses overlap semantics (startTime < windowEnd AND endTime > windowStart) for correct boundary detection

packages/trpc/server/routers/viewer/slots/util.ts

  • In calculateHostsAndAvailabilities(): when rescheduleUid is present, look up the original booking's attendees via BookingRepository
  • Filter out host emails (booking owner + event type hosts) to isolate guest-only emails
  • Fetch their accepted bookings in the slot search window, excluding the booking being rescheduled
  • Pass as guestBusyTimes through initialData

packages/features/availability/lib/getUserAvailability.ts

  • Add optional guestBusyTimes field to GetUserAvailabilityInitialData
  • Spread guestBusyTimes into detailedBusyTimes so guest conflicts block slot availability

How it works

  1. Host clicks reschedule → slot picker calls getSchedule with rescheduleUid
  2. We look up the original booking's attendees and identify which are guests (not hosts)
  3. We query all accepted bookings where those guest emails appear as attendees
  4. Those bookings become busy times that block slots, just like host busy times

Fixes #16378
/claim #28164

When a host reschedules a booking, the slot picker now filters out time
slots that conflict with the guests' (attendees') existing accepted
bookings — preventing double-booking guests.

Changes:
- BookingRepository: add getAcceptedBookingsByAttendeeEmails() to query
  bookings where any attendee email appears in a given list, within a
  date range, excluding the booking being rescheduled
- AvailableSlotsService.calculateHostsAndAvailabilities(): when
  rescheduleUid is present, look up the original booking's attendees,
  filter out host emails to get guest-only emails, fetch their accepted
  bookings in the slot search window, and pass them as guestBusyTimes
- GetUserAvailabilityInitialData: add optional guestBusyTimes field
- getUserAvailability(): spread guestBusyTimes into detailedBusyTimes so
  guest conflicts are treated as busy time during availability calculation

Fixes calcom#16378
Copilot AI review requested due to automatic review settings March 1, 2026 19:42
@sirrodgepodge sirrodgepodge requested a review from a team as a code owner March 1, 2026 19:42
@github-actions github-actions bot added $200 bookings area: bookings, availability, timezones, double booking Medium priority Created by Linear-GitHub Sync ✨ feature New feature or request 💎 Bounty A bounty on Algora.io 🧹 Improvements Improvements to existing features. Mostly UX/UI labels Mar 1, 2026
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Mar 1, 2026
@graphite-app
Copy link

graphite-app bot commented Mar 1, 2026

Graphite Automations

"Send notification to Community team when bounty PR opened" took an action on this PR • (03/01/26)

2 teammates were notified to this PR based on Keith Williams's automation.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

No issues found across 3 files

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds guest-conflict awareness to the reschedule slot picker so hosts can’t reschedule into times where non-host attendees already have an accepted booking.

Changes:

  • Add BookingRepository.getAcceptedBookingsByAttendeeEmails() to fetch accepted bookings for a set of attendee emails in a time window (with overlap + excluded booking semantics).
  • In slots availability calculation, fetch the original booking’s guest emails on reschedule and inject their accepted bookings as guestBusyTimes.
  • Extend getUserAvailability initialData to accept guestBusyTimes and merge them into detailedBusyTimes to block conflicting slots.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
packages/trpc/server/routers/viewer/slots/util.ts Collect guest attendee emails during reschedule and pass guest-derived busy intervals into availability calculation.
packages/features/bookings/repositories/BookingRepository.ts New Prisma query to find accepted bookings by attendee emails with overlap/exclusion filtering.
packages/features/availability/lib/getUserAvailability.ts Accept guestBusyTimes in initialData and include them in busy-time subtraction.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const rescheduleBooking = await bookingRepo.findByUidIncludeEventTypeAttendeesAndUser({
bookingUid: input.rescheduleUid,
});
if (rescheduleBooking) {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

getSchedule is a public endpoint, and this code uses the client-provided rescheduleUid to look up a booking and then query other accepted bookings for the booking’s attendee emails. Without an explicit authorization/gating check (e.g., only run when the requester is the booking owner/host), this can become a privacy leak (inferring attendees’ busy times) if a rescheduleUid is disclosed. Consider gating guest-conflict lookups to authenticated host reschedules and/or verifying the requester is allowed to access the booking before querying attendee-based conflicts.

Suggested change
if (rescheduleBooking) {
// Only consider guest conflicts if the reschedule booking belongs to one of the
// current hosts/users participating in this scheduling flow. This prevents
// unauthorized callers from probing guest availability using arbitrary UIDs.
if (rescheduleBooking && rescheduleBooking.userId && allUserIds.includes(rescheduleBooking.userId)) {

Copilot uses AI. Check for mistakes.
guestBusyTimes = guestBookings.map((b) => ({
start: b.startTime.toISOString(),
end: b.endTime.toISOString(),
title: b.title ?? undefined,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

guestBusyTimes is populated with title from bookings that belong to the guests. This will flow into getUserAvailability's busy output and debug logs, and may expose private meeting titles unrelated to the host/reschedule flow. Consider omitting the title (or replacing with a generic label) for guest-derived busy times.

Suggested change
title: b.title ?? undefined,
// Do not leak guest-owned meeting titles; use a generic label instead.
title: "Busy",

Copilot uses AI. Check for mistakes.
Comment on lines +858 to +866
// Guest emails = attendees that are NOT hosts
const guestEmails = rescheduleBooking.attendees
.map((a) => a.email)
.filter((email) => !hostEmails.has(email));

if (guestEmails.length > 0) {
const guestBookings = await bookingRepo.getAcceptedBookingsByAttendeeEmails({
attendeeEmails: guestEmails,
startDate: startTimeDate,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

guestEmails can contain duplicates (and potentially empty strings, depending on upstream data). Deduplicating/filtering before calling getAcceptedBookingsByAttendeeEmails will reduce the size of the SQL IN (...) list and avoid unnecessary DB work.

Copilot uses AI. Check for mistakes.
Comment on lines 999 to 1004
busyTimesFromLimits: busyTimesFromLimitsMap,
eventTypeForLimits: eventType && (bookingLimits || durationLimits) ? eventType : null,
teamBookingLimits: teamBookingLimitsMap,
teamForBookingLimits: teamForBookingLimits,
guestBusyTimes,
},
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

guestBusyTimes is passed via initialData and then appended into every host's detailedBusyTimes. For multi-host event types this duplicates the same guest busy intervals across all users (and into debug logs), which can inflate memory/CPU for large teams or wide date windows. Consider applying guest conflicts once at the aggregated availability stage (global blocker) or otherwise avoiding per-user duplication.

Copilot uses AI. Check for mistakes.
Comment on lines +2229 to +2262
async getAcceptedBookingsByAttendeeEmails({
attendeeEmails,
startDate,
endDate,
excludedUid,
}: {
attendeeEmails: string[];
startDate: Date;
endDate: Date;
excludedUid?: string | null;
}) {
if (attendeeEmails.length === 0) return [];
return this.prismaClient.booking.findMany({
where: {
status: BookingStatus.ACCEPTED,
// Use overlap semantics: any booking whose time range intersects [startDate, endDate].
startTime: { lt: endDate },
endTime: { gt: startDate },
...(excludedUid ? { uid: { not: excludedUid } } : {}),
attendees: {
some: {
email: { in: attendeeEmails },
},
},
},
select: {
id: true,
startTime: true,
endTime: true,
title: true,
uid: true,
userId: true,
},
});
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This new query method introduces important overlap/exclusion semantics but doesn't appear to be covered by repository tests. Since BookingRepository.integration-test.ts already sets up real bookings/attendees, adding coverage for overlap boundaries (touching endpoints), multiple emails, and excludedUid would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines 621 to 633
const detailedBusyTimes: EventBusyDetails[] = [
...busyTimes.map((a) => ({
...a,
start: dayjs(a.start).toISOString(),
end: dayjs(a.end).toISOString(),
title: a.title,
source: params.withSource ? a.source : undefined,
})),
...busyTimesFromLimits,
...busyTimesFromTeamLimits,
// Guest busy times from the booking being rescheduled: prevents double-booking guests.
...(initialData?.guestBusyTimes ?? []),
];
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

guestBusyTimes is now merged into detailedBusyTimes, but there are no tests asserting that guest conflicts actually remove slots during rescheduling. Given the existing test suite around availability calculations, adding a unit test that injects initialData.guestBusyTimes and verifies dateRanges subtraction/slot filtering would improve confidence in this behavior.

Copilot uses AI. Check for mistakes.
@romitg2 romitg2 closed this Mar 2, 2026
@Moses-main
Copy link

Hi team, I'd like to contribute. Please assign!

@romitg2
Copy link
Member

romitg2 commented Mar 2, 2026

Hi team, I'd like to contribute. Please assign!

Hey @Moses-main stop commenting on PRs, you can pick up any issue form https://github.com/calcom/cal.com/issues and raise a PR for fixing that issue, avoid issue with "needs approval" label.

@Moses-main
Copy link

Hi @romitg2, you're absolutely right! Thanks for the guidance. I'll directly pick up an open issue, implement the fix, and create a PR instead of asking to be assigned.

Going forward, I'll:

  1. Find open issues without 'needs approval' label
  2. Fork, clone, and fix the issue
  3. Create a PR directly

Appreciate the feedback! 🙏

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

Labels

bookings area: bookings, availability, timezones, double booking 🙋 Bounty claim 💎 Bounty A bounty on Algora.io community Created by Linear-GitHub Sync ✨ feature New feature or request 🧹 Improvements Improvements to existing features. Mostly UX/UI Medium priority Created by Linear-GitHub Sync size/M $200

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CAL-4531] Take into account guest's availability when rescheduling

4 participants