Blog drafting section (dev-only): - BlogDraftGenerator gathers project data (gaps, proposals, stats) as context and calls Claude to produce Medium-style blog posts - DB schema: blog_drafts table with title, content, tags, cost tracking - Web UI: list, generate (async with live preview), detail (rendered + source toggle), edit, and export routes - 6 writing styles: deep-dive, overview, opinion, listicle, comparison, series-post - Nav link added to sidebar under Proposals Bug fixes found via route testing (scripts/test_all_routes.py): - /authors/<id>: Draft.status → Draft.states (correct attribute name) - /false-positives: add missing `import re` in ratings.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 lines
807 B
Python
31 lines
807 B
Python
"""Blog draft data access for the web dashboard."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from ietf_analyzer.db import Database
|
|
|
|
|
|
def get_all_blog_drafts(db: Database) -> list[dict]:
|
|
"""Return all blog drafts, newest first."""
|
|
drafts = db.all_blog_drafts()
|
|
for d in drafts:
|
|
try:
|
|
d["tags"] = json.loads(d.get("tags") or "[]")
|
|
except (json.JSONDecodeError, TypeError):
|
|
d["tags"] = []
|
|
return drafts
|
|
|
|
|
|
def get_blog_draft_detail(db: Database, draft_id: int) -> dict | None:
|
|
"""Return a single blog draft with parsed tags."""
|
|
d = db.get_blog_draft(draft_id)
|
|
if not d:
|
|
return None
|
|
try:
|
|
d["tags"] = json.loads(d.get("tags") or "[]")
|
|
except (json.JSONDecodeError, TypeError):
|
|
d["tags"] = []
|
|
return d
|