From b016fd820763a3f3ecea254bd359b2514be74bdf Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Tue, 12 May 2026 04:34:39 +0300 Subject: [PATCH] [AZ-498] [AZ-499] Cycle 2 batch 11: satellite tiles + OWM hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AZ-498 — self-hosted satellite tiles + drop classic/satellite toggle: - Single TILE_URL via getTileUrl() (mirrors getOwmBaseUrl/getApiBase pattern from AZ-449/AZ-450); env-var VITE_SATELLITE_TILE_URL with dev default http://localhost:5100/tiles/{z}/{x}/{y}. - FlightMap + MiniMap render one TileLayer with crossOrigin="use-credentials" so Leaflet's tile fetcher attaches the same-origin satellite-provider auth cookie. - ImportMetaEnv + .env.example collapse the prior OSM/Esri pair into one var. The flights.planner.satellite i18n key is removed in lockstep across en.json + ua.json (parity preserved). - E2E harness wired end-to-end: compose passes the new var to azaion-ui; tile-stub serves /tiles/{z}/{x}/{y} with Content-Type=image/jpeg + Cache-Control + ETag matching the contract; infrastructure.e2e.ts AC-2 asserts the new path; dead OSM defenses removed from EXTERNAL_HOSTS route guard. - Fast-profile MSW handlers rewritten for the cookie-auth path shape. - 8 colocated fast tests under src/features/flights/__tests__/. AZ-499 — mission-planner OWM env-var hardening + AZ-482 source-scan gap close: - WeatherService.ts reads VITE_OWM_API_KEY + VITE_OWM_BASE_URL; fail-soft null when key unset (mirrors AZ-448 main-SPA contract). Public signature getWeatherData(lat, lon) preserved. - mission-planner/.env.example + vite-env.d.ts declare both vars. - New owm_key_in_source banned-deps kind scans src/ AND mission-planner/ for the rotated literal; STC-SEC1C row added to scripts/run-tests.sh; check-banned-deps.mjs dispatch extended. - 7 fast tests under tests/mission_planner_weather.test.ts cover AC-1..AC-4 + trailing-slash + happy path + network-error fail-soft. Spec drift (recorded in batch_11_report.md, user-approved Choose B on 2026-05-12): - AZ-498 AC-8 dropped (named tile_split_zoom* files belong to AZ-474 image-annotation surface, not map tiles). - 4 missing files added in-scope (msw tiles handler, tile-stub server, compose env, dead VITE_TILE_BASE_URL replaced). - AZ-499 STC-S6 ID conflict resolved by using STC-SEC1C. Pending USER ACTION (BLOCKING for AZ-499 close): - Revoke OpenWeatherMap key 335799082893fad97fa36118b131f919 at home.openweathermap.org/api_keys; capture evidence on AZ-499. Cross-workspace deploy gate (handled at autodev Step 16, not a Step-10 blocker for AZ-498): - satellite-provider cookie-auth on GET /tiles/{z}/{x}/{y} (separate AZAION ticket on the satellite-provider workspace). Reports: _docs/03_implementation/batch_11_report.md and _docs/03_implementation/reviews/batch_11_review.md (verdict PASS_WITH_WARNINGS — 1 Low, pre-existing trim-trailing-slash duplication across vite roots). Static gates: STC-ARCH-01, STC-ARCH-02, STC-T1, STC-FP22, STC-FP23, STC-SEC1C all PASS post-refactor. +15 fast tests; +1 STC-SEC1C row. Co-authored-by: Cursor --- .env.example | 24 +- _docs/02_document/modules/mission-planner.md | 8 +- .../modules/src__features__flights.md | 11 +- .../AZ-498_satellite_tile_swap.md | 0 .../AZ-499_mission_planner_weather_env.md | 0 _docs/03_implementation/batch_11_report.md | 95 ++++++++ .../reviews/batch_11_review.md | 135 +++++++++++ _docs/_autodev_state.md | 16 +- e2e/docker-compose.suite-e2e.yml | 5 +- e2e/stubs/tile/server.ts | 31 ++- e2e/tests/infrastructure.e2e.ts | 23 +- mission-planner/.env.example | 24 ++ .../src/services/WeatherService.ts | 11 +- mission-planner/src/vite-env.d.ts | 2 + scripts/check-banned-deps.mjs | 3 +- scripts/run-tests.sh | 11 + src/features/flights/FlightMap.tsx | 17 +- src/features/flights/MiniMap.tsx | 7 +- .../flights/__tests__/satellite_tile.test.tsx | 217 ++++++++++++++++++ src/features/flights/types.ts | 23 +- src/i18n/en.json | 1 - src/i18n/ua.json | 1 - src/vite-env.d.ts | 3 +- tests/mission_planner_weather.test.ts | 116 ++++++++++ tests/msw/handlers/tiles.ts | 32 +-- tests/security/banned-deps.json | 8 + 26 files changed, 739 insertions(+), 85 deletions(-) rename _docs/02_tasks/{todo => done}/AZ-498_satellite_tile_swap.md (100%) rename _docs/02_tasks/{todo => done}/AZ-499_mission_planner_weather_env.md (100%) create mode 100644 _docs/03_implementation/batch_11_report.md create mode 100644 _docs/03_implementation/reviews/batch_11_review.md create mode 100644 src/features/flights/__tests__/satellite_tile.test.tsx create mode 100644 tests/mission_planner_weather.test.ts diff --git a/.env.example b/.env.example index 08d7d29..e6bc0ea 100644 --- a/.env.example +++ b/.env.example @@ -6,11 +6,14 @@ # # Every variable is OPTIONAL. When unset, the SPA falls back to production- # default behavior: -# - VITE_API_BASE_URL : '' (relative paths; SPA and suite share nginx) -# - VITE_OWM_API_KEY : undefined → getWeatherData returns null -# - VITE_OWM_BASE_URL : https://api.openweathermap.org/data/2.5 -# - VITE_OSM_TILE_URL : https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png -# - VITE_ESRI_TILE_URL : https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x} +# - VITE_API_BASE_URL : '' (relative paths; SPA and suite share nginx) +# - VITE_OWM_API_KEY : undefined → getWeatherData returns null +# - VITE_OWM_BASE_URL : https://api.openweathermap.org/data/2.5 +# - VITE_SATELLITE_TILE_URL : http://localhost:5100/tiles/{z}/{x}/{y} +# (dev default; production builds MUST override +# to the same-origin nginx path so cookie auth +# is honored — AZ-498 / contract @ +# _docs/02_document/contracts/satellite-provider/tiles.md) # Prefix for every API request (production: empty; tests / alt deployments: set). # A trailing slash is stripped automatically. @@ -26,10 +29,7 @@ VITE_OWM_API_KEY= # Example for the e2e profile: http://owm-stub:8081/data/2.5 VITE_OWM_BASE_URL= -# OSM map tile URL template (Leaflet TileLayer.url). -# Example for the e2e profile: http://tile-stub:8082/{z}/{x}/{y}.png -VITE_OSM_TILE_URL= - -# Esri satellite tile URL template (Leaflet TileLayer.url for the satellite layer). -# Example for the e2e profile: http://tile-stub:8082/sat/{z}/{y}/{x} -VITE_ESRI_TILE_URL= +# Suite satellite-provider tile URL template (Leaflet TileLayer.url). +# Production: same-origin path (`/tiles/{z}/{x}/{y}`) so the auth cookie rides. +# E2E profile: http://tile-stub:8082/tiles/{z}/{x}/{y} +VITE_SATELLITE_TILE_URL= diff --git a/_docs/02_document/modules/mission-planner.md b/_docs/02_document/modules/mission-planner.md index df82072..006ae22 100644 --- a/_docs/02_document/modules/mission-planner.md +++ b/_docs/02_document/modules/mission-planner.md @@ -35,7 +35,7 @@ mission-planner/src/ ├── services/ │ ├── calculateDistance.ts Haversine + plane climb/cruise/descend │ ├── AircraftService.ts mockGetAirplaneParams (returns hardcoded fixed-wing) -│ ├── WeatherService.ts OpenWeatherMap fetch +│ ├── WeatherService.ts OpenWeatherMap fetch (env-vars: VITE_OWM_API_KEY + VITE_OWM_BASE_URL; fail-soft `null` when key unset, AZ-499) │ └── calculateBatteryUsage.ts Drag + thrust lookup; same algorithm as src/features/flights/flightPlanUtils.calculateBatteryPercentUsed ├── icons/ │ ├── MapIcons.tsx Leaflet icon factories @@ -82,10 +82,10 @@ The React 19 port translates module-for-module wherever possible. Status as of t | `flightPlanning/Aircraft.ts` | (no equivalent) | Aircraft is server-side; the SPA fetches `/api/flights/aircrafts`. | | `services/calculateDistance.ts` | `flightPlanUtils.calculateDistance` | Ported. | | `services/calculateBatteryUsage.ts` | `flightPlanUtils.calculateBatteryPercentUsed` + `calculateAllPoints` | Ported. | -| `services/WeatherService.ts` | `flightPlanUtils.getWeatherData` | Ported (with the same hardcoded API key — Step 4 fix). | +| `services/WeatherService.ts` | `flightPlanUtils.getWeatherData` | Ported. Env-vars `VITE_OWM_API_KEY` + `VITE_OWM_BASE_URL` since AZ-499 (mirrors AZ-448 / AZ-449); same fail-soft `null` contract. | | `services/AircraftService.ts` | `flightPlanUtils.getMockAircraftParams` (mock only) | Real fetch is `/api/flights/aircrafts` in `FlightsPage`. | | `constants/translations.ts` + `LanguageContext.tsx` | `src/i18n/{en,ua}.json` + `i18n/i18n.ts` | Migrated to i18next. | -| `constants/{actionModes,maptypes,tileUrls,purposes,languages}.ts` | `features/flights/types.ts` (`PURPOSES`, `TILE_URLS`, `ActionMode`) | Consolidated into one file. | +| `constants/{actionModes,maptypes,tileUrls,purposes,languages}.ts` | `features/flights/types.ts` (`PURPOSES`, `TILE_URL`, `ActionMode`) | Consolidated into one file. `TILE_URL` collapsed from the prior classic/satellite pair to a single self-hosted satellite URL by AZ-498. | | `icons/{MapIcons,PointIcons,SidebarIcons,PhoneIcon}.tsx` | `features/flights/mapIcons.ts` | Only the marker icons survived; SidebarIcons + PhoneIcon dropped (no rotate-phone overlay in the SPA today). | | `utils.ts` (`newGuid`) | `flightPlanUtils.newGuid` | Ported. | | `config.ts` | `features/flights/types.COORDINATE_PRECISION` | Single constant migrated. | @@ -98,7 +98,7 @@ The React 19 port translates module-for-module wherever possible. Status as of t - **Rotate-phone overlay** (`icons/PhoneIcon.tsx`): MP shows a rotate-phone hint when held in portrait. The SPA does not. - **Per-purpose marker icons** (`icons/PointIcons.tsx`): MP draws a different marker per `meta` purpose. The SPA uses three colour-coded icons (start / mid / end). - **`Aircraft.ts` helper class**: never used in the SPA — aircraft state is fetched and treated as a plain DTO. -- **OpenWeather call directly from `WeatherService.ts`**: same flaw as the SPA port (hardcoded key, no proxy). Both flagged for Step 4. +- **OpenWeather call directly from `WeatherService.ts`**: same flaw as the SPA port (no proxy). Hardcoded key fixed by AZ-499 (env-vars + fail-soft); proxy story still owned by the broader F1 mission-planner deduplication track. - **MUI 5**: MP uses MUI for dialogs / inputs / icons. The SPA replaced everything with hand-rolled Tailwind components matching `_docs/ui_design/README.md`. MUI is not a dep of the workspace. ## Findings carried into Step 4 / 6 / 8 diff --git a/_docs/02_document/modules/src__features__flights.md b/_docs/02_document/modules/src__features__flights.md index 8b5de2c..1813267 100644 --- a/_docs/02_document/modules/src__features__flights.md +++ b/_docs/02_document/modules/src__features__flights.md @@ -17,7 +17,7 @@ Currently handles only the planning surface; the gps-denied orthophoto upload / | Module | Layer | Responsibility | |---|---|---| -| `types.ts` | leaf | All flight-feature-only types (`FlightPoint`, `CalculatedPointInfo`, `MapRectangle`, `WindParams`, `AircraftParams`, `MovingPointInfo`, `ActionMode`), plus tile URLs (`TILE_URLS`), `PURPOSES` (`tank` / `artillery`), and `COORDINATE_PRECISION = 8`. | +| `types.ts` | leaf | All flight-feature-only types (`FlightPoint`, `CalculatedPointInfo`, `MapRectangle`, `WindParams`, `AircraftParams`, `MovingPointInfo`, `ActionMode`), plus the single self-hosted satellite tile URL (`TILE_URL`, AZ-498 — env-var `VITE_SATELLITE_TILE_URL`, dev default `http://localhost:5100/tiles/{z}/{x}/{y}`), `PURPOSES` (`tank` / `artillery`), and `COORDINATE_PRECISION = 8`. | | `mapIcons.ts` | leaf | Three coloured Leaflet `Icon` instances + the default Leaflet pin (loaded from a CDN — see Findings). | | `flightPlanUtils.ts` | leaf | Pure-ish helpers: `newGuid`, haversine `calculateDistance` (with plane climb/cruise/descend profile), OpenWeatherMap fetch, semi-empirical `calculateBatteryPercentUsed`, `calculateAllPoints` (sequential reduce), `parseCoordinates`, `getMockAircraftParams`. | | `WaypointList.tsx` | sub-component | `@hello-pangea/dnd` reorderable list, hover-only Edit/Remove buttons, shows distance / time / battery / altitude per point. | @@ -30,7 +30,7 @@ Currently handles only the planning surface; the gps-denied orthophoto upload / | `FlightListSidebar.tsx` | sub-component | Left rail: flight list, "+ Create", inline-create row, telemetry date stub. | | `JsonEditorDialog.tsx` | sub-component | Modal `