Commit Graph

144 Commits

Author SHA1 Message Date
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 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 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 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 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 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 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 893a09a4d3 Add real open-data station observations alongside the synthetic/FTP feed 2026-08-23 10:08:58 +03:00
b0txec 6185dbfdb0 Harden LVĢMC warning fetching against partial failures and type drift 2026-08-22 22:41:35 +03:00
b0txec bc5bee171c Add LVGMC warning map workflow 2026-08-22 20:11:20 +03:00
b0txec f3197bf340 Return API responses as JSON 2026-08-22 14:22:15 +03:00
b0txec e726291bdb Build fixed Faktiska production workflow 2026-08-19 17:11:58 +03:00
b0txec b1fff676af Add isolated staging and synthetic weather workflow 2026-08-18 21:26:34 +03:00
Guntis Smaukstelis fabf233c31 Add snow depth to downloadable data 2025-11-19 11:54:47 +02:00
Guntis Smaukstelis 9ab4cfe3fe Changed api to fetch manually only most recent files 2025-11-04 22:58:53 +02:00
Guntis Smaukstelis 56b85cbae2 Fetch weather stations in cron from ftp server 2025-10-29 23:44:50 +02:00
Guntis Smaukstelis d621c2c89a Faster downloading grib files 2025-07-11 11:12:06 +03:00
Guntis Smaukstelis cc9077360b Dont break grib parsing on corrupted data 2025-05-05 22:56:10 +03:00
Guntis Smaukstelis 87792b955c Increase grib download timeout to 60 seconds 2025-03-24 20:45:44 +02:00
Guntis Smaukstelis b44364d9a6 Write fatal errors to logs, view logs, clear tmp folder 2025-03-16 15:38:11 +02:00
Guntis Smaukstelis 329776b67f Move debug logic from server to separate file 2025-03-16 13:33:52 +02:00
Guntis Smaukstelis 841b4b6ec9 Getting lvgmc csv files in frontend 2025-03-10 16:56:24 +02:00
Guntis Smaukstelis 9d6b86e218 Fetching from ftp server 2025-03-09 20:02:16 +02:00
Guntis Smaukstelis 52180544e9 Add debugging rest methods for folders and time 2025-03-06 00:00:21 +02:00
Guntis Smaukstelis e9408391ac Rest api for deleting old forecasts 2025-03-05 23:25:42 +02:00
Guntis Smaukstelis 17cb0e550a Recover dateTime parsing with hardcded date 2025-03-03 22:32:39 +02:00
Guntis Smaukstelis b43764b868 Restart program 5sec after fatal error 2025-03-03 22:22:20 +02:00
Guntis Smaukstelis 5b99d12d9e Try().recover() for reference date time extracting from grib 2025-03-03 21:31:39 +02:00
Guntis Smaukstelis f0c5d2c42b Fix Dockerfile.local except volumes 2025-03-01 18:16:56 +02:00
Guntis Smaukstelis 2206e1018a Remove console logs 2025-02-26 13:32:07 +02:00
Guntis Smaukstelis 5e47a336e6 Process slideshow requests in batches, add more memory 2025-02-25 18:19:42 +02:00
Guntis Smaukstelis 3696172c72 Optimize getAllStructure with parTraverseN, chunk reading with RandomAccessFile 2025-02-24 23:56:21 +02:00
Guntis Smaukstelis b6cc4a4b24 Show which slide show hours is fetched 2025-02-23 20:21:11 +02:00
Guntis Smaukstelis cb9e6a88a0 Draw latvia contour, rescale canvas 2025-02-18 15:37:17 +02:00
Guntis Smaukstelis 692594dcad Save csv to postgres after fetching 2025-02-16 16:42:35 +02:00
Guntis Smaukstelis 0b221bcfee Download structure for all files in one go. For better UX 2025-02-14 20:26:57 +02:00
Guntis Smaukstelis 8bd5be413d Cleanup now keeps only needed grib files 2025-02-14 14:59:07 +02:00
Guntis Smaukstelis 15cfbd1801 Refactor to one scheduler, separated csv/grib under one folder 2025-02-14 14:46:31 +02:00
Guntis Smaukstelis 3b8bb7d826 Hacky way to hardcode routes for spa 2025-02-14 12:58:19 +02:00
Guntis Smaukstelis dc82b95a8a Fix paths in docker images 2025-02-13 23:21:19 +02:00
Guntis Smaukstelis 37ac7eb0e3 Fix deployment for fly.io 2025-02-13 16:11:50 +02:00
Guntis Smaukstelis c3f85fb477 Create just one scheduler instance 2025-02-13 13:15:46 +02:00
Guntis Smaukstelis 78060f61c0 Add fetch recent gribs to scheduler 2025-02-12 23:32:30 +02:00