架構決策 (ADR)
Admin 生成 endpoint 獨立於家長端(不複用 /v1/contents/generate)
Context
Admin 後台需要讓內容管理員直接生成「YiBi 精選(curated)故事」並上架,不需要綁定任何家庭或孩子。
家長端已有 POST /v1/contents/generate,但其設計前提是:
- 強制要求
child_profile_id(無法接受 null) - 生成後進入 DRAFT_READY 狀態等待家長批准
- Celery task 用 child profile 的年齡參數 + 家長 context 構建 prompt
Admin curated 生成的語意完全不同:無家庭綁定、admin 直接審閱草稿、最終由 admin 呼叫 approve 上架。
Decision
結論:新建獨立的 admin/generate.py router,提供三個獨立 endpoint:
POST /api/v1/admin/generation-jobs— 觸發 curated 生成GET /api/v1/admin/generation-jobs/{job_id}— 查詢進度POST /api/v1/admin/generation-jobs/{job_id}/approve— Admin 審閱後上架
不修改家長端 POST /v1/contents/generate,兩個路徑完全獨立。
Alternatives Considered
- 複用家長端
/v1/contents/generate,加 optional child_profile_id — 考慮過。問題是 task 邏輯會累積大量if is_curated判斷,污染家長端路徑,且 DRAFT_READY 狀態機對 admin 流程無意義。 - 在家長端 generate 加 admin override flag — 考慮過。flag 會讓安全邊界模糊,admin 端 vs 家長端的授權邏輯難以獨立審計。
Consequences
好:
- 家長端與 admin 端語意完全隔離,各自可獨立演進
- 安全邊界清晰(admin route 有獨立 auth check)
- Celery task 的 curated path 加
is_curated=True旗標,不影響既有家長路徑
壞 / 代償:
- GenerationJob model 需要允許
family_id和child_profile_id為 NULL(見 ADR-0002) - 兩套 endpoint 有部分重複邏輯(job status polling),需小心保持一致
中性:
- Admin frontend 用獨立的
generate-curated.tsx頁面 - Polling 策略相同(3 秒 interval,5 分鐘 timeout)