feat: Tutorial-Fortschritt serverseitig persistieren (Resume/Restart)

- Neuer Router /api/tutorial mit GET/PUT/DELETE für Fortschritt pro User
- DB-Migration: tutorial_step + tutorial_completed in users-Tabelle
- Resume-Dialog bei abgebrochenem Tutorial (Fortsetzen/Neu starten)
- Chat-Hinweis passt sich dem Tutorial-Status dynamisch an
- API-Methoden: getTutorialState, saveTutorialState, resetTutorialState

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-17 22:51:06 +01:00
Ursprung 4b9ed6439a
Commit 5e194d43e0
6 geänderte Dateien mit 202 neuen und 19 gelöschten Zeilen

77
src/routers/tutorial.py Normale Datei
Datei anzeigen

@@ -0,0 +1,77 @@
"""Tutorial-Router: Fortschritt serverseitig pro User speichern."""
import logging
from fastapi import APIRouter, Depends
from auth import get_current_user
from database import db_dependency
import aiosqlite
logger = logging.getLogger("osint.tutorial")
router = APIRouter(prefix="/api/tutorial", tags=["tutorial"])
@router.get("/state")
async def get_tutorial_state(
current_user: dict = Depends(get_current_user),
db: aiosqlite.Connection = Depends(db_dependency),
):
"""Tutorial-Fortschritt des aktuellen Nutzers abrufen."""
cursor = await db.execute(
"SELECT tutorial_step, tutorial_completed FROM users WHERE id = ?",
(current_user["id"],),
)
row = await cursor.fetchone()
if not row:
return {"current_step": None, "completed": False}
return {
"current_step": row["tutorial_step"],
"completed": bool(row["tutorial_completed"]),
}
@router.put("/state")
async def save_tutorial_state(
body: dict,
current_user: dict = Depends(get_current_user),
db: aiosqlite.Connection = Depends(db_dependency),
):
"""Tutorial-Fortschritt speichern (current_step und/oder completed)."""
updates = []
params = []
if "current_step" in body:
step = body["current_step"]
if step is not None and (not isinstance(step, int) or step < 0 or step > 31):
from fastapi import HTTPException
raise HTTPException(status_code=422, detail="current_step muss 0-31 oder null sein")
updates.append("tutorial_step = ?")
params.append(step)
if "completed" in body:
updates.append("tutorial_completed = ?")
params.append(1 if body["completed"] else 0)
if not updates:
return {"ok": True}
params.append(current_user["id"])
await db.execute(
f"UPDATE users SET {', '.join(updates)} WHERE id = ?",
params,
)
await db.commit()
return {"ok": True}
@router.delete("/state")
async def reset_tutorial_state(
current_user: dict = Depends(get_current_user),
db: aiosqlite.Connection = Depends(db_dependency),
):
"""Tutorial-Fortschritt zuruecksetzen (fuer Neustart)."""
await db.execute(
"UPDATE users SET tutorial_step = NULL, tutorial_completed = 0 WHERE id = ?",
(current_user["id"],),
)
await db.commit()
return {"ok": True}