HexaWetter v1.2.0: Security, Admin, Radar, UI
This commit is contained in:
59
api/app/services/radar_sync.py
Normal file
59
api/app/services/radar_sync.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from app.config import RADAR_PRODUCT, RADAR_RETENTION_HOURS, RAW_RADAR_DIR
|
||||
from app.database import upsert_radar_meta
|
||||
from app.http_client import get_client
|
||||
from app.services.dwd_radar import RADAR_FILE_RE, fetch_radar_index
|
||||
|
||||
_FILENAME_RE = re.compile(r"^[a-z0-9_-]+$", re.IGNORECASE)
|
||||
|
||||
|
||||
def utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def cleanup_old_radar_files() -> None:
|
||||
cutoff = utc_now() - timedelta(hours=RADAR_RETENTION_HOURS)
|
||||
if not RAW_RADAR_DIR.exists():
|
||||
return
|
||||
for path in RAW_RADAR_DIR.rglob("*"):
|
||||
if not path.is_file() or not RADAR_FILE_RE.fullmatch(path.name):
|
||||
continue
|
||||
modified = datetime.fromtimestamp(path.stat().st_mtime, timezone.utc)
|
||||
if modified < cutoff:
|
||||
path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
async def sync_radar_files(product: str = RADAR_PRODUCT, limit: int = 6) -> dict:
|
||||
if not _FILENAME_RE.fullmatch(product):
|
||||
raise ValueError("Invalid product")
|
||||
|
||||
latest = await fetch_radar_index(product=product, limit=limit)
|
||||
target_dir = RAW_RADAR_DIR / "composite" / product
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
downloaded: list[str] = []
|
||||
skipped: list[str] = []
|
||||
|
||||
client = get_client()
|
||||
for item in latest["files"]:
|
||||
out = target_dir / item["name"]
|
||||
if out.exists() and out.stat().st_size > 0:
|
||||
skipped.append(item["name"])
|
||||
continue
|
||||
response = await client.get(item["url"], timeout=90.0)
|
||||
response.raise_for_status()
|
||||
out.write_bytes(response.content)
|
||||
downloaded.append(item["name"])
|
||||
modified = datetime.fromtimestamp(out.stat().st_mtime, timezone.utc)
|
||||
await upsert_radar_meta(product, item["name"], out.stat().st_size, modified, "pending")
|
||||
|
||||
cleanup_old_radar_files()
|
||||
return {
|
||||
"product": product,
|
||||
"target_dir": str(target_dir),
|
||||
"downloaded": downloaded,
|
||||
"skipped": skipped,
|
||||
}
|
||||
@@ -86,11 +86,11 @@ async def fetch_all_warnings() -> list[dict[str, Any]]:
|
||||
|
||||
client = get_client()
|
||||
response = await client.get(DWD_WARNINGS_URL, timeout=10.0)
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
if text.startswith("warnWetter.loadWarnings("):
|
||||
text = text[text.index("(") + 1 : text.rindex(")")]
|
||||
payload = json.loads(text)
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
if text.startswith("warnWetter.loadWarnings("):
|
||||
text = text[text.index("(") + 1 : text.rindex(")")]
|
||||
payload = json.loads(text)
|
||||
|
||||
warnings = _flatten_warnings(payload)
|
||||
await cache_set(cache_key, warnings, CACHE_TTL_WARNINGS)
|
||||
|
||||
@@ -4,6 +4,7 @@ import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from app.cache import cache_get, cache_set
|
||||
from app.http_client import get_client
|
||||
from app.config import CACHE_TTL_WMS_TIMES, DWD_WMS_URL
|
||||
|
||||
@@ -59,10 +60,10 @@ async def fetch_wms_time_steps(layer: str, minutes: int = 120) -> dict:
|
||||
params = {"service": "WMS", "version": "1.3.0", "request": "GetCapabilities"}
|
||||
client = get_client()
|
||||
response = await client.get(DWD_WMS_URL, params=params, timeout=15.0)
|
||||
response.raise_for_status()
|
||||
dimension = _find_layer_dimension(response.text, layer)
|
||||
if not dimension:
|
||||
raise ValueError(f"No TIME dimension found for layer {layer}")
|
||||
response.raise_for_status()
|
||||
dimension = _find_layer_dimension(response.text, layer)
|
||||
if not dimension:
|
||||
raise ValueError(f"No TIME dimension found for layer {layer}")
|
||||
|
||||
if "/" in dimension:
|
||||
start, end, step_minutes = _parse_time_dimension(dimension)
|
||||
|
||||
Reference in New Issue
Block a user