diff --git a/rowing-courses-spec.md b/rowing-courses-spec.md index 53348bf1..1bf127b7 100644 --- a/rowing-courses-spec.md +++ b/rowing-courses-spec.md @@ -264,20 +264,31 @@ POST /rowers/courses/{id}/unfollow/ ``` Add or remove a course ID from the `liked:{athlete_id}` KV entry. Return 200 on success. -### 1.7 Course map browser (GitHub Pages) +``` +GET /api/me +``` +Returns `{athleteId, liked: [...]}` if a valid `rn_session` cookie is present, otherwise 401. Used by the GitHub Pages browser on load to determine login state and populate like buttons. Also used to verify session liveness without a full page reload. -A single-page Leaflet application served from the `site/` directory. Features: +### 1.7 Course map browser (GitHub Pages + Worker) +The map browser is a single-page Leaflet application served as static files from the `site/` directory via GitHub Pages. DNS routes `rownative.icu/*` to GitHub Pages and `rownative.icu/api/*` to the Cloudflare Worker. Static assets (HTML, JS, CSS, `index.json`, KML files) are served for free with no compute; the Worker only wakes up for API calls. + +**Static features (no Worker involved):** - Map centred on user geolocation on first load, falling back to world view. -- Loads `courses/index.json` on init; renders a marker per course. -- Clicking a marker shows course name, distance, country, status badge, and a link to the course detail page. -- Course detail page fetches the full JSON and renders the polygon chain on the map. +- Loads `index.json` on init (raw GitHub URL); renders a marker per course. +- Clicking a marker shows course name, distance, country, status badge. +- Course detail view fetches the full course JSON and renders the polygon chain on the map. - Filter controls: country dropdown, distance range slider, status toggle (provisional / established / both). - Search by name (client-side, against the index). -- "Submit a course" link leading to the submission form. -- KML download button per course (links to `kml/{id}.kml`). +- KML download button per course (links to static `kml/{id}.kml`). -No backend calls needed for browsing — entirely static. +**Dynamic features (require Worker + login):** +- On page load, the browser calls `GET /api/me`. If a valid session cookie is present the Worker returns `{athleteId, liked: [...]}` and the page renders like/unlike buttons accordingly. If not, a "Sign in with intervals.icu" button is shown instead. +- Like/unlike buttons POST to `/api/rowers/courses/{id}/follow/` and `/unfollow/`. The page updates optimistically; the Worker updates KV. +- "Submit a course" form POSTs to `POST /api/courses/submit` (KML upload → GitHub PR). Requires login. +- ZIP import form POSTs to `POST /api/courses/import-zip`. Requires login. + +**CORS:** Worker responses for `/api/*` include `Access-Control-Allow-Origin: https://rownative.icu` and `Access-Control-Allow-Credentials: true` so the GitHub Pages origin can make credentialed `fetch()` calls carrying the session cookie. ### 1.8 Infrastructure setup (Stage 1) @@ -373,25 +384,31 @@ Users are notified of this migration path via the Rowsandall shutdown announceme **Cloudflare Worker:** - [ ] `wrangler.toml` with KV binding and secrets +- [ ] CORS headers on all `/api/*` responses (`Allow-Origin: https://rownative.icu`, `Allow-Credentials: true`) - [ ] intervals.icu OAuth login flow (`GET /oauth/authorize`, `GET /oauth/callback`) - [ ] Encrypted HTTP-only cookie (`rn_session`) — AES-GCM encrypt/decrypt of `{athleteId, accessToken, refreshToken, expiresAt}` -- [ ] HMAC-derived CrewNerd API key (`apiKeyForAthlete()`) — shown on user profile page -- [ ] Platform API key verification on incoming CrewNerd requests +- [ ] Token refresh on expired `expiresAt` — rewrite cookie with fresh tokens +- [ ] HMAC-derived CrewNerd API key (`apiKeyForAthlete()`) — format `{athleteId}.{mac}` +- [ ] API key verification on incoming CrewNerd requests (split, recompute, constant-time compare) +- [ ] `GET /api/me` — return `{athleteId, liked}` from session cookie or 401 - [ ] `GET /api/courses/` — course index with geo filtering - [ ] `GET /api/courses/{id}/` — single course KML - [ ] `GET /api/courses/kml/liked/` — liked courses KML bundle - [ ] `GET /api/courses/kml/` — multi-course KML bundle - [ ] `POST /rowers/courses/{id}/follow/` and `/unfollow/` +- [ ] `POST /api/auth/crewnerd` — bearer token exchange for API key - [ ] `POST /api/courses/submit` — KML upload → GitHub PR - [ ] `POST /api/courses/import-zip` — ZIP import: check each owned ID against `index.json`, open PR only for IDs not already present; restore liked list in KV unconditionally; return summary to user - [ ] KML generation logic (port of `courses.py`: `coursetokml`, `getcoursefolder`, `crewnerdify`, `sort_coordinates_ccw`) **GitHub Pages site:** -- [ ] Leaflet map browser with course markers and detail view -- [ ] Filter controls (country, distance, status) -- [ ] Course submission form (KML upload) -- [ ] ZIP import form (for Rowsandall migrants) -- [ ] "Sign in with intervals.icu" link +- [ ] Leaflet map with course markers and polygon detail view +- [ ] Filter controls (country, distance, status) and name search +- [ ] Login state: call `GET /api/me` on load; show "Sign in" button or like buttons accordingly +- [ ] Like/unlike buttons (optimistic UI, POST to Worker) +- [ ] Course submission form (KML upload, requires login) +- [ ] ZIP import form (for Rowsandall migrants, requires login) +- [ ] DNS routing: `rownative.icu/*` → GitHub Pages, `rownative.icu/api/*` → Worker ---