Commit Graph

349 Commits

Author SHA1 Message Date
b0txec 1e83e48cba Fix a real CSS collision and remove dead frontend code
CI / backend (push) Successful in 1m8s
CI / frontend (push) Successful in 43s
Result.css (Kartes' results grid/list): a newer consolidated .item rule
used background:var(--surface)!important to force out an older duplicate
rule instead of removing it -- the old rule's background-color was fully
dead but its padding/text-align/max-width were still silently active
underneath. Consolidated into one rule per view, dropped the !important.

Dead code, verified via a systematic check of every class in each
stylesheet against actual .tsx usage (not just the classes a prior review
happened to flag -- that list undercounted by several in mapGraphics.css
alone):
- mapGraphics.css: an entire leftover "Production layout" block (9 classes:
  graphicsSetup, graphicsMain, dataOptions, dataOptionsGrid, productionOptions,
  productionToolbar, loadMapButton, choiceGroup, windChoice), including a
  scoped selector (.mapGraphicsPage .workspaceRail) that never matched
  because no such element exists in the current Faktiskā markup -- leftover
  from an earlier panel restructure.
- index.css / aggregator.css: unused .grid-1-1/.grid-1-2 utility classes
  and a standalone .panel rule superseded by the more specific
  cityPanel/queryPanel/resultsPanel variants.
- calendar.module.css: .calendarNav (never rendered) and .nextMonth (no
  next-month dimming logic exists, only prevMonth).
- DateList.tsx: an empty <ul> wrapping fully commented-out code, superseded
  by the Calendar component already rendered above it.

Verified: typecheck clean, production build succeeds, and headless-browser
screenshots of every affected page (Kartes including an actual query
result, Faktiskā, Arhīvs) show no visual regression.
2026-08-25 10:38:07 +03:00
b0txec 464e03581c Add shared side padding to appContent for breathing room on full-bleed pages
CI / backend (push) Successful in 1m17s
CI / frontend (push) Successful in 46s
Kartes, Stacijas, and Apskats had no CSS of their own and rendered fully
edge-to-edge (no other layout control was actually shared across all pages
before this -- appContent only set min-height). clamp(16px,3vw,48px) gives
consistent side padding globally while staying full-width, not adding a
centered max-width column -- the ask was breathing room, not a narrower
layout. Verified on Rocky: Kartes now has visible margins, Home (which
already set its own max-width/padding) shows no visible double-padding
issue at 1600px viewport width.
2026-08-25 10:22:04 +03:00
b0txec ea583ed57d Correct two doc sections written before the real CI bugs were found
CI / backend (push) Successful in 1m27s
CI / frontend (push) Successful in 52s
The changelog claimed ci.yml #1 verified green (it didn't -- that was a
wrong WebFetch summary of the Gitea Actions page, reported as fact without
checking it visually) and CONTINUOUS_INTEGRATION.md described the
HOME/cache permission warning as harmless (it wasn't -- it blocked
resolving actions/checkout entirely). Both corrected with what actually
happened: three real, different bugs, found only by running the workflow
and reading user-provided screenshots of the actual failures.
2026-08-24 22:27:39 +03:00
b0txec 27ab795f54 Add a combined 'all' push remote and document the new push habit
CI / backend (push) Successful in 1m32s
CI / frontend (push) Successful in 55s
Pushing to rocky alone doesn't trigger CI (Gitea can't see that private bare
repo), and two separate git push commands are easy to half-remember. 'all'
has two push URLs (rocky, gitea) so one push updates the staging checkout's
source and triggers CI together. Deliberately excludes origin (GitHub)
pending the still-open question about that repo's ownership/involvement.
2026-08-24 22:25:35 +03:00
b0txec 2c78ae8286 Install a real Node 20 in the backend CI job, not Debian bullseye's default
CI / backend (push) Successful in 1m28s
CI / frontend (push) Successful in 56s
Node got installed (previous fix worked) but actions/checkout@v4's bundled
JS uses modern syntax (class static blocks) requiring Node >=16.11.
hseeberger/scala-sbt is Debian bullseye-based, whose default apt "nodejs"
package is a stale Node 12 -- SyntaxError: Unexpected token '{' at "static
{". HOP's own image is bookworm-based (newer default), which is why they
never hit this. Use NodeSource's setup script to get an actual current Node
20 regardless of the distro's packaged version.
2026-08-24 22:11:47 +03:00
b0txec 7e1b492a0c Install Node in the backend CI job before checkout
CI / backend (push) Failing after 11s
CI / frontend (push) Successful in 53s
The frontend job succeeded after the runner HOME fix, but backend failed at
a different point: actions/checkout is a JavaScript action and needs a Node
runtime inside the job container to run it at all (exec: "node": executable
file not found in $PATH), and hseeberger/scala-sbt has no Node installed.
Same lesson HOP's own CI setup already documented ("JavaScript actions need
Node in the job image") -- mirroring their fix: install git+nodejs via apt
before the checkout step.
2026-08-24 22:04:06 +03:00
b0txec 1a99515182 Trigger CI to verify the runner HOME fix
CI / backend (push) Failing after 2s
CI / frontend (push) Successful in 53s
Both jobs in the first two CI runs failed identically at the checkout step:
"Unable to clone https://github.com/actions/checkout ... mkdir /.cache:
permission denied". The runner container runs as a fixed uid with no
writable $HOME, so act cloning action sources into its cache directory
failed outright -- this affects every remote action reference, not just the
actions/cache action type as I'd assumed earlier. Fixed by setting
HOME=/data (the already-writable bind-mounted data directory) on the
runner container; not a source change, just re-triggering CI to confirm.
2026-08-24 22:01:10 +03:00
b0txec a989d2a6a8 Document the Gitea Actions CI setup and the follow-up security review
CI / backend (push) Failing after 1s
CI / frontend (push) Failing after 1s
Adds CONTINUOUS_INTEGRATION.md (mirrors HOP's CI doc structure), links it
from README, and records the CI changelog entry plus the follow-up review's
open findings (scheduler crash-loop, System.gc(), connection pooling, the
PostgresService field-routing invariant, and the mislabeled main-method
"tests") in the roadmap's Phase 2/Phase 5 checklists.
2026-08-24 21:55:48 +03:00
b0txec 5b88e69c40 Add Gitea Actions CI workflow
CI / backend (push) Failing after 15s
CI / frontend (push) Failing after 18s
Runs on push/PR to codex/staging-baseline plus manual dispatch: sbt test for
the backend, and npm ci/typecheck/build/audit (full + production-only) for
the frontend, matching the checks already documented as the manual Rocky
verification routine. Verified sbt test compiles and passes with no .env
file present (matching what a checkout-only CI job will actually have) via
a git-archive dry run before writing this.

Two separate jobs, each with its own container image, rather than one
shared runner-label image with ad-hoc installs — reuses the exact
hseeberger/scala-sbt image already used all session for local backend
builds, and a plain node:20-bookworm for the frontend, so nothing needs
apt-get bootstrapping of a second language runtime into the same container.
2026-08-24 21:52:29 +03:00
b0txec 1e6f5298ae Record the missed FTP route and its fix in the roadmap changelog 2026-08-24 12:20:29 +03:00
b0txec 9bab93daa6 Gate /api/show/lvgmc-forecast behind ENABLE_LVGMC_FTP_JOBS too
Caught by an independent follow-up review: the FTP auth-bypass fix in
6b9c7cf only gated /api/fetch/lvgmc/stations, the one route the original
security review named. This sibling route calls the same fetch.fetchFile,
which opens a real, unauthenticated FTP login to LVGMC regardless of any
caller-supplied filename, and was only getting the traversal fix
(ValidateFileName) applied to it, not the auth-bypass fix. Traced every
caller of fetch.fetchFile/fetchWeatherStations in Server.scala/Main.scala
this time to confirm these are the only two HTTP-reachable call sites and
both are now gated.
2026-08-24 12:15:56 +03:00
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