Admin CSV export (sources snapshot + audience time-series)
Per Codex v1 — boring-in-the-best-way: inspect/archive operational data outside the app. Admin-gated, Python csv module, text/csv + attachment disposition. * GET /api/admin/export/sources.csv — current-state snapshot per source: name, feed/homepage, status, visible, served/accepted/total, acceptance/duplicate/ accepted-dup/image-coverage %, last success/error, retry-after, review. * GET /api/admin/export/audience.csv?days= — summary block (visitors, returning, accounts, feedback, shares) + a blank line + the daily visits/opens series; range applies to audience, sources is a snapshot. * source_health now also returns feed_url/homepage. Small download links on the Sources + Audience tabs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -160,3 +160,27 @@ def test_safe_fetch_feed_blocks_ssrf():
|
||||
"http://169.254.169.254/latest", "ftp://x/y"):
|
||||
with pytest.raises(RuntimeError):
|
||||
safe_fetch_feed(bad, timeout=2)
|
||||
|
||||
|
||||
def test_export_sources_csv(tmp_path, monkeypatch):
|
||||
app, api = _make(tmp_path, monkeypatch, admin_email="boss@x.com")
|
||||
tc = _signin(app, api, "boss@x.com")
|
||||
r = tc.get("/api/admin/export/sources.csv")
|
||||
assert r.status_code == 200 and r.headers["content-type"].startswith("text/csv")
|
||||
assert 'attachment; filename="sources.csv"' in r.headers["content-disposition"]
|
||||
lines = r.text.splitlines()
|
||||
assert lines[0].startswith("name,feed_url,homepage,status,visible,served")
|
||||
assert any("http://s/f" in ln for ln in lines[1:]) # the seeded source row
|
||||
assert TestClient(app).get("/api/admin/export/sources.csv").status_code == 401 # gated
|
||||
|
||||
|
||||
def test_export_audience_csv(tmp_path, monkeypatch):
|
||||
app, api = _make(tmp_path, monkeypatch, admin_email="boss@x.com")
|
||||
tc = _signin(app, api, "boss@x.com")
|
||||
tc.post("/api/events", json={"kind": "visit", "visitor": "v1"})
|
||||
r = tc.get("/api/admin/export/audience.csv?days=7")
|
||||
assert r.status_code == 200 and r.headers["content-type"].startswith("text/csv")
|
||||
body = r.text
|
||||
assert "metric,value" in body and "window_days,7" in body
|
||||
assert "date,visitors,opens" in body # daily time-series section
|
||||
assert TestClient(app).get("/api/admin/export/audience.csv").status_code == 401 # gated
|
||||
|
||||
Reference in New Issue
Block a user