"""FlowDeck — v5.2.0 Onboarding: /welcome wizard + its API. First-launch experience: create workspace → connect a forge (optional) → create the first project (welcome page), then land in the app. """ from __future__ import annotations import json import logging from fastapi import APIRouter, Body, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from app.auth.session import SessionManager from app.db import get_conn logger = logging.getLogger(__name__) router = APIRouter(tags=["onboarding"]) WORKSPACE_COOKIE = "flowdeck_workspace" def _require_user(request: Request) -> dict: user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) if not user or not user.get("id"): raise HTTPException(status_code=401, detail="Not authenticated") return user @router.get("/welcome", response_class=HTMLResponse) def onboarding_page(request: Request): """Onboarding wizard. Redirects logged-out users to login and users who already have a workspace straight to the app.""" user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) if not user: return RedirectResponse("/auth/login?provider=local", status_code=302) with get_conn() as conn: ws_count = conn.execute( "SELECT COUNT(*) FROM workspaces WHERE owner_id=?", (user["id"],) ).fetchone()[0] if ws_count > 0: return RedirectResponse("/workspaces", status_code=302) from app.templating import ENV env = ENV template = env.get_template("welcome.html") return HTMLResponse(content=template.render( user=user, gitea_url_configured=_forge_configured("gitea"), github_url_configured=_forge_configured("github"), )) def _forge_configured(provider: str) -> bool: try: from app.auth.providers import get_provider return get_provider(provider) is not None except Exception: return False # ═══════════ Onboarding API ═══════════ @router.post("/api/onboarding/workspace") def onboarding_create_workspace(request: Request, body: dict = Body(default={})): """Step 1 — create the first local workspace.""" user = _require_user(request) name = (body.get("name") or "").strip() or "My Workspace" with get_conn() as conn: cur = conn.execute( "INSERT INTO workspaces (name, owner_id, settings_json) VALUES (?, ?, '{}')", (name, user["id"]), ) conn.execute( "INSERT INTO workspace_members (workspace_id, user_id, role) VALUES (?, ?, 'owner')", (cur.lastrowid, user["id"]), ) conn.commit() ws_id = cur.lastrowid response = JSONResponse({"status": "ok", "id": ws_id, "name": name}) response.set_cookie(WORKSPACE_COOKIE, str(ws_id), max_age=86400 * 30, httponly=True, path="/") return response @router.post("/api/onboarding/project") def onboarding_create_project(request: Request, body: dict = Body(default={})): """Step 3 — create the first project: a welcome page in the workspace.""" user = _require_user(request) title = (body.get("title") or "").strip() or "Welcome to FlowDeck" workspace_id = body.get("workspace_id") with get_conn() as conn: ws = None if workspace_id: ws = conn.execute( "SELECT id FROM workspaces WHERE id=? AND owner_id=?", (workspace_id, user["id"]), ).fetchone() if not ws: ws = conn.execute( "SELECT id FROM workspaces WHERE owner_id=? ORDER BY id LIMIT 1", (user["id"],), ).fetchone() if not ws: raise HTTPException(status_code=400, detail="Create a workspace first") blocks = [ {"id": "1", "type": "heading_1", "content": title}, {"id": "2", "type": "paragraph", "content": "Welcome to FlowDeck 🎉 — your workspace is ready."}, {"id": "3", "type": "paragraph", "content": "Use the slash command « / » in any page to add blocks, databases, to-dos and more."}, {"id": "4", "type": "paragraph", "content": "Connect Gitea or GitHub in Settings → Integrations to sync your repositories."}, ] cur = conn.execute( "INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section) " "VALUES (?, ?, ?, ?, 'blocks', 'Private')", (user.get("login", "local"), ws["id"], title, json.dumps(blocks)), ) conn.commit() page_id = cur.lastrowid return {"status": "ok", "id": page_id, "title": title, "workspace_id": ws["id"]}