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

9.1 KiB
Raw Blame History

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

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.

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__/

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