feat: ✨ add to calendar on schedule#539
Open
kylebtran wants to merge 5 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
4 issues found across 19 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Contributor
There was a problem hiding this comment.
5 issues found across 21 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replaces the "Add to Calendar" prefilled-link flow with real Google Calendar API writes, and wires schedule commits / meeting deletion to fan out to every responding member's primary calendar.
OAuth
calendar.events.ownedscope to the Google login route so we can insert / patch / delete events on the user's behalf (in addition to the existing read-only scope).Schema
meeting_google_calendar_eventstable (migration0018) keyed on(meeting_id, member_id). Storesgoogle_calendar_id,google_event_id, alast_synced_snapshotjsonb (date,fromTime,toTime), andupdated_at. Cascades on meeting / member delete.Server actions (
src/server/actions/availability/google/calendar/action.ts)addOrUpdateMeetingGoogleCalendarEvent— idempotent per-member upsert. Uses a deterministic event ID (sha256(meetingId:memberId) → base32hex) so retries / races collapse to one event. Patches the tracked event if we have one, falls back to insert, recovers from 404 (deleted) and 409 (already exists), and persists the snapshot.removeMeetingGoogleCalendarEvent— tolerates 404/410 and clears the tracking row.syncMeetingToAllMemberCalendars/unsyncMeetingFromAllMemberCalendars— fan out across members who responded to the meeting and currently have a valid Google refresh token, with bounded concurrency (5) and a retry/backoff helper that only retries 5xx + Google rate-limit 403s.loadMergedScheduledInterval— merges contiguous 15-min blocks into the single{date, fromTime, toTime}snapshot the calendar event model uses.addMeetingToMyGoogleCalendar— single-member action invoked from the new button.Schedule commit (
commitMeetingSchedule)syncMeetingToAllMemberCalendars(non-empty schedule) orunsyncMeetingFromAllMemberCalendars(host cleared it) so previously-synced events don't linger. Fan-out runs post-commit so a Google API failure can't roll back the local schedule, and theFanOutOutcomeis surfaced in the UI snackbar.Archive / delete (
archiveMeeting)ArchiveMeetingResultwith the unsyncFanOutOutcome.DeleteModalnow uses a newshowWarningsnackbar variant to tell the host when some member calendars couldn't be cleaned up (e.g. revoked token), instead of silently succeeding.Per-user button (
add-to-calendar-button.tsx)getGoogleCalendarPrefilledLinkbutton. Derives one of four label states from the stored snapshot vs the current merged interval:add→ "Add to Calendar"in_sync→ "Added to Calendar" (disabled, check icon)drifted→ "Update Calendar Event" (refresh icon) when the schedule moved after the user already syncednon_contiguous→ button is hidden, since a multi-interval schedule can't be represented as one Google eventsrc/lib/google-calendar/snapshot.tsso the state machine is unit-testable.Page loading
/availability/[slug]loads availabilities, scheduled blocks, merged interval, and the per-user snapshot in parallel viaPromise.all.Recording/Screenshots
Before
After
Test Plan
calendar.events.ownedscope is granted.N added / N skipped / N failed.(meeting_id, member_id)inmeeting_google_calendar_eventswith a matchinglast_synced_snapshot.Issues
Future Follow-Up
{date, fromTime, toTime}snapshot produced byloadMergedScheduledIntervalFor/mergeContiguousTimeBlocks, which returnsnullfor any schedule that spans more than one date or has gaps. For weekly / recurring meetings we'd need to either (a) emit a GoogleRRULErecurring event with the appropriateBYDAY/UNTILand store the recurrence inlast_synced_snapshot, or (b) keep one tracked event per occurrence (extendmeeting_google_calendar_eventsto be keyed on(meeting_id, member_id, occurrence_key)and adapt the fan-out + drift detection accordingly). The button'snon_contiguousstate and the schedule-commit fan-out are the two places to teach about the new shape.