"""FlowDeck — Sidebar customization API (v4.6.0).""" from __future__ import annotations import json import logging from fastapi import APIRouter, HTTPException, Request from app.auth.session import SessionManager from app.db import get_conn logger = logging.getLogger(__name__) router = APIRouter(tags=["sidebar"], prefix="/api/sidebar") def _get_user(request: Request) -> dict: user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) if not user: raise HTTPException(status_code=401, detail="Not authenticated") return user DEFAULT_CONFIG = { "workspace": {"visible": True, "order": 0, "show_count": None}, "gitea": {"visible": True, "order": 1, "show_count": None}, "meetings": {"visible": True, "order": 2, "show_count": 5}, "recents": {"visible": True, "order": 3, "show_count": 10}, "favorites": {"visible": True, "order": 4, "show_count": 10}, "agents": {"visible": True, "order": 5, "show_count": None}, "teamspaces": {"visible": True, "order": 6, "show_count": None}, "shared": {"visible": True, "order": 7, "show_count": 10}, "published": {"visible": True, "order": 8, "show_count": 10}, "private": {"visible": True, "order": 9, "show_count": None}, } @router.get("/config") def get_sidebar_config(request: Request): """Get the current user's sidebar customization config.""" user = _get_user(request) with get_conn() as conn: row = conn.execute( "SELECT sidebar_config FROM users WHERE id=?", (user["id"],) ).fetchone() if not row: return {"config": DEFAULT_CONFIG} raw = row["sidebar_config"] if not raw: return {"config": DEFAULT_CONFIG} try: stored = json.loads(raw) except (json.JSONDecodeError, TypeError): return {"config": DEFAULT_CONFIG} # Merge with defaults to ensure all keys exist merged = dict(DEFAULT_CONFIG) merged.update(stored) return {"config": merged} def get_sidebar_config_sync(user_id: int) -> dict: """Synchronous helper to get sidebar config (used during template rendering).""" with get_conn() as conn: row = conn.execute( "SELECT sidebar_config FROM users WHERE id=?", (user_id,) ).fetchone() if not row: return dict(DEFAULT_CONFIG) raw = row["sidebar_config"] if not raw: return dict(DEFAULT_CONFIG) try: stored = json.loads(raw) except (json.JSONDecodeError, TypeError): return dict(DEFAULT_CONFIG) merged = dict(DEFAULT_CONFIG) merged.update(stored) return merged @router.put("/config") async def save_sidebar_config(request: Request): """Save the current user's sidebar customization config.""" user = _get_user(request) try: body = await request.json() except Exception: raise HTTPException(status_code=400, detail="Invalid JSON body") from None config = body.get("config") if not config or not isinstance(config, dict): raise HTTPException(status_code=400, detail="config object is required") # Merge with defaults to ensure validity merged = dict(DEFAULT_CONFIG) for key, val in config.items(): if key in DEFAULT_CONFIG and isinstance(val, dict): merged[key] = { "visible": val.get("visible", DEFAULT_CONFIG[key]["visible"]), "order": val.get("order", DEFAULT_CONFIG[key]["order"]), "show_count": val.get("show_count", DEFAULT_CONFIG[key]["show_count"]), } elif key not in DEFAULT_CONFIG: # Allow new custom sections merged[key] = val with get_conn() as conn: conn.execute( "UPDATE users SET sidebar_config=? WHERE id=?", (json.dumps(merged), user["id"]), ) conn.commit() return {"status": "ok", "config": merged}