196 lines
9.1 KiB
Markdown
196 lines
9.1 KiB
Markdown
# 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 won’t 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`
|