Source management console: pause/resume, flag/clear, decision metrics
Turn the Sources tab into a real management console (per Codex):
* source_health now lists ALL sources (active + paused) with backing metrics:
served / accepted_total / total_articles / duplicates + acceptance & duplicate
rates + review_reason, alongside last success/attempt, next poll, failures.
* Admin endpoints (gated, 404 on missing): POST sources/{id}/active (pause/
resume) and /review (flag/clear with reason).
* Pausing only stops future polling — the feed query has no active filter, so a
paused source's accepted articles stay live.
* Frontend: metric table + Paused filter + per-row Pause/Resume & Flag/Clear
(optimistic, revert on failure). Attention 'resting' now scoped to active.
Retire/Delete intentionally deferred (distinct lifecycle state, later).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -381,6 +381,15 @@ class FeedbackReadBody(BaseModel):
|
||||
read: bool = True
|
||||
|
||||
|
||||
class SourceActiveBody(BaseModel):
|
||||
active: bool = True
|
||||
|
||||
|
||||
class SourceReviewBody(BaseModel):
|
||||
flag: bool = False
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
_FEEDBACK_CATEGORIES = {"idea", "concern", "bug", "praise", "other"}
|
||||
|
||||
# The only event kinds we record. All aggregate, non-personal.
|
||||
@@ -813,6 +822,31 @@ def create_app() -> FastAPI:
|
||||
conn.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@app.post("/api/admin/sources/{sid}/active")
|
||||
def admin_source_active(sid: int, body: SourceActiveBody, request: Request) -> dict:
|
||||
# Pause/resume a feed. Pausing only stops future polling — its already-
|
||||
# accepted articles stay live (the feed query doesn't filter on active).
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
cur = conn.execute("UPDATE sources SET active = ? WHERE id = ?", (1 if body.active else 0, sid))
|
||||
if cur.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="source not found")
|
||||
conn.commit()
|
||||
return {"ok": True, "active": body.active}
|
||||
|
||||
@app.post("/api/admin/sources/{sid}/review")
|
||||
def admin_source_review(sid: int, body: SourceReviewBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
cur = conn.execute(
|
||||
"UPDATE sources SET review_flag = ?, review_reason = ? WHERE id = ?",
|
||||
(1 if body.flag else 0, (body.reason or None) if body.flag else None, sid),
|
||||
)
|
||||
if cur.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="source not found")
|
||||
conn.commit()
|
||||
return {"ok": True, "flag": body.flag}
|
||||
|
||||
@app.get("/api/admin/stats")
|
||||
def admin_stats(request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
|
||||
Reference in New Issue
Block a user