Spaces:
Running
Running
| """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() | |
| 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, | |
| } | |
| 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"]} | |
| 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} | |