diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index 58e9e46..7961c76 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -190,6 +190,94 @@ async def accounts_page(request: Request): return template.render(**ctx) +@router.get("/accounts/settings", response_class=HTMLResponse) +async def settings_page(request: Request): + """User settings page — profile, forges, tokens.""" + from jinja2 import Environment, FileSystemLoader + env = Environment(loader=FileSystemLoader("app/templates")) + sidebar = _sidebar_data(request, []) + user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + if not user: + from fastapi.responses import RedirectResponse + return RedirectResponse("/auth/login?provider=local", status_code=302) + # Check forge connections + gitea_connected = False + github_connected = False + if user.get("id"): + with get_conn() as conn: + tokens = conn.execute( + "SELECT provider FROM user_oauth_tokens WHERE user_id=?", (user["id"],) + ).fetchall() + for t in tokens: + if t["provider"] == "gitea": + gitea_connected = True + elif t["provider"] == "github": + github_connected = True + ctx = {**sidebar, "user": user, "gitea_connected": gitea_connected, "github_connected": github_connected} + template = env.get_template("settings.html") + response = template.render(**ctx) + return HTMLResponse(content=response) + + +# ═══════════ User API endpoints ═══════════ + +def _get_user_id(request: Request) -> int: + user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + return user["id"] if user and user.get("id") else 1 + + +@router.put("/api/user/profile") +async def update_profile(request: Request): + import json + body = await request.json() + full_name = body.get("full_name", "").strip() + uid = _get_user_id(request) + with get_conn() as conn: + conn.execute("UPDATE users SET full_name=? WHERE id=?", (full_name, uid)) + conn.commit() + return {"status": "ok"} + + +@router.put("/api/user/password") +async def update_password(request: Request): + import json + from app.password_utils import hash_password + body = await request.json() + password = body.get("password", "").strip() + if len(password) < 6: + return {"error": "Password must be at least 6 characters"} + uid = _get_user_id(request) + with get_conn() as conn: + conn.execute("UPDATE users SET password_hash=? WHERE id=?", (hash_password(password), uid)) + conn.commit() + return {"status": "ok"} + + +@router.post("/api/user/token") +async def generate_token(request: Request): + import secrets + uid = _get_user_id(request) + token = secrets.token_hex(32) + with get_conn() as conn: + conn.execute( + "INSERT OR REPLACE INTO user_tokens (gitea_user_id, gitea_token, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)", + (uid, token), + ) + conn.commit() + return {"token": f"fd_{token}"} + + +@router.delete("/api/user/forge/{provider}") +async def disconnect_forge(request: Request, provider: str): + uid = _get_user_id(request) + with get_conn() as conn: + conn.execute( + "DELETE FROM user_oauth_tokens WHERE user_id=? AND provider=?", (uid, provider) + ) + conn.commit() + return {"status": "ok"} + + @router.get("/", response_class=HTMLResponse) async def dashboard( request: Request,