Feature 16: Google Calendar Sync #130

Merged
approver_agent merged 5 commits from feature/c53328cb-577c-4aea-b4b2-817acfc3dfeb-google-calendar-sync into dev 2026-10-08 03:13:22 -04:00
Member

Feature 16 — Google Calendar Sync

Integration pull request: feature/c53328cb-577c-4aea-b4b2-817acfc3dfeb-google-calendar-sync -> dev.

One-way push of the weekly dinner schedule into a shared Google Calendar, with the app as source of truth (requirements §8 [Decided]; owner chose Google OAuth in Settings — D-011; CTXD RUNBOOKS "Google Cloud setup").

Tasks (all five merged, each independently reviewed at its exact head)

Task Maestro PR Reviewed head
Google OAuth connect and disconnect 1e9d7850 #125 2cd2e5a
Calendar list and selection 0fe3f1f2 #126 d23d30e
Push plan to Google Calendar 5885d2b1 #127 fb7d8dd
Token failure handling b8d8da5a #128 ee67d52
Calendar settings screen and plan sync status 677261bc #129 3a1884d

What the feature delivers

  • Google OAuth from Settings: GET /api/v1/integrations/google/connect (session-bound state, PKCE, access_type=offline, prompt=consent), the callback exchanging the code with the file-sourced secret under MEALQUEST_PUBLIC_URL, the refresh token encrypted with secretbox, and DELETE revoking at Google and disconnecting. One grantedConnection decider reports not-connected / refused-grant / missing-calendar in a fixed order.
  • Calendar list and selection (GET .../calendars, PUT .../calendar) filtered to writer/owner, with one forgetEvents invalidation helper shared by a selection change and a disconnect, so no stored plan-day event id and no synced plan outlives the calendar it belongs to.
  • POST /api/v1/plans/{weekStart}/push-calendar writing one deterministic-id event per day (summary "Dinner: {name}", household dinner_time for 60 minutes, description with the crock-pot "Start by" and a dinner-page link), insert-then-update on 409, recording calendar_synced_at and each day's calendar_event_id under the account lock only after every write succeeds.
  • Token-failure handling: invalid_grant or a 401 after refresh on any Calendar call clears the token, sets last_sync_error, and yields 409 google_reconnect_required on push and reconnect_required from the new GET /api/v1/integrations/google; transient 429/5xx retry twice then surface a retryable 503 without disconnecting.
  • The Settings → Google Calendar screen (connect link, calendar picker, Connected · {calendar}, Disconnect with confirmation, grape reconnect banner) and the plan screens' real Push to Calendar buttons with progress and error states.

Integration checks at the feature head 081ad75fe256c99ae51082844242f3cd7e83be34 (5 commits ahead of dev, 0 behind)

  • gofmt -l . — empty; go vet ./... — clean
  • go test -race -count=1 ./... — 26 packages ok, 0 failures
  • CGO_ENABLED=0 go build ./... — ok
  • make sqlc-check SQLC=$(go env GOPATH)/bin/sqlc — no drift
  • cd web && npm run typecheck — PASS; npm test — PASS 46 files / 804 tests; npm run build — PASS
  • make test, make build, docker build . — PASS

The branch already contains all of dev, so no dev merge was needed.

Deployment prerequisite — please read

MEALQUEST_GOOGLE_CLIENT_ID and MEALQUEST_GOOGLE_CLIENT_SECRET_FILE are required at boot (fail-fast, matching the OIDC variables). A release containing this feature will not start until the owner creates the Google OAuth web client and sets the secret on the host. The steps are in the runbook section "Google Calendar OAuth client", and deploy/compose.yaml / deploy/.env.example already carry the mount and variables.

Notes

  • Migration 0015_google_reconnect_required.sql makes integrations_google.refresh_token_ct nullable with a CHECK that a row without a token carries a last_sync_error. The independent release-style review examined it and confirmed it is safe to apply to the existing household database at boot.
  • Non-blocking findings were dispositioned and are tracked as Maestro task d21e4a83 in Feature 22 (the same plan-screen hardening task, which the push mutation's retention fix has now partly closed).
  • The real production push to the household "Dinners" calendar with a screenshot was split out into Maestro task 9a4d2601 in Feature 22 (owner-dependent: it needs the real OAuth client and the deployed service).
## Feature 16 — Google Calendar Sync Integration pull request: `feature/c53328cb-577c-4aea-b4b2-817acfc3dfeb-google-calendar-sync` -> `dev`. One-way push of the weekly dinner schedule into a shared Google Calendar, with the app as source of truth (requirements §8 [Decided]; owner chose Google OAuth in Settings — D-011; CTXD RUNBOOKS "Google Cloud setup"). ## Tasks (all five merged, each independently reviewed at its exact head) | Task | Maestro | PR | Reviewed head | | --- | --- | --- | --- | | Google OAuth connect and disconnect | 1e9d7850 | #125 | 2cd2e5a | | Calendar list and selection | 0fe3f1f2 | #126 | d23d30e | | Push plan to Google Calendar | 5885d2b1 | #127 | fb7d8dd | | Token failure handling | b8d8da5a | #128 | ee67d52 | | Calendar settings screen and plan sync status | 677261bc | #129 | 3a1884d | ## What the feature delivers - Google OAuth from Settings: `GET /api/v1/integrations/google/connect` (session-bound state, PKCE, `access_type=offline`, `prompt=consent`), the callback exchanging the code with the file-sourced secret under `MEALQUEST_PUBLIC_URL`, the refresh token encrypted with secretbox, and `DELETE` revoking at Google and disconnecting. One `grantedConnection` decider reports not-connected / refused-grant / missing-calendar in a fixed order. - Calendar list and selection (`GET .../calendars`, `PUT .../calendar`) filtered to writer/owner, with one `forgetEvents` invalidation helper shared by a selection change and a disconnect, so no stored plan-day event id and no `synced` plan outlives the calendar it belongs to. - `POST /api/v1/plans/{weekStart}/push-calendar` writing one deterministic-id event per day (summary "Dinner: {name}", household dinner_time for 60 minutes, description with the crock-pot "Start by" and a dinner-page link), insert-then-update on 409, recording `calendar_synced_at` and each day's `calendar_event_id` under the account lock only after every write succeeds. - Token-failure handling: invalid_grant or a 401 after refresh on any Calendar call clears the token, sets `last_sync_error`, and yields 409 `google_reconnect_required` on push and `reconnect_required` from the new `GET /api/v1/integrations/google`; transient 429/5xx retry twice then surface a retryable 503 without disconnecting. - The Settings → Google Calendar screen (connect link, calendar picker, Connected · {calendar}, Disconnect with confirmation, grape reconnect banner) and the plan screens' real Push to Calendar buttons with progress and error states. ## Integration checks at the feature head `081ad75fe256c99ae51082844242f3cd7e83be34` (5 commits ahead of `dev`, 0 behind) - `gofmt -l .` — empty; `go vet ./...` — clean - `go test -race -count=1 ./...` — 26 packages ok, 0 failures - `CGO_ENABLED=0 go build ./...` — ok - `make sqlc-check SQLC=$(go env GOPATH)/bin/sqlc` — no drift - `cd web && npm run typecheck` — PASS; `npm test` — PASS 46 files / 804 tests; `npm run build` — PASS - `make test`, `make build`, `docker build .` — PASS The branch already contains all of `dev`, so no `dev` merge was needed. ## Deployment prerequisite — please read `MEALQUEST_GOOGLE_CLIENT_ID` and `MEALQUEST_GOOGLE_CLIENT_SECRET_FILE` are **required at boot** (fail-fast, matching the OIDC variables). A release containing this feature will not start until the owner creates the Google OAuth web client and sets the secret on the host. The steps are in the runbook section "Google Calendar OAuth client", and `deploy/compose.yaml` / `deploy/.env.example` already carry the mount and variables. ## Notes - Migration `0015_google_reconnect_required.sql` makes `integrations_google.refresh_token_ct` nullable with a CHECK that a row without a token carries a `last_sync_error`. The independent release-style review examined it and confirmed it is safe to apply to the existing household database at boot. - Non-blocking findings were dispositioned and are tracked as Maestro task `d21e4a83` in Feature 22 (the same plan-screen hardening task, which the push mutation's retention fix has now partly closed). - The real production push to the household "Dinners" calendar with a screenshot was split out into Maestro task `9a4d2601` in Feature 22 (owner-dependent: it needs the real OAuth client and the deployed service).
orchestrator-agent added 5 commits 2026-10-08 02:56:55 -04:00
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
Add the Google Calendar settings screen and wire Push to Calendar (Feature 16) (#129)
CI / ci (push) Successful in 5m1s
CI / ci (pull_request) Successful in 4m22s
081ad75fe2
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
review_agent left a comment
Member

Independent Codex gpt-6-sol review: APPROVED for this exact commit.

Independent Codex gpt-6-sol review: APPROVED for this exact commit.
approver_agent merged commit 7003ff92ec into dev 2026-10-08 03:13:22 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#130