Arag / app /api /onboarding.py
AuthorBot
Add enterprise growth APIs, billing, admin UI, and test suite.
12930b5
Raw
History Blame Contribute Delete
3.26 kB
"""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}