Calendar list and selection (Feature 16) #126

Member

Task

Maestro task 0fe3f1f2-4932-4d10-81dd-74c4f630d215 — "Calendar list and selection" (Feature 16, task 2 of 5).

Done-condition

GET /api/v1/integrations/google/calendars uses the stored refresh token to call the Calendar API calendarList endpoint and returns [{id, summary, primary, accessRole}] filtered to calendars with writer or owner access; PUT /api/v1/integrations/google/calendar {calendar_id} validates the id against that list and stores calendar_id + calendar_name. Tests against a fake Calendar API cover listing, filtering out reader-only calendars, selecting a valid calendar, rejecting an unknown id, and the not-connected case returning a clear 409.

Scope

Listing and selection only. Push to Calendar (5885d2b1), token-failure bookkeeping (b8d8da5a), the settings screen (677261bc) and Feature 17's shopping-list trigger are untouched. The developer states in NOTES what it chose when a Calendar call here meets an expired or revoked grant, rather than half-building task b8d8da5a's disconnect semantics or hiding the failure.

Points for review

  • The listing returns exactly the named fields and nothing else from Google's response (the fake deliberately adds description, summaryOverride, colours and reminders) and filters to writer/owner (the fake includes reader and freeBusyReader).
  • Selection validates the client's id against the same filtered list rather than trusting it, stores id and name, and rejects an unknown id with a clear error (422 unknown_calendar, stored row unchanged).
  • The not-connected case answers 409 not_connected without calling Google.
  • internal/gcal/calendars.go is new and reuses the reviewed gcal.Service machinery, including the per-account mutex for anything reading or writing the credential, and the token-secrecy discipline (nothing in a response, header, log or error). It adds Config.CalendarAPI (a base URL the binary supplies, with a GoogleCalendarAPI constant) so the fake can be pointed at — not a new env var or boot requirement; please confirm that.
  • migrations/queries/settings.sql gained SetIntegrationsGoogleCalendar :execrows and sqlc was regenerated (committed, no drift); no new migration.

Evidence (orchestrator-run gate at 1e3e42e0b1)

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

The developer also ran deliberate mutation checks (removing the writer/owner filter, trusting the client's id, dropping the lock in SelectCalendar, dropping the invalid_grant mapping, stopping paging, changing the summary, leaking Google's body or the refresh error into error text) and reports each made at least one new test fail.

Implementation commit: 1e3e42e0b14d3519d22e939af0382a9ca65f93dc

## Task Maestro task `0fe3f1f2-4932-4d10-81dd-74c4f630d215` — "Calendar list and selection" (Feature 16, task 2 of 5). ## Done-condition `GET /api/v1/integrations/google/calendars` uses the stored refresh token to call the Calendar API calendarList endpoint and returns `[{id, summary, primary, accessRole}]` filtered to calendars with writer or owner access; `PUT /api/v1/integrations/google/calendar {calendar_id}` validates the id against that list and stores `calendar_id` + `calendar_name`. Tests against a fake Calendar API cover listing, filtering out reader-only calendars, selecting a valid calendar, rejecting an unknown id, and the not-connected case returning a clear 409. ## Scope Listing and selection only. Push to Calendar (`5885d2b1`), token-failure bookkeeping (`b8d8da5a`), the settings screen (`677261bc`) and Feature 17's shopping-list trigger are untouched. The developer states in NOTES what it chose when a Calendar call here meets an expired or revoked grant, rather than half-building task `b8d8da5a`'s disconnect semantics or hiding the failure. ## Points for review - The listing returns exactly the named fields and nothing else from Google's response (the fake deliberately adds `description`, `summaryOverride`, colours and reminders) and filters to writer/owner (the fake includes reader and freeBusyReader). - Selection validates the client's id against the same filtered list rather than trusting it, stores id and name, and rejects an unknown id with a clear error (422 `unknown_calendar`, stored row unchanged). - The not-connected case answers 409 `not_connected` without calling Google. - `internal/gcal/calendars.go` is new and reuses the reviewed `gcal.Service` machinery, including the per-account mutex for anything reading or writing the credential, and the token-secrecy discipline (nothing in a response, header, log or error). It adds `Config.CalendarAPI` (a base URL the binary supplies, with a `GoogleCalendarAPI` constant) so the fake can be pointed at — not a new env var or boot requirement; please confirm that. - `migrations/queries/settings.sql` gained `SetIntegrationsGoogleCalendar :execrows` and sqlc was regenerated (committed, no drift); no new migration. ## Evidence (orchestrator-run gate at 1e3e42e0b14d3519d22e939af0382a9ca65f93dc) - `gofmt -l .` — empty; `go vet ./...` — clean - `go test -race -count=1 ./...` — 26 packages ok, 0 failures (server 129.2s) - `CGO_ENABLED=0 go build ./...` — ok - `make sqlc-check SQLC=$(go env GOPATH)/bin/sqlc` — no drift - `cd web && npm run typecheck && npm test && npm run build` — PASS, 45 files / 764 tests (no web change) The developer also ran deliberate mutation checks (removing the writer/owner filter, trusting the client's id, dropping the lock in SelectCalendar, dropping the invalid_grant mapping, stopping paging, changing the summary, leaking Google's body or the refresh error into error text) and reports each made at least one new test fail. Implementation commit: `1e3e42e0b14d3519d22e939af0382a9ca65f93dc`
orchestrator-agent added 1 commit 2026-10-07 21:22:39 -04:00
List Google calendars and choose the one to push to (Feature 16)
CI / ci (push) Successful in 3m49s
CI / ci (pull_request) Successful in 3m34s
1e3e42e0b1
GET /api/v1/integrations/google/calendars trades the stored refresh token
for an access token, reads every calendarList page and answers
[{id, summary, primary, accessRole}] for writer and owner calendars only.
PUT /api/v1/integrations/google/calendar {calendar_id} validates the id
against that same filtered listing and stores calendar_id and
calendar_name; any other id is 422 unknown_calendar.

Both hold the service's account lock, answer 409 not_connected with
nothing connected, and 502 reconnect_required when Google no longer
accepts the grant, leaving the connection as it is. The gcaltest fake
gains the refresh grant and a pausable calendarList endpoint.

Maestro task 0fe3f1f2-4932-4d10-81dd-74c4f630d215.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent approved these changes 2026-10-07 21:24:35 -04:00
Dismissed
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.
overseer added 1 commit 2026-10-07 21:36:40 -04:00
List each Google calendar once when calendarList pages overlap
CI / ci (push) Successful in 3m41s
CI / ci (pull_request) Successful in 3m41s
d23d30e695
Deduplicate by calendar id as the pages are collected, keeping the
first occurrence in Google's order (MQ-126-01), with a test serving
two overlapping pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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 482bec9727 into feature/c53328cb-577c-4aea-b4b2-817acfc3dfeb-google-calendar-sync 2026-10-07 21:57:47 -04:00
Sign in to join this conversation.
No Reviewers
No labels
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#126