Commit Graph

338 Commits

Author SHA1 Message Date
b0txec e33e78e2d4 Record the VPS deployment of the security review fixes
Release 0be325f (image weathertool:0be325fbbbf92b03bc8d574dc6f7b9449ef310ea)
ships the path-traversal/auth-bypass/DoS and SQL-injection fixes to
production; b0b58d2 remains the rollback target.
2026-08-24 12:06:07 +03:00
b0txec 0be325fbbb Document the security review fixes and the Rocky Postgres password rotation
Records the path-traversal/auth-bypass/DoS fixes (6b9c7cf), the SQL
injection fix (8c45d8d), and the subsequent Postgres password rotation on
Rocky in the roadmap changelog and Phase 4 checklist, and notes current
status in README/DEVELOPMENT_AND_STAGING. VPS deployment of these fixes is
still pending.
2026-08-24 12:02:27 +03:00
b0txec 8c45d8de1d Fix SQL injection in the /query/city aggregation route
field on that route reached PostgresService.query unvalidated, 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. Add ValidateField (allowlists WeatherData's known field names, same
pattern AggFieldList already uses for /query/country) and apply it to the
field path segment. AggregateKey values reaching Fragment.const elsewhere are
already safe since they come from a closed ADT, not raw user input.
2026-08-24 11:55:52 +03:00
b0txec 6b9c7cf4ae Fix path traversal, auth-bypass, and DoS findings from the security review
- Add ValidateFileName (allowlist regex, rejects .. and separators) and apply
  it to every route that concatenates a raw path segment into a filesystem or
  remote FTP path: /show/lvgmc-forecast, /show/grib, /grib/binary-chunk, and
  /debug/file. Previously an unauthenticated caller could read arbitrary
  files, including /proc/self/environ (leaks LVGMC_PASSWORD/POSTGRES_PASSWORD).
- Harden ValidateInt to reject negative integers.
- Gate /api/fetch/lvgmc/stations behind ENABLE_LVGMC_FTP_JOBS so it can no
  longer trigger a real, unauthenticated FTP login regardless of the flag;
  stop leaking error.getMessage in its response.
- Add an explicit /api/* catch-all (NotFound) so an unmatched API route can
  never fall through to the SPA fallback and be served index.html as a 200.
- Cap binary-chunk read length at 64MB to prevent an unbounded allocation.
2026-08-24 11:50:53 +03:00
b0txec fdd5508e43 Fix a stale README line still claiming FTP runs on schedule 2026-08-24 11:00:40 +03:00
b0txec 546ad46546 Record the SPA fallback fix and the morning FTP re-test finding
The SPA fallback (2c44888/b0b58d2) is deployed to VPS as b0b58d2,
verified via curl and headless-browser on real routes, a missing
asset, and /api, both locally and through the public domain.

Also updated the FTP saga: re-testing this morning found a Rocky-only
failure with the VPS confirmed off, which the two-machine-collision
theory from last night can't explain. Decided to stop self-testing
and wait for a real answer from LVGMC about the account's connection
policy rather than keep guessing through trial and error.
2026-08-24 10:42:12 +03:00
b0txec b0b58d2f1e Keep a missing /assets file a real 404 in the SPA fallback
A stale browser tab referencing a bundle removed by a later deploy
should get a clean 404, not HTML served where JS was expected.
2026-08-24 10:35:34 +03:00
b0txec 2c4488846e Add a general SPA fallback instead of an explicit per-route list
Direct hits on client-side routes not in the hardcoded list (e.g. a
browser refresh on /faktiska or /udens-temperatura) 404ed instead of
loading the app — a known limitation that was actually hit in
production. Real files now serve as-is; anything else falls back to
index.html so the SolidJS router handles it, matching the existing
"TODO rewrite in more generic way" comment. Future routes need no
backend changes.
2026-08-24 10:32:38 +03:00
b0txec 1086c8b548 Revert VPS FTP after it crash-looped the app; document the diagnosis
The ltv account fails to authenticate from the VPS specifically
(works fine from Rocky with the same credentials, network path to
ftp.meteo.lv:21 confirmed fine from the VPS too) — narrowed to either
a password transcription error in .env.staging or LVGMC IP-allowlisting
the account, neither confirmable without inspecting the password.
Caught via the overnight watch this was set up for rather than
assumed safe; reverted ENABLE_LVGMC_FTP_JOBS to false on the VPS
immediately once the failure was confirmed real, restoring stable
operation. FTP stays enabled on Rocky, where it works.
2026-08-23 22:14:58 +03:00
b0txec 9a6cb30552 Catch up documentation on tonight's real-data/timezone work
Changelog rows for the COALESCE upsert fix, both timezone fixes, the
ENABLE_LVGMC_FTP_JOBS/ENABLE_HARMONIE_JOBS split, and the VPS compose
LVGMC templating fix — none had one yet. Updated the stale "currently
deployed" references (were still e446f1ff) to the actual final
release (3eddf95). Recorded the DMI Harmonie research (no API key
needed, new domain, verified live) and the 5-step verification plan
for actually wiring it up, deferred to a dedicated session. One line
about the VPS's first FTP fetch is marked pending — a background
watch is still confirming it as of this commit; will follow up once
it resolves.
2026-08-23 22:03:46 +03:00
b0txec dc04f66b9d Enable real FTP credentials to flow through to the VPS
deploy/vps/compose.yml hardcoded LVGMC_USER/PASSWORD/URL to inert
placeholder strings directly in the file, unlike POSTGRES_* which
already read from .env.staging — so simply adding real values to
.env.staging would have had no effect. Switched to the same
${VAR} substitution pattern, and enabled ENABLE_LVGMC_FTP_JOBS now
that real credentials exist. HARMONIE stays on placeholders pending
real DMI credentials.
2026-08-23 21:37:29 +03:00
b0txec 3eddf95008 Split ENABLE_LEGACY_PROVIDER_JOBS into independent FTP/HARMONIE flags
Real LVGMC FTP credentials arrived today; real DMI HARMONIE
credentials haven't. The combined flag would have enabled both the
moment FTP's were ready, crash-looping the app on HARMONIE's still-
placeholder values via parMapN — caught this before it happened
(the Grib job was ~15 min from its first scheduled run). Split into
ENABLE_LVGMC_FTP_JOBS and ENABLE_HARMONIE_JOBS so each provider can
be enabled independently as its own credentials become real.
2026-08-23 21:32:11 +03:00
b0txec 3315f00fb1 Fix the same UTC-vs-local mismatch in Ūdens's observedAt display
Same root cause as the station-observation fix (this uses the same
open-data portal, same UTC DATETIME field): the raw UTC string was
passed straight through to the frontend as a display label, which
JS's Date parser then reads as local time for a string with no
timezone suffix — silently showing observation times 2-3h behind
the newsroom's actual clock. Internal recency filtering (isRecent)
was already self-consistent either way; this only affects display.
2026-08-23 21:26:14 +03:00
b0txec cab94d097c Fix a real UTC-vs-local timezone mismatch in open-data station timestamps
Found while wiring up real FTP credentials: the open-data portal's
DATETIME field is UTC, but was being stored into weather.dateTime
as-is with no conversion — while the private FTP feed's "Laiks"
column is already Latvia local time and also stored as-is. Both
paths write the same column, so the table has been silently holding
two timelines 2-3h apart since open-data went live, invisible only
because FTP was never actually running with real data until today.
Verified live: at real local time 20:31 EEST, the fix now correctly
produces dateTime=20:00 instead of 17:00, matching what FTP writes
for the same real hour.
2026-08-23 21:26:08 +03:00
b0txec 2b5dff6255 Make the weather table upsert non-destructive across sources
ON CONFLICT DO UPDATE was a blind full-row overwrite. Open-data
station rows always carry null visibilityMin/dewPoint/sunDuration
and an empty phenomena array (fields it doesn't publish), so once a
second source (FTP) writes real values for those fields, a later
open-data write for the same (city, dateTime) would silently null
them back out. Switched to COALESCE(excluded.field, weather.field)
so a missing value from one source never erases a real value the
other already wrote; phenomena needs NULLIF against an empty array
specifically, since Scala's List[String] never maps to SQL NULL.
2026-08-23 21:26:00 +03:00
b0txec b91958c0e5 Record the e446f1ff VPS release (weather-icon CSS consolidation) 2026-08-23 14:19:33 +03:00
b0txec e446f1ff09 Consolidate the weather-icon-picker CSS into one file, no !important
.symbolPalette/.selectedPreview/.currentSymbol are used by both
Faktiskā's IconInputs.tsx and Brīdinājumi's Warnings.tsx, but were
only defined in mapGraphics.css (Faktiskā's own stylesheet, twice,
with a fully dead first version) and then size-patched for both
consumers via !important overrides in weatherIcons.css. Moved the one
real definition into weatherIcons.css, the file both pages actually
import, and removed the dead/duplicate/!important versions.

No visual change intended — verified via headless-browser screenshot
on Faktiskā and computed-style checks on both pages. One incidental
fix: .currentSymbol.empty's Inter font was silently losing to the
!important rule; normal cascade now applies it correctly again.
2026-08-23 14:17:20 +03:00
b0txec ec2d43e0e9 Record the d7439a2b VPS release (WindInputs fix, dead CSS cleanup) 2026-08-23 13:43:31 +03:00
b0txec d7439a2bd5 Translate WindInputs to Latvian on Faktiskā, remove dead CSS
Found by a fresh-eyes review after today's design pass: WindInputs
had no productionTemplate branching, so it kept showing "Wind
direction"/"Wind speed"/"Gusts" in English right under the freshly-
translated "Temperatūra un vējš" heading. Threaded the same
productionTemplate flag MapView.tsx already uses for its other
labels. Also removed .assignedSymbol, an unreferenced CSS rule the
same review flagged as dead.
2026-08-23 13:41:02 +03:00
b0txec 90f65d76e6 Record the f78b0a26 VPS release (today's frontend design pass)
Application-only release: no database or scheduler changes.
2026-08-23 13:37:31 +03:00
b0txec f78b0a265f Document today's frontend design pass in the roadmap changelog
References 744d706, a1c2fd5, 5c00e48, 59bb499.
2026-08-23 13:31:53 +03:00
b0txec 59bb4996ff Promote Brīdinājumi to the visible header nav, add icons throughout
Brīdinājumi was tucked in the "Vairāk" overflow menu despite being a
primary newsroom workflow. Moved it into the always-visible nav
alongside Stacijas/Kartes/Faktiskā/Ūdens, and gave every visible nav
item a small icon (reusing the same icons as their Home cards) so the
header matches the icon treatment the overflow menu already had.
2026-08-23 13:31:22 +03:00
b0txec 5c00e48bdd Fix temperature numbers sitting visually high on the map badges
canvas textBaseline "middle" centers on font em-box metrics, not
visible ink — digits have no descenders, so they read as sitting too
high in their boxes. Center on the actual glyph bounds instead,
matching the technique already used correctly in Ūdens's drawRanges.
Shared by Faktiskā and the older Kartes comparison map.
2026-08-23 13:31:17 +03:00
b0txec a1c2fd5d7d Declutter Faktiskā: collapse stations and map settings by default
Both "Jaunākās temperatūras" and "Kartes noformējums" dumped their
full content on load, forcing a scroll past both just to reach the
map/export on every visit. Both now collapse by default (Faktiskā
only; the older non-production Kartes overlay panel keeps its current
always-open behavior) behind a toggle that still surfaces a manual-
override count so nothing is silently hidden.

Also fixed a real bug found in the process: Faktiskā's resolution
buttons carried both "resolutionChoices" and "faktiskaResolutionChoices"
classes, and the former (defined in waterTemperature.css for a narrow
sidebar layout) was winning the cascade in this wide-content context,
forcing full-width, left-aligned buttons instead of the intended
compact pair.

Restructured "Kartes noformējums" into distinct bordered cards instead
of one continuous flow, translated the still-English "Weather symbols"
/"City assignments" block to Latvian ("Laikapstākļu simboli"/"Mākoņi
pilsētām"), and collapsed the city list to show only active exceptions
by default with a "Rādīt pārējās pilsētas" button to reveal the rest.
2026-08-23 13:31:12 +03:00
b0txec 744d706c83 Rework the visual palette: muted blue-gray backdrop, bright accent kept
Replaced the flat near-white page background with a layered blue-gray
gradient (from a sampled palette) for actual visual depth, while
keeping buttons/icons/links on the original saturated accent blue so
interactive elements still stand out against the calmer backdrop.
Also removed the "Testa dati" badge on Home, a synthetic-data leftover
now that both Rocky and the VPS run on real data only.
2026-08-23 13:31:02 +03:00
b0txec e19f4899b7 Wipe Rocky's synthetic weather rows too, matching the VPS
Backed up locally via pg_dump before truncating; both environments now
hold only real open-data observations.
2026-08-23 12:39:33 +03:00
b0txec 4a104c18dd Record the 138f57c8 full go-live VPS release
Deployed everything since 6185dbf (Brīdinājumi draggable symbols, 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, and wiped the
VPS weather table's synthetic rows after a PostgreSQL backup.
2026-08-23 12:17:27 +03:00
b0txec 138f57c808 Enable real scheduled data ingestion on the VPS
Flips ENABLE_SCHEDULED_JOBS on for the VPS release: the open-data
station and water-temperature paths are proven safe on Rocky.
ENABLE_LEGACY_PROVIDER_JOBS stays off pending real FTP/HARMONIE
credentials.
2026-08-23 12:09:41 +03:00
b0txec 69a5260e5f Bring architecture/workflow docs current with real-data ingestion
ARCHITECTURE.md, PRODUCT_WORKFLOWS.md, DEVELOPMENT_AND_STAGING.md, and
README.md still described the removed synthetic-seed staging setup and
Ūdens as pure manual entry. Updates this session (open-data station
ingestion, the scheduler split, water-temperature auto-populate)
weren't reflected outside UPDATE_ROADMAP.md's changelog.
2026-08-23 12:03:57 +03:00
b0txec 81b41d4ee3 Document the real per-zone water-temperature range fix in the roadmap
References 9eca9eb.
2026-08-23 11:49:27 +03:00
b0txec 9eca9ebaf3 Show real regional water-temperature ranges, not one station duplicated as both bounds
Classify all 65 LVĢMC stations reporting water temperature (56 inland
WTEMD + 9 coastal SEDUT) into the six Ūdens zones by geography, and
report the real min/max across each zone's currently-reporting
stations instead of one hand-picked station's single value shown
twice. Drops readings older than 12h so a stuck sensor can't skew a
zone's range.
2026-08-23 11:45:43 +03:00
b0txec ed768fc6af Document water temperature backend and frontend work in the roadmap
References 4036d24-4b92272 (backend) and 5a92465 (frontend).
2026-08-23 11:27:57 +03:00
b0txec 5a924653af Auto-populate Ūdens temperatures from real LVĢMC data, with manual override
Mirrors the Faktiskā pattern: fetch on load, auto-fill min/max per zone,
track manual overrides, per-zone reset, loading/error states.
2026-08-23 11:27:26 +03:00
b0txec 4b92272e64 Fix Latgale water-temperature station: Daugavpils only reports water level, not temperature 2026-08-23 11:17:30 +03:00
b0txec 4036d2477f Add real-time water temperature backend (Ūdens), fetch-on-demand like warnings 2026-08-23 11:15:33 +03:00
b0txec ea1c563ff3 Document scheduler split and METEO_* removal 2026-08-23 10:50:08 +03:00
b0txec 39fcb3ed6b Remove the dead METEO_* fetch path
Confirmed via git history it's not a separate vendor: the very first
commit (2023-04-13) included real sample CSVs from it with a Latvian
header identical to the LVGMC/open-data fields — same underlying LVĢMC
data, just an earlier delivery mechanism superseded by the FTP feed and
never removed. Never wired into anything that runs (Server.scala's
import was already commented out).
2026-08-23 10:49:26 +03:00
b0txec 6ddfe73ca0 Split scheduled jobs so the working open-data fetch can run without the credential-less FTP/Harmonie jobs crash-looping the app 2026-08-23 10:34:44 +03:00
b0txec 82d9e0ba6e Log the synthetic-data-generation removal 2026-08-23 10:24:38 +03:00
b0txec cb25316cdf Stop generating synthetic weather data 2026-08-23 10:24:23 +03:00
b0txec a22872a328 Document the real open-data station ingestion path (Phase 1) 2026-08-23 10:09:51 +03:00
b0txec 893a09a4d3 Add real open-data station observations alongside the synthetic/FTP feed 2026-08-23 10:08:58 +03:00
b0txec 29f4212901 Document draggable per-warning symbol placement in Brīdinājumi 2026-08-23 09:33:45 +03:00
b0txec c97875d6fe Add draggable, resizable weather-symbol placement to Brīdinājumi 2026-08-23 09:32:47 +03:00
b0txec 65f761fc8c Record the 6185dbf warning-hardening VPS release 2026-08-22 22:55:11 +03:00
b0txec 6185dbfdb0 Harden LVĢMC warning fetching against partial failures and type drift 2026-08-22 22:41:35 +03:00
b0txec 0882a52f78 Record the b5150ab VPS release across the docs 2026-08-22 22:23:43 +03:00
b0txec b5150abd46 Document multi-warning Brīdinājumi selection and chip restyle 2026-08-22 21:52:25 +03:00
b0txec 56b871606f Support multi-warning Brīdinājumi selection with narrower, solid-color chips 2026-08-22 21:51:09 +03:00
b0txec c563012df7 Document the decoupled Brīdinājumi selection/detail interaction 2026-08-22 21:42:24 +03:00