feat(api): /db/snapshot, /db/waveforms/recent.zip, gated /db/restore

This commit is contained in:
2026-07-02 06:53:24 +00:00
parent 280b3d25ec
commit b56eb8c692
2 changed files with 118 additions and 0 deletions
+65
View File
@@ -72,6 +72,7 @@ from sfm.cache import SFMCache, get_cache
from sfm.database import SeismoDb
from sfm.live_cache import LiveCache as _LiveCache
from sfm.waveform_store import WaveformStore
from sfm import db_snapshot
logging.basicConfig(
level=logging.INFO,
@@ -2808,6 +2809,70 @@ def db_unit_waveforms_zip(
return StreamingResponse(buf, media_type="application/zip", headers=headers)
# ── Full-snapshot bundle endpoints (see sfm/db_snapshot.py) ───────────────────
# SFM_DB_RESTORE_ENABLED gates /db/restore (dormant → 404). Set true on dev only.
@app.get("/db/snapshot")
def db_snapshot_download() -> FileResponse:
"""WAL-safe copy of seismo_relay.db, for the terra-view full-snapshot bundle."""
import os
import tempfile
from starlette.background import BackgroundTask
fd, tmp = tempfile.mkstemp(suffix=".db")
os.close(fd)
db_snapshot.snapshot_db(_get_db().db_path, Path(tmp))
return FileResponse(
tmp, media_type="application/x-sqlite3", filename="seismo_relay.db",
background=BackgroundTask(os.unlink, tmp),
)
@app.get("/db/waveforms/recent.zip")
def db_waveforms_recent_zip(n: int = Query(200, ge=1, le=2000)) -> FileResponse:
"""Zip of the n most-recent events' waveform + sidecar files."""
import os
import tempfile
from starlette.background import BackgroundTask
fd, tmp = tempfile.mkstemp(suffix=".zip")
os.close(fd)
stats = db_snapshot.build_recent_waveforms_zip(_get_db(), _get_store(), n, Path(tmp))
return FileResponse(
tmp, media_type="application/zip", filename="waveforms.zip",
headers={"X-Event-Count": str(stats["events"]), "X-File-Count": str(stats["files"])},
background=BackgroundTask(os.unlink, tmp),
)
@app.post("/db/restore")
async def db_restore(
db: UploadFile = File(...),
waveforms: Optional[UploadFile] = File(None),
) -> dict:
"""Destructive: swap seismo_relay.db + unpack waveforms. Dormant (404) unless
SFM_DB_RESTORE_ENABLED is set."""
import os
import shutil as _shutil
import tempfile
if not db_snapshot.sfm_db_restore_enabled():
raise HTTPException(status_code=404, detail="Not found")
def _save(upload) -> Path:
fd, tmp = tempfile.mkstemp()
with os.fdopen(fd, "wb") as out:
_shutil.copyfileobj(upload.file, out)
return Path(tmp)
db_tmp = _save(db)
wf_tmp = _save(waveforms) if waveforms is not None else None
try:
return db_snapshot.restore_db_and_waveforms(
_get_db().db_path, _get_store().root, db_tmp, wf_tmp,
)
finally:
db_tmp.unlink(missing_ok=True)
if wf_tmp is not None:
wf_tmp.unlink(missing_ok=True)
@app.get("/db/monitor_log")
def db_monitor_log(
serial: Optional[str] = Query(None, description="Filter by unit serial"),