Per-client ICS calendar feed #22

Closed
opened 2026-07-23 14:07:59 +02:00 by mivanchenko · 1 comment
Owner

Parent

#14

What to build

A subscribable external calendar feed for the owner, scoped only to their own bookings, replacing
the current shared-token feed that lets any token-holder view any client's bookings by changing a
query parameter.

  • /api/bookings.ics (or equivalent) gated by the per-client ics_token (from ticket 1's
    clients.ics_token column) instead of the current single shared ICS_TOKEN + client_id query
    param.
  • The owner dashboard surfaces their own subscribe URL (with their token embedded) so they can add
    it to their phone/desktop calendar app.

Acceptance criteria

  • Client A's ics_token only ever returns client A's bookings — client B's token cannot be
    substituted to view client A's feed, and there is no way to view another client's feed by
    varying a query parameter alone.
  • The owner dashboard shows a copyable subscribe URL containing the owner's own token.
  • The feed content (event titles, times) matches the existing bookings.ics format already
    used by calendar apps today, so existing subscriptions aren't broken in an unexpected way at
    cutover.

Blocked by

  • #15 (Booking schema + tenancy-safe data access layer)
  • #19 (Owner accounts: auth, password reset, CRM dashboard visibility)
## Parent #14 ## What to build A subscribable external calendar feed for the owner, scoped only to their own bookings, replacing the current shared-token feed that lets any token-holder view any client's bookings by changing a query parameter. - `/api/bookings.ics` (or equivalent) gated by the per-client `ics_token` (from ticket 1's `clients.ics_token` column) instead of the current single shared `ICS_TOKEN` + `client_id` query param. - The owner dashboard surfaces their own subscribe URL (with their token embedded) so they can add it to their phone/desktop calendar app. ## Acceptance criteria - [ ] Client A's `ics_token` only ever returns client A's bookings — client B's token cannot be substituted to view client A's feed, and there is no way to view another client's feed by varying a query parameter alone. - [ ] The owner dashboard shows a copyable subscribe URL containing the owner's own token. - [ ] The feed content (event titles, times) matches the existing `bookings.ics` format already used by calendar apps today, so existing subscriptions aren't broken in an unexpected way at cutover. ## Blocked by - #15 (Booking schema + tenancy-safe data access layer) - #19 (Owner accounts: auth, password reset, CRM dashboard visibility)
mivanchenko added the ready-for-agentenhancementbooking labels 2026-07-23 14:07:59 +02:00
mivanchenko added a new dependency 2026-07-23 14:09:17 +02:00
mivanchenko added a new dependency 2026-07-23 14:09:19 +02:00
Author
Owner

Implemented in 24d9aca.

  • clients.ics_token is now generated lazily (booking_db.ensure_ics_token) on first /owner/settings visit -- no separate backfill migration needed for existing clients.
  • /api/bookings.ics?token=... resolves the client directly from the token (get_client_by_ics_token); the old shared ICS_TOKEN env var and the client_id query param are both gone, so there's no way to view another client's feed by varying a parameter. Added a partial unique index on clients.ics_token to guarantee the token->client mapping is 1:1.
  • The owner settings page now shows a copyable subscribe URL with the client's own token embedded.
  • Feed content/format (VEVENT fields, DTSTART/DTEND, etc.) is unchanged from the existing implementation, so working subscriptions keep working once the owner re-subscribes with their new per-client URL.

Acceptance criteria:

  • Client A's token only returns client A's bookings; client_id can no longer be swapped to view another client's feed (covered by test_swapping_client_id_query_param_has_no_effect).
  • Owner dashboard shows a copyable subscribe URL containing the owner's own token.
  • Feed content format matches the existing bookings.ics output.

Full test suite (131 tests) green via the Docker-based runner.

Implemented in 24d9aca. - `clients.ics_token` is now generated lazily (`booking_db.ensure_ics_token`) on first `/owner/settings` visit -- no separate backfill migration needed for existing clients. - `/api/bookings.ics?token=...` resolves the client directly from the token (`get_client_by_ics_token`); the old shared `ICS_TOKEN` env var and the `client_id` query param are both gone, so there's no way to view another client's feed by varying a parameter. Added a partial unique index on `clients.ics_token` to guarantee the token->client mapping is 1:1. - The owner settings page now shows a copyable subscribe URL with the client's own token embedded. - Feed content/format (VEVENT fields, DTSTART/DTEND, etc.) is unchanged from the existing implementation, so working subscriptions keep working once the owner re-subscribes with their new per-client URL. Acceptance criteria: - [x] Client A's token only returns client A's bookings; client_id can no longer be swapped to view another client's feed (covered by `test_swapping_client_id_query_param_has_no_effect`). - [x] Owner dashboard shows a copyable subscribe URL containing the owner's own token. - [x] Feed content format matches the existing bookings.ics output. Full test suite (131 tests) green via the Docker-based runner.
Sign in to join this conversation.