This document tracks proposed WeatherTool improvements. Work should be delivered in small, reviewable phases rather than as one large rewrite. Each phase should leave the application runnable and independently testable.
- **Windows source workspace:** source editing, review, and Git operations only; do not install dependencies, compile, build, run, or test here.
- **Rocky development and staging:** the sole compile, build, development-runtime, and test environment, with production-like Docker staging at `http://192.168.1.101:9190`.
- **Git over SSH:** Windows pushes reviewed commits to a private bare repository on Rocky; the Rocky staging checkout pulls those commits and rebuilds.
- **Ubuntu VPS deployment:** release `b0b58d2` is publicly operational behind Cloudflare strict TLS, Nginx, and Authelia, ingesting real LVĢMC open-data station/water-temperature observations on a schedule; the `weather` table was wiped and re-populated fresh once the UTC/local timezone mismatch between sources was fixed. FTP (`ENABLE_LVGMC_FTP_JOBS`) stays `false` on both Rocky and the VPS pending a real answer from the LVGMC contact about the `ltv` account's connection/rate policy — see Phase 7 for the full, still-unresolved diagnosis. The VPS does not compile or build the project.
The first observed frontend install reported 15 vulnerabilities: 1 critical, 10 high, 3 moderate, and 1 low. The frontend dependency tree was reviewed in controlled groups, obsolete packages were removed, and both the complete and production-only npm audits now report zero known vulnerabilities at commit `de6f279`.
- [x] Establish that the production dependency audit is primarily blocked by `solid-js@1.9.4` resolving vulnerable `seroval@1.2.0`; confirm that the full audit also contains development-tool advisories.
- [x] Update direct frontend dependencies in controlled groups.
- [x] Replace or remove obsolete frontend packages where appropriate.
- [x] Add a committed TypeScript typecheck command and validate the updated frontend with a clean `npm ci`, typecheck, and production build.
- [x] Resolve the remaining transitive build-tool advisories after reviewing the proposed `npm audit fix` changes; verify full and production-only audits at zero.
- [x] Build, smoke-test, checksum, transfer, and deploy a commit-addressed VPS release containing `de6f279` or later without restarting PostgreSQL or Authelia. (`ef64895` deployed 2026-08-22.)
- [ ] Visually compare every page and representative exported PNG in the deployed dependency-maintenance release.
- [ ] Rotate and remove the API key exposed in a source comment (`src/main/scala/fetch/dmi/FetchService.scala:56`, a DMI HARMONIE key introduced 2025-02-01; confirmed the repo's GitHub `origin` is public, so this has been externally exposed for roughly 18 months — decision on rotation vs. a git-history rewrite is still open).
- [x] Remove credentials/internal error details from connection-error messages: `/api/fetch/lvgmc/stations` was returning `error.getMessage` straight to the caller; now logs it server-side and returns a generic message.
- [ ] Protect or remove debug and administrative endpoints. Partial: `/api/fetch/lvgmc/stations` (the one that could trigger a real FTP login) is now gated behind `ENABLE_LVGMC_FTP_JOBS`, but `/api/debug/time`, `/api/debug/folder-structure`, and `/api/debug/delete-tmp` remain unauthenticated.
- [ ] Introduce closed, validated weather-field and aggregation types. Partial: the one route that let a raw field string reach SQL now validates against `WeatherData.getKeys` (see below), but this is a route-level allowlist, not a closed type threaded through the codebase.
- [x] Eliminate raw user-controlled SQL identifiers: `/api/query/city/.../{field}/...` let an arbitrary path segment reach `Fragment.const` unescaped; added `ValidateField`, the same allowlist-against-`WeatherData.getKeys` pattern `/query/country` already used.
- [x] Validate and constrain filenames, resolved paths, offsets, and byte lengths: added `ValidateFileName` (allowlist regex, rejects `..` and separators including decoded `%2f`) and applied it to every route that concatenated a raw path segment into a filesystem or FTP path (`/debug/file`, `/show/grib`, `/grib/binary-chunk`, `/show/lvgmc-forecast`); hardened `ValidateInt` to reject negative integers; capped `binary-chunk` read length at 64MB to remove an unbounded allocation.
- [x] Harden `WarningService`: bound LVĢMC retry attempts to once per cache window during an upstream outage, isolate one warning's polygon-fetch failure from the rest instead of failing the whole response, verify the CKAN polygon filter against real column-type ambiguity rather than guessing, and log (rather than silently swallow) a defaulted vertex-order value.
- [x] Add a separate Latvian-named Brīdinājumi workspace that renders current LVĢMC warning polygons without manual tracing.
- [x] Add production border overlay artwork and feathered warning-polygon fills to Brīdinājumi.
- [x] Replace Brīdinājumi's 4-corner bounding-box projection with an affine fit calibrated against the same validated Kartes/Faktiskā city pixel positions.
- [x] Limit the Brīdinājumi canvas to one operator-selected warning (superseded below by multi-warning selection), remove the on-canvas legend, collapse card detail to phenomenon/severity by default with full detail on click, and pre-fill the title from the selected warning's phenomenon.
- [x] Redesign Brīdinājumi warning cards as compact phenomenon-icon chips and move full warning text into a dialog popup over a dimmed, blurred backdrop.
- [x] Give Brīdinājumi chips a hover state where the severity-colored icon capsule expands to fill the chip, adapted from a reviewed external Uiverse.io reference component.
- [x] Split Brīdinājumi's warning selection (checkbox, drives the map) from viewing a warning's detail (clicking the chip, opens the popup) so choosing a map warning no longer forces the popup open; widen the detail popup for readability.
- [x] Replace single-warning selection with multi-select checkboxes so combined-severity newsroom maps (e.g. yellow + orange wind together) can be produced; re-add severity-ordered compositing of every checked warning's polygons. Restyle chips as narrower, fully severity-colored, white-icon/white-text buttons, and port a reviewed Uiverse.io checkbox component for the selection control.
- [x] Add per-warning editorial weather-symbol placement to Brīdinājumi, reusing Faktiskā's existing symbol set: drag to move, drag a corner handle to resize, stored as canvas-fraction coordinates so placement holds across both export resolutions; the drag/resize affordance is preview-only and never reaches the exported PNG. This is the app's first direct-manipulation canvas control (everywhere else is plain form inputs).
- [ ] Decide whether Brīdinājumi should actively prevent checking warnings of different phenomena together, or continue leaving that as an operator responsibility.
- [x] Build and verify commit-addressed images on Rocky, transfer and checksum-verify them on the VPS, and update only the application service. Current full-SHA image: `weathertool:138f57c808631dc17396b22ac0305b670fbcf776`; PostgreSQL and Authelia were not restarted.
- The fixed production PNG bases, browser/social metadata, reviewed frontend dependency updates, stabilized header navigation, authenticated API routing, the full Brīdinājumi warning-map workspace (including draggable per-warning symbol placement), hardened LVĢMC warning fetching, real open-data station/water-temperature ingestion, and the scheduler safety split are deployed in UAT release `138f57c8`; newsroom workflow validation is in progress.
- Link-preview crawlers cannot authenticate through Authelia; the final Nginx policy must deliberately expose only the minimum preview metadata/assets if WhatsApp previews are required.
- Both the VPS and Rocky `weather` tables now hold only real open-data station observations; their original synthetic rows were backed up and wiped 2026-08-23.
- The private LVGMC FTP feed has real credentials as of 2026-08-23, but `ENABLE_LVGMC_FTP_JOBS` stays `false` on both Rocky and the VPS pending a real answer from LVGMC about the `ltv` account's connection/rate policy (see Phase 7). The DMI HARMONIE forecast feed remains gated (`ENABLE_HARMONIE_JOBS`, default off) pending real credentials — turned out not to need any (DMI dropped its key requirement), but wiring it up is deferred to a dedicated verification session (see Phase 7).
| 2026-08-20 | `16a8c68` | Deploy approved production map bases, normalized branding assets, favicon, Apple icon, and social metadata to public UAT | Yes — Rocky build and VPS/browser smoke checks |
| 2026-08-22 | `ef64895` | Deploy the reviewed zero-advisory frontend dependency state as an immutable full-SHA VPS release while leaving PostgreSQL, Authelia, and HOP uninterrupted | Yes — isolated Rocky smoke test, dual-host checksum, matching image ID, container health, loopback, authentication-gate, and HOP checks |
| 2026-08-22 | `36d094e` | Stabilize the header, keep the primary workspaces visible, and move secondary workspaces into an icon-labelled menu | Yes — Rocky browser and Jam navigation checks |
| 2026-08-22 | `df911f1` | Preserve API 401 responses through Authelia/Nginx so frontend queries do not follow cross-origin login redirects | Yes — Rocky and VPS health checks; authenticated browser query verified after refreshing synthetic data |
| 2026-08-22 | `f3197bf` | Return query API payloads with an explicit JSON content type | Yes — Rocky API and browser verification; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `0608865` | Localize the operator-facing workspace names and concise workflow copy in Latvian | Yes — Rocky browser verification; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `7f09181` | Independently calibrate both Ūdens templates and optically center Monda values using visible glyph bounds | Yes — both native-resolution PNG exports visually validated on Rocky; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `bc5bee1` | Add the Brīdinājumi LVĢMC warning-map workflow with metadata/polygon ingestion, day/phenomenon filtering, and manual title entry | Yes — Rocky browser verification; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `13115f0` | Add production border overlay artwork and feathered warning-polygon fills to Brīdinājumi | Yes — Rocky browser verification; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `4395935` | Replace Brīdinājumi's 4-corner bounding-box projection, which drifted up to ~200px on the 3840 canvas, with an affine fit calibrated against the validated Kartes/Faktiskā city pixel positions | Yes — Rocky typecheck, build, and headless-browser screenshot comparison at both native export resolutions; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `924e410` | Limit Brīdinājumi to one selected warning per export, remove the on-canvas legend, collapse card detail until clicked, and pre-fill the title from the selected warning's phenomenon | Yes — Rocky typecheck, build, and headless-browser screenshot verification of selection switching and card detail; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `cafc75a` | Redesign Brīdinājumi warning cards as compact phenomenon-icon chips and move full warning text into a dialog popup over a dimmed, blurred backdrop | Yes — Rocky typecheck, build, and headless-browser screenshot verification of the popup open/close flow; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `70fd24c` | Give Brīdinājumi chips a hover-expanding severity capsule, adapted from a reviewed Uiverse.io reference component | Yes — Rocky typecheck, build, and headless-browser screenshot verification of rest/hover/active chip states; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `6fbf672` | Split Brīdinājumi warning selection (checkbox) from viewing detail (chip click) and widen the detail popup | Yes — Rocky typecheck, build, and headless-browser verification that checkbox clicks change the map selection without opening the popup and chip clicks open the correct warning's detail without changing the selection; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `56b8716` | Replace single-warning Brīdinājumi selection with multi-select checkboxes and severity-ordered polygon compositing, and restyle chips as narrower, fully severity-colored, white-icon/white-text buttons with a ported Uiverse.io checkbox component | Yes — Rocky typecheck, build, and headless-browser screenshot verification of multiple simultaneous checked warnings compositing correctly on the map; deployed to VPS in release `b5150ab` on 2026-08-22 |
| 2026-08-22 | `6185dbf` | Harden `WarningService` against partial upstream failures: bounded outage retry, per-warning fetch isolation, a live-API-verified CKAN polygon filter, and visible logging for a previously-silent vertex-order default | Yes — Scala tests, Rocky full-stack rebuild verified against real live LVĢMC data (including a genuinely severe 52,345-point wind-warning polygon rendering correctly); deployed to VPS in release `6185dbf` on 2026-08-22, verified against live data on the VPS itself |
| 2026-08-23 | `c97875d` | Add draggable, resizable per-warning weather-symbol placement to Brīdinājumi, reusing Faktiskā's symbol set and image cache | Yes — Rocky typecheck, build, and headless-browser mouse-drag/resize verification against fixture warning data (the live LVĢMC feed had zero active warnings at test time); confirmed the exported PNG excludes the preview-only drag/resize handles; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `893a09a` | Add `fetch.lvgmc.OpenDataStationService`: real, free, keyless current station observations from LVĢMC's open-data portal, scheduled alongside (not replacing) the private FTP feed | Yes — Scala tests, manual `sbt runMain` fetch+write against the real feed on Rocky staging (1,577 rows, 33 cities), confirmed in `weather` via direct SQL, Faktiskā's 13 fixed stations rendering real temperatures with correct stale-fallback for the one uncovered city (Valmiera), and Kartes' aggregate query API returning sane blended values; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `cb25316` | Stop generating synthetic weather data (remove `dev/seed_weather.sql`, the `seed` Compose service, and the README section describing it) now that real data is verified and this tool may plausibly be used against live broadcasts | Yes — `docker compose config` valid, running Rocky stack unaffected (profile-gated service, no running containers touched); deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `6ddfe73` | Split scheduled jobs (`ENABLE_SCHEDULED_JOBS` vs. new `ENABLE_LEGACY_PROVIDER_JOBS`) so the working open-data station fetch runs without the FTP/Harmonie jobs' placeholder credentials crash-looping the whole app via `parMapN` | Yes — Scala tests, Rocky rebuild deployed with the flag on: logs confirm only the safe jobs scheduled, FTP/Harmonie never attempted, app stayed up; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `39fcb3e` | Remove the dead `METEO_*` fetch path (`fetch/csv/FetchService.scala`, its empty test, and its env vars everywhere) — confirmed via git history it's the same underlying LVĢMC data as the FTP/open-data paths, just an earlier, superseded delivery mechanism that was never wired into anything running | Yes — Scala tests (2 suites now, down from 3, the deleted one was empty); deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `4036d24`–`4b92272` | Add `fetch.lvgmc.WaterTemperatureService`: real, free, keyless water temperatures for all 6 Ūdens zones (2 coastal `SEDUT` stations for Jūra/Līcis, 4 inland `WTEMD` stations for Kurzeme/Zemgale/Vidzeme/Latgale), fetch-on-demand with a 15-minute in-memory cache mirroring `WarningService` rather than the persisted station-observation path, since Ūdens has never stored history | Yes — Scala tests, Rocky rebuild, direct API curl confirming real values for all 6 zones; caught and fixed one station-selection error (Daugavpils only reports water level, not temperature — swapped for Ludza) via direct verification before committing; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `5a92465` | Auto-populate Ūdens's min/max fields from `/api/water-temperatures` on load, mirroring Faktiskā's fetch/override/reset pattern, with per-zone manual-override tracking and an "Atiestatīt" reset button | Yes — Rocky typecheck, build, and headless-browser verification (Playwright in Docker, since `chromium-cli` wasn't available in this environment) confirming auto-populated values match the API, manual edit correctly flips a zone to "Manuāli" and enables its reset button, and reset correctly restores the fetched value; noted along the way that `/udens-temperatura` 404s on a direct hit (no SPA fallback yet, consistent with the known limitation already listed) but works via client-side nav; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `9eca9eb` | Fix the one-station-per-zone design catching a real problem after deploy: every zone showed the same value duplicated as both min and max ("19...19"). Classified all 65 LVĢMC stations that report water temperature (56 inland WTEMD, 9 coastal SEDUT) into the 6 zones by geography and report the real min/max across each zone's currently-reporting stations, dropping readings over 12h stale so one stuck sensor can't skew a range | Yes — Scala tests, Rocky rebuild, direct API curl confirming a genuine spread per zone (e.g. Vidzeme 11.4–18.7°C, traced the low point to a real fresh reading from a colder headwater station near Cēsis, not a stale-sensor artifact), and headless-browser screenshot confirming the UI renders the real spread (e.g. "11...19") with no console errors; deployed to VPS in release `138f57c8` on 2026-08-23 |
| 2026-08-23 | `69a5260` | Bring architecture/workflow docs current with real-data ingestion (removed synthetic-seed staging descriptions, documented Ūdens auto-populate, the scheduler split) | Yes — doc-only change, no rebuild needed |
| 2026-08-23 | `138f57c8` | **Full go-live VPS release**: deployed image `weathertool:138f57c808631dc17396b22ac0305b670fbcf776` (everything from `c97875d` through `69a5260` — Brīdinājumi draggable symbol placement, real open-data station/water-temperature ingestion, synthetic-data removal, scheduler split, `METEO_*` deletion, real per-zone water-temperature ranges), flipped `ENABLE_SCHEDULED_JOBS=true` on the VPS now that the open-data path is proven, and wiped the VPS `weather` table's original 12,784 synthetic rows down to 0 (Rocky's own table was left untouched — separate decision, not requested for this release) | Yes — exact release image smoke-tested on Rocky (healthy, real warnings/water-temperature data) before transfer; VPS PostgreSQL backed up to `/srv/weathertool/backups/pre-real-data-release/` before the wipe; checksum verified on both ends; `app` service recreated without touching PostgreSQL/Authelia; confirmed healthy container, working loopback and public HTTPS (302 unauthenticated page, 401 unauthenticated API — matching established behavior), real warnings/water-temperature API responses, the scheduled open-data job firing on schedule and landing 1,577 real rows within ~2 minutes, and a real (non-Valmiera) city query returning genuine current temperatures with Valmiera correctly absent rather than falling back to wiped synthetic data |
| 2026-08-23 | `744d706` | Rework the visual palette: layered blue-gray background gradient (sampled tones, iterated visually against Home/Brīdinājumi/Kartes) replacing the near-flat pale tint, while keeping buttons/icons/links on the original saturated accent blue; removed the "Testa dati" badge, a synthetic-data leftover | Yes — Rocky build and headless-browser screenshot comparison across Home, Brīdinājumi, and Kartes; deployed to VPS in release `f78b0a26` on 2026-08-23 |
| 2026-08-23 | `a1c2fd5` | Declutter Faktiskā: collapse "Jaunākās temperatūras" and "Kartes noformējums" by default behind toggles (with a manual-override-count badge so nothing's silently hidden), fix a real cascade bug forcing Faktiskā's resolution buttons into full-width left-aligned rows instead of a compact pair, restructure "Kartes noformējums" into distinct bordered cards, translate its still-English symbol/city-assignment block to Latvian ("Mākoņi pilsētām"), and collapse its city list to exceptions-only by default | Yes — Rocky typecheck, build, and headless-browser screenshot verification of the collapse/expand toggles, the exceptions-only city list, and the button fix; deployed to VPS in release `f78b0a26` on 2026-08-23 |
| 2026-08-23 | `5c00e48` | Fix temperature numbers rendering visually too high in their map badges: canvas `textBaseline: "middle"` centers on font em-box metrics, not visible glyph ink, and digits have no descenders — centered on actual glyph bounds instead, matching the technique already used correctly in Ūdens | Yes — Rocky build and headless-browser screenshot comparison of the map badges before/after; shared by Faktiskā and the older Kartes comparison map; deployed to VPS in release `f78b0a26` on 2026-08-23 |
| 2026-08-23 | `59bb499` | Promote Brīdinājumi from the "Vairāk" overflow menu into the always-visible header nav, and add a small icon to every visible nav item (reusing the same icons as their Home cards) | Yes — Rocky build and headless-browser screenshot verification at 1900/1440/1200px widths, confirming no overflow and correct active/overflow-menu state; deployed to VPS in release `f78b0a26` on 2026-08-23 |
| 2026-08-23 | `f78b0a26` | Deploy the full frontend design pass (background/accent rework, Faktiskā decluttering, temperature-badge centering fix, header nav) to the VPS — no database or scheduler changes, application-only release | Yes — exact release image smoke-tested on Rocky (bundle hash and headless-browser screenshot confirmed against the known-good local build) before transfer; checksum verified on both ends; `app` service recreated without touching PostgreSQL/Authelia; confirmed healthy container, matching bundle hash on the VPS, working loopback and public HTTPS (302 unauthenticated page, 401 unauthenticated API), and real warnings/water-temperature API responses |
| 2026-08-23 | `d7439a2b` | Fresh-eyes review of the whole design pass turned up two real issues, fixed here: `WindInputs` had no Latvian branching and was showing "Wind direction"/"Wind speed"/"Gusts" in English right under the freshly-translated "Temperatūra un vējš" heading on Faktiskā (threaded the same `productionTemplate` flag `MapView.tsx` already uses elsewhere), and a confirmed-unreferenced `.assignedSymbol` CSS rule was removed. The review also flagged two things left alone: a pre-existing (not introduced this session) `.symbolPalette`/`.selectedPreview`/`.currentSymbol` cross-file class collision in `weatherIcons.css` currently masked by `!important` rather than actually resolved, and a naming nit on "Mākoņi pilsētām" (the section assigns arbitrary weather symbols, not just clouds) — both worth a look later, not blocking | Yes — Rocky typecheck, build, and headless-browser screenshot confirming the wind fields now render in Latvian; deployed to VPS in release `d7439a2b`, exact release image smoke-tested (bundle hash matched) before transfer, checksum verified, container healthy, public HTTPS/API checks passing |
| 2026-08-23 | `e446f1f` | Consolidate `.symbolPalette`/`.selectedPreview`/`.currentSymbol` (used by both Faktiskā's `IconInputs.tsx` and Brīdinājumi's `Warnings.tsx`) into one real definition in `weatherIcons.css`, the file both actually import, removing the dead/duplicate versions in `mapGraphics.css` and the `!important` size patches that were masking the collision. "Mākoņi pilsētām" naming confirmed intentional — the newsroom users are non-technical and the same people who'll use this long-term, so intuitive-but-imprecise beats literally-correct-but-jargony | Yes — Rocky typecheck, build, headless-browser screenshot on Faktiskā (pixel-identical to before), and computed-style verification on both Faktiskā and Brīdinājumi confirming identical resolved CSS with no `!important`; caught one incidental fix for free (`.currentSymbol.empty`'s Inter font was silently losing to the `!important` rule, normal cascade now applies it correctly); deployed to VPS in release `e446f1ff`, exact release image smoke-tested (bundle hash matched) before transfer, checksum verified, container healthy, public HTTPS/API checks passing |
| 2026-08-23 | `2b5dff6` | Make the `weather` table upsert non-destructive: `ON CONFLICT DO UPDATE` was a blind full-row overwrite, so open-data's always-null `visibilityMin`/`dewPoint`/`sunDuration` and always-empty `phenomena` would silently erase real FTP values whenever open-data's write landed later for the same row. Switched to `COALESCE(excluded.field, weather.field)`, with `NULLIF` against an empty array specifically for `phenomena` since Scala's `List[String]` never maps to SQL `NULL` | Yes — Scala tests; found while wiring up real FTP credentials, before any real dual-source writes had happened |
| 2026-08-23 | `cab94d0` | Fix a real UTC-vs-local timezone mismatch: the open-data portal's `DATETIME` is UTC, stored into `weather.dateTime` with no conversion, while FTP's "Laiks" column is already local and also stored as-is — silently present since open-data went live (`893a09a`), invisible until a second, correctly-labeled source existed alongside it. Fixed by converting to `Europe/Riga` at ingestion in `OpenDataStationService` | Yes — Scala tests; verified live: at real local time 20:31 EEST the fix produces `dateTime=20:00` instead of the previous `17:00`, matching what FTP writes for the same real hour |
| 2026-08-23 | `3315f00` | Fix the same UTC-vs-local mismatch in Ūdens's `observedAt` display (`WaterTemperatureService`) — same open-data portal, same root cause, display-only (internal recency filtering was already self-consistent either way) | Yes — Scala tests; verified live via `/api/water-temperatures`: `observedAt` now matches real local time |
| 2026-08-23 | `3eddf95` | Split `ENABLE_LEGACY_PROVIDER_JOBS` into independent `ENABLE_LVGMC_FTP_JOBS`/`ENABLE_HARMONIE_JOBS` — real LVGMC FTP credentials arrived today, real DMI HARMONIE credentials haven't, and the combined flag would have enabled both together, crash-looping the app on HARMONIE's still-placeholder values via `parMapN` | Yes — Scala tests; caught before it could happen (the Grib job was ~15 min from its first scheduled run when noticed) and reverted within under a minute; re-verified after the fix that only "Fetch Weather Stations" scheduled, not "Fetch Grib" |
| 2026-08-23 | `dc04f66` | Fix `deploy/vps/compose.yml` hardcoding `LVGMC_USER`/`PASSWORD`/`URL` to inert placeholder strings directly in the file (unlike `POSTGRES_*`, which already read from `.env.staging`) — real credentials added to `.env.staging` alone would have had no effect until this switched to the same `${VAR}` substitution pattern. Enabled `ENABLE_LVGMC_FTP_JOBS` on the VPS | Yes — `docker compose config` syntax valid; VPS `app` container came up healthy with real credentials loaded (would have crashed immediately on missing-config if not) |
| 2026-08-24 | `6b9c7cf` | Fix path traversal, auth-bypass, and DoS findings from an independent fresh-eyes security review (requested by the user specifically to get a second, skeptical pass against the local Rocky version): a live-verified CRITICAL — `/api/debug/file/{fileName}` allowed unauthenticated arbitrary file read, confirmed by reading `/proc/self/environ` and leaking `LVGMC_PASSWORD`/`POSTGRES_PASSWORD` into the review's own output — plus the same raw-filename pattern in `/show/grib`, `/grib/binary-chunk`, and `/show/lvgmc-forecast`. Added `ValidateFileName` (allowlist regex; rejects `..` and separators including decoded `%2f`) and applied it everywhere a path segment reached a filesystem or FTP path; hardened `ValidateInt` to reject negatives; gated `/api/fetch/lvgmc/stations` behind `ENABLE_LVGMC_FTP_JOBS` (it could otherwise trigger a real, unauthenticated FTP login regardless of the flag) and stopped it leaking `error.getMessage`; added an explicit `/api/*` catch-all so an unmatched API route can never fall through to the SPA shell; capped `binary-chunk` read length at 64MB | Yes — Scala tests, Rocky rebuild, live curl verification of `%2f`-encoded and literal traversal payloads (404), the gated FTP route (503 while the flag is off), an unmatched `/api/*` route (404, not `index.html`), and legitimate filenames/queries still returning correct 200s |
| 2026-08-24 | `8c45d8d` | Fix SQL injection surfaced by the same security review: `/api/query/city/.../{field}/...` passed its `field` path segment unvalidated into `PostgresService.query`, which splices it into SQL via `Fragment.const` (unescaped) whenever the aggregate key is min/max/avg/sum/distinct, or whenever granularity is `hour` in the list branch. Added `ValidateField`, reusing the `WeatherData.getKeys` allowlist `AggFieldList` already applied to `/query/country`. `AggregateKey` values reaching `Fragment.const` elsewhere were already safe — they come from a closed ADT (`AggregateKey.fromString`), not raw input | Yes — Scala tests, Rocky rebuild, live curl verification that SQL-injection payloads in the `field` segment 404 and a legitimate query still returns correct data |
| 2026-08-24 | *(operational, no commit)* | Rotated Rocky's `POSTGRES_PASSWORD` following the security review, since the old value had been exposed into this session's context multiple times (two of my own sloppy shell commands, plus the `/proc/self/environ` read the review used as its traversal proof). Changed the role's actual password via `ALTER ROLE` against the running container (editing `.env` alone has no effect on an already-initialized PostgreSQL data directory), then updated `.env` and recreated the `scala` service. The Postgres container itself was also recreated as a side effect (its own `POSTGRES_PASSWORD` env interpolation changed too), which is harmless — that variable only takes effect on a fresh, empty data directory, not an existing one — but is worth knowing about | Yes — clean scala container startup with no auth errors, and a real query (`/api/query/latest-temperatures/Rīga`) returning live data over the new password |
| 2026-08-24 | `2c44888`–`b0b58d2` | Replace the hardcoded per-route static-file list (`/station`, `/cities`, `/latvia`, `/database`, `/harmonie`, `/lvgmc-forecast`, `/bridinajumi`) with a general SPA fallback: real files serve as-is, anything else falls back to `index.html` so the SolidJS router handles it client-side — fixes a real, user-reported bug where a direct hit (e.g. a browser refresh) on `/faktiska` or `/udens-temperatura` 404ed instead of loading the app, since those two routes were never added to the old list. A missing file under `/assets` specifically still 404s properly rather than silently serving HTML, so a stale tab after a future deploy gets a clean error instead of a confusing JS parse failure | Yes — first attempt silently no-op'd because the fix was built into a `git archive HEAD` image before being committed (classic mistake, caught immediately by re-testing and finding identical old behavior); after committing, verified via curl on all previously-working routes, both previously-broken routes, a missing asset (404), a real asset (200), and `/api` (200), then a full headless-browser render check (zero console errors, real data, correct nav state) on a genuine direct hit — not just HTTP status codes; deployed to VPS in release `b0b58d2`, verified the same way through both the loopback port and the public domain (Authelia gate still correctly redirects unauthenticated requests) |