"""Author RAG — Onboarding Wizard API. Tracks wizard progress so the admin panel knows which step to show. State is stored per-author in the DB using a simple JSON blob. Routes: GET /api/admin/{slug}/onboarding/status → {current_step, completed_steps} POST /api/admin/{slug}/onboarding/complete → mark a step complete POST /api/admin/{slug}/onboarding/dismiss → dismiss wizard entirely """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession import json from app.dependencies import get_current_author, get_db from app.models.user import User router = APIRouter() class StepComplete(BaseModel): step: int # 1-5 ONBOARDING_KEY = "onboarding_completed_steps" def _get_meta(user: User) -> dict: """Read onboarding metadata from user's bio field (temporary storage).""" # We store it in a reserved metadata field until a dedicated table is added try: if user.bio and user.bio.startswith("__meta__"): return json.loads(user.bio[8:]) except Exception: pass return {} async def _set_meta(db: AsyncSession, user: User, meta: dict) -> None: user.bio = "__meta__" + json.dumps(meta) await db.commit() @router.get("/{slug}/onboarding/status") async def onboarding_status( slug: str, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Return onboarding wizard state for this author.""" if author.id != slug: raise HTTPException(403, "Forbidden") meta = _get_meta(author) completed = meta.get("completed_steps", []) dismissed = meta.get("dismissed", False) # Determine next step for step in range(1, 6): if step not in completed: current_step = step break else: current_step = 6 # All done return { "current_step": current_step, "completed_steps": completed, "dismissed": dismissed, "total_steps": 5, "is_complete": len(completed) >= 5 or dismissed, } @router.post("/{slug}/onboarding/complete") async def complete_step( slug: str, body: StepComplete, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Mark a wizard step as completed.""" if author.id != slug: raise HTTPException(403, "Forbidden") if not 1 <= body.step <= 5: raise HTTPException(400, "Step must be between 1 and 5") meta = _get_meta(author) completed = meta.get("completed_steps", []) if body.step not in completed: completed.append(body.step) meta["completed_steps"] = sorted(completed) await _set_meta(db, author, meta) return {"completed_steps": meta["completed_steps"]} @router.post("/{slug}/onboarding/dismiss") async def dismiss_wizard( slug: str, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Dismiss the onboarding wizard — don't show it again.""" if author.id != slug: raise HTTPException(403, "Forbidden") meta = _get_meta(author) meta["dismissed"] = True await _set_meta(db, author, meta) return {"dismissed": True}