Files
motovaultpro/STATION-CHANGES.md
2025-12-14 12:00:42 -06:00

196 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Stations (Gas/Fuel) Feature — Dispatchable Change Plan
This document is written as an execution plan that can be handed to multiple AI agents to implement in parallel.
## Repo Constraints (Must Follow)
- Docker-first workflow (production builds): validate changes via `make rebuild` and container logs.
- Mobile + Desktop requirement: every UI change must be validated on both `frontend/src/features/stations/pages/StationsPage.tsx` (desktop) and `frontend/src/features/stations/mobile/StationsMobileScreen.tsx` (mobile).
- Never expose the Google Maps API key to the browser or logs.
## Scope
1. Fix broken station photo rendering on stations UI after the “hide Google API key” change.
2. Add navigation links for saved/favorite stations:
- “Navigate in Google” (Google Maps)
- “Navigate in Apple Maps” (Apple Maps)
- “Navigate in Waze” (Waze)
## Bug: Station Photos Not Displaying
### Current Implementation (What Exists Today)
- Frontend cards render an `<img>` via MUI `CardMedia` when `station.photoReference` is present:
- `frontend/src/features/stations/components/StationCard.tsx`
- URL generation: `frontend/src/features/stations/utils/photo-utils.ts``/api/stations/photo/:reference`
- Backend exposes a proxy endpoint that fetches the Google Places photo (server-side, using the secret key):
- Route: `GET /api/stations/photo/:reference`
- `backend/src/features/stations/api/stations.routes.ts`
- `backend/src/features/stations/api/stations.controller.ts`
- Google client: `backend/src/features/stations/external/google-maps/google-maps.client.ts` (`fetchPhoto`)
### Likely Root Cause (Agents Must Confirm)
The photo endpoint is protected by `fastify.authenticate`, but `<img src="...">` requests do not include the Authorization header. This results in `401 Unauthorized` responses and broken images.
Second thing to confirm while debugging:
- Verify what `Station.photoReference` contains at runtime:
- expected: Google `photo_reference` token
- risk: code/docs mismatch where `photoReference` became a URL like `/api/stations/photo/{reference}`, causing double-encoding by `getStationPhotoUrl()`.
### Repro Checklist (Fast Confirmation)
- Open stations page and observe broken images in browser devtools Network:
- `GET /api/stations/photo/...` should show `401` if auth-header issue is the cause.
- Confirm backend logs show JWT auth failure for photo requests.
## Decision: Image Strategy (Selected)
Selected: **Option A1** (keep images; authenticated blob fetch in frontend; photo endpoint remains JWT-protected).
### Option A (Keep Images): Fix Auth Mismatch Without Exposing API Key
#### Option A1 (Recommended): Fetch Photo as Blob via Authenticated XHR
Why: Keeps `/api/stations/photo/:reference` protected (prevents public key abuse), avoids putting JWT in query params, and avoids exposing the Google API key.
Implementation outline:
- Frontend: replace direct `<img src="/api/stations/photo/...">` usage with an authenticated fetch that includes the JWT (via existing Axios `apiClient` interceptors), then render via `blob:` object URL.
- Add a small component like `StationPhoto` used by `StationCard`:
- `apiClient.get('/stations/photo/:reference', { responseType: 'blob' })`
- `URL.createObjectURL(blob)` for display
- `URL.revokeObjectURL` cleanup on unmount / reference change
- graceful fallback (hide image) on 401/500
- Backend: no route auth changes required.
Tradeoffs:
- Slightly more frontend code, but minimal security risk.
- Must ensure caching behavior is acceptable (browser cache wont cache `blob:` URLs; rely on backend caching headers + client-side memoization).
#### Option A2 (Simplest Code, Higher Risk): Make Photo Endpoint Public
Why: Restores `<img>` behavior with minimal frontend work.
Implementation outline:
- Backend: remove `preHandler: [fastify.authenticate]` from `/stations/photo/:reference`.
- Add lightweight protections to reduce abuse (choose as many as feasible without adding heavy deps):
- strict input validation (length/charset) for `reference`
- low maxWidth clamp and no arbitrary URL fetching
- maintain `Cache-Control` header (already present)
- optionally add server-side rate limit (only if repo already uses a rate-limit plugin; avoid introducing new infra unless necessary)
Tradeoffs:
- Anyone can hit `/api/stations/photo/:reference` and spend your Google quota.
### Option B (Remove Images): Simplify Cards
Why: If image delivery adds too much complexity or risk, remove images from station cards.
Implementation outline:
- Frontend: remove `CardMedia` photo block from `StationCard` and any other station photo rendering.
- Leave `photoReference` in API/types untouched for now (or remove later as a cleanup task, separate PR).
- Update any tests that assert on image presence.
Tradeoffs:
- Reduced UX polish, but simplest and most robust.
## Feature: Navigation Links on Saved/Favorite Stations
### UX Requirements
- On saved station UI (desktop + mobile), provide 3 explicit navigation options:
- Google Maps
- Apple Maps
- Waze
- “Saved/favorite” is interpreted as “stations in the Saved list”; favorites are a subset.
### URL Construction (Preferred)
Use coordinates if available; fall back to address query if not.
- Google Maps:
- Preferred: `https://www.google.com/maps/dir/?api=1&destination=LAT,LNG&destination_place_id=PLACE_ID`
- Fallback: `https://www.google.com/maps/search/?api=1&query=ENCODED_QUERY`
- Apple Maps:
- Preferred: `https://maps.apple.com/?daddr=LAT,LNG`
- Fallback: `https://maps.apple.com/?q=ENCODED_QUERY`
- Waze:
- Preferred: `https://waze.com/ul?ll=LAT,LNG&navigate=yes`
- Fallback: `https://waze.com/ul?q=ENCODED_QUERY&navigate=yes`
Important: some saved stations may have `latitude/longitude = 0` if cache miss; treat `(0,0)` as “no coordinates”.
### UI Placement Recommendation
- Desktop saved list: add a “Navigate” icon button that opens a small menu with the 3 links (cleaner than inline links inside `ListItemText`).
- File: `frontend/src/features/stations/components/SavedStationsList.tsx`
- Mobile bottom sheet (station details): add a “Navigate” section with the same 3 links (buttons or list items).
- File: `frontend/src/features/stations/mobile/StationsMobileScreen.tsx`
## Work Breakdown for Multiple Agents
### Agent 1 — Confirm Root Cause + Backend Adjustments (If Needed)
Deliverables:
- Confirm whether photo requests return `401` due to missing Authorization.
- Confirm whether `photoReference` is a raw reference token vs a URL string.
- Implement backend changes only if Option A2 is chosen.
Files likely touched (Option A2 only):
- `backend/src/features/stations/api/stations.routes.ts` (remove auth preHandler on photo route)
- `backend/src/features/stations/api/stations.controller.ts` (add stricter validation; keep cache headers)
- `backend/src/features/stations/docs/API.md` (update auth expectations for photo endpoint)
### Agent 2 — Frontend Photo Fix (Option A1) OR Photo Removal (Option B)
Deliverables:
- Option A1: implement authenticated blob photo loading for station cards.
- Option B: remove station photos from cards cleanly (no layout regressions).
Files likely touched:
- `frontend/src/features/stations/components/StationCard.tsx`
- Option A1:
- Add `frontend/src/features/stations/components/StationPhoto.tsx` (or similar)
- Potentially update `frontend/src/features/stations/utils/photo-utils.ts`
- Add unit tests under `frontend/src/features/stations/__tests__/`
### Agent 3 — Navigation Links for Saved Stations (Desktop + Mobile)
Deliverables:
- Create a single URL-builder utility with tests.
- Add a “Navigate” menu/section in saved stations UI (desktop + mobile).
Files likely touched:
- `frontend/src/features/stations/utils/` (new `navigation-links.ts`)
- `frontend/src/features/stations/components/SavedStationsList.tsx`
- `frontend/src/features/stations/mobile/StationsMobileScreen.tsx`
- Optional: reuse in `frontend/src/features/stations/components/StationCard.tsx` (only if product wants it outside Saved)
### Agent 4 — Tests + QA Pass (Update What Breaks)
Deliverables:
- Update/extend tests to cover:
- navigation menu/links present for saved stations
- photo rendering behavior per chosen option
- Ensure both desktop and mobile flows still pass basic E2E checks.
Files likely touched:
- `frontend/cypress/e2e/stations.cy.ts`
- `frontend/src/features/stations/__tests__/components/StationCard.test.tsx`
- New tests for `navigation-links.ts`
## Acceptance Criteria
- Station photos render on station cards via Option A1 without exposing Google API key (no `401` responses for photo requests in Network).
- Saved stations show 3 navigation options (Google, Apple, Waze) on both desktop and mobile.
- No lint/test regressions; container build succeeds.
## Validation (Container-First)
- Rebuild and watch logs: `make rebuild` then `make logs`
- Optional focused logs: `make logs-frontend` and `make logs-backend`
- Run feature tests where available (prefer container exec):
- Backend: `docker compose exec mvp-backend npm test -- features/stations`
- Frontend: `docker compose exec mvp-frontend npm test -- stations`
- E2E: `docker compose exec mvp-frontend npm run e2e`