radio: add /api/db.sqlite for offline laptop sync
Streams the read-only archive.db over the same Tailscale-routed port as the search service. Companion to azcomputerguru/radio-archive-portable which curl-fetches from this endpoint and runs locally on the laptop. Disclosure equivalent to /api/search (which already exposes every transcript), so no auth added. Deployed to Jupiter; verified GET returns 60 MB SQLite blob with all 1,405 classifier rows intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
|
||||
DB_PATH = os.environ.get("ARCHIVE_DB", "/data/archive.db")
|
||||
PORT = int(os.environ.get("PORT", "8765"))
|
||||
@@ -218,6 +218,23 @@ def stats():
|
||||
return {"counts": counts, "by_year": by_year}
|
||||
|
||||
|
||||
@app.get("/api/db.sqlite")
|
||||
def download_db():
|
||||
"""Stream the read-only archive.db for offline laptop sync.
|
||||
|
||||
Anyone who can reach /api/search can already read every transcript,
|
||||
so exposing the underlying SQLite file adds no meaningful disclosure.
|
||||
Sync side: curl -o archive.db <host>:<port>/api/db.sqlite
|
||||
"""
|
||||
if not Path(DB_PATH).exists():
|
||||
raise HTTPException(404, "archive db not present")
|
||||
return FileResponse(
|
||||
DB_PATH,
|
||||
media_type="application/vnd.sqlite3",
|
||||
filename="archive.db",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def index():
|
||||
return INDEX_HTML
|
||||
|
||||
Reference in New Issue
Block a user