"""FlowDeck — Library API: recents, favorites, shared, published, private, workspace.""" from __future__ import annotations import logging from fastapi import APIRouter, Request, Query from fastapi.responses import JSONResponse from app.db import get_conn from app.auth.session import SessionManager logger = logging.getLogger(__name__) router = APIRouter(tags=["library"], prefix="/api/library") SOURCE_TYPES = {"all", "local", "gitea", "github"} 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 def _detect_source_type(workspace: str) -> str: """Detect source type from workspace field. - '' or plain name (no slash) → local - 'owner/repo' format → gitea - 'github:owner/repo' format → github """ ws = (workspace or "").strip() if not ws: return "local" if ws.startswith("github:"): return "github" if "/" in ws: return "gitea" return "local" def _source_badge(source_type: str) -> str: return {"local": "Local", "gitea": "Gitea", "github": "GitHub"}.get(source_type, "Local") def _build_item(db_row: dict) -> dict: """Convert a DB row into a library item dict.""" workspace = db_row.get("workspace", "") or "" source_type = _detect_source_type(workspace) # Extract workspace display name if source_type == "gitea": workspace_name = workspace # "owner/repo" elif source_type == "github": workspace_name = workspace.replace("github:", "", 1) else: workspace_name = workspace or "Private" # URL construction page_id = db_row["id"] if source_type == "gitea": parts = workspace.split("/", 1) owner = parts[0] if len(parts) > 0 else "" repo = parts[1] if len(parts) > 1 else "" url = f"/gitea-workspace?owner={owner}&repo={repo}" elif source_type == "github": gh_ws = workspace.replace("github:", "", 1) parts = gh_ws.split("/", 1) owner = parts[0] if len(parts) > 0 else "" repo = parts[1] if len(parts) > 1 else "" url = f"/gitea-workspace?owner={owner}&repo={repo}" else: url = f"/pages/{page_id}" return { "id": page_id, "title": db_row.get("title") or "Untitled", "icon": "📄", "source_type": source_type, "source_badge": _source_badge(source_type), "workspace_name": workspace_name, "updated_at": db_row.get("updated_at", ""), "url": url, } def _apply_source_filter(query: str, params: list, source_type: str) -> tuple[str, list]: """Add WHERE clause for source_type filter. Mutates params in place.""" if source_type == "local": query += " AND (workspace = '' OR workspace NOT LIKE '%/%' AND workspace NOT LIKE 'github:%')" elif source_type == "gitea": query += " AND workspace LIKE '%/%' AND workspace NOT LIKE 'github:%'" elif source_type == "github": query += " AND workspace LIKE 'github:%'" # 'all' → no filter return query, params # ── Tab endpoints ── @router.get("/recents") async def library_recents( request: Request, source_type: str = Query(default="all"), ): """Recent pages ordered by updated_at DESC.""" if source_type not in SOURCE_TYPES: source_type = "all" uid = _get_user_id(request) query = ( "SELECT id, title, workspace, updated_at FROM pages " "WHERE parent_section != 'Trash' AND deleted_at IS NULL" ) params: list = [] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY updated_at DESC LIMIT 50" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items} @router.get("/favorites") async def library_favorites( request: Request, source_type: str = Query(default="all"), ): """Favorite pages for the current user.""" if source_type not in SOURCE_TYPES: source_type = "all" uid = _get_user_id(request) query = ( "SELECT p.id, p.title, p.workspace, p.updated_at FROM favorites f " "JOIN pages p ON p.id = f.page_id " "WHERE f.user_id = ? AND p.parent_section != 'Trash' AND p.deleted_at IS NULL" ) params: list = [uid] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY f.position" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items} @router.get("/shared") async def library_shared( request: Request, source_type: str = Query(default="all"), ): """Pages shared with others (share_mode != 'private').""" if source_type not in SOURCE_TYPES: source_type = "all" query = ( "SELECT id, title, workspace, updated_at FROM pages " "WHERE share_mode != 'private' AND parent_section != 'Trash' AND deleted_at IS NULL" ) params: list = [] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY updated_at DESC LIMIT 50" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items} @router.get("/published") async def library_published( request: Request, source_type: str = Query(default="all"), ): """Published pages (published = 1).""" if source_type not in SOURCE_TYPES: source_type = "all" query = ( "SELECT id, title, workspace, updated_at FROM pages " "WHERE published = 1 AND parent_section != 'Trash' AND deleted_at IS NULL" ) params: list = [] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY updated_at DESC LIMIT 50" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items} @router.get("/private") async def library_private( request: Request, source_type: str = Query(default="all"), ): """Private pages (parent_section = 'Private').""" if source_type not in SOURCE_TYPES: source_type = "all" query = ( "SELECT id, title, workspace, updated_at FROM pages " "WHERE parent_section = 'Private' AND deleted_at IS NULL" ) params: list = [] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY updated_at DESC LIMIT 50" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items} @router.get("/workspace") async def library_workspace( request: Request, source_type: str = Query(default="all"), ): """Pages from workspaces (non-empty workspace field).""" if source_type not in SOURCE_TYPES: source_type = "all" query = ( "SELECT id, title, workspace, updated_at FROM pages " "WHERE workspace != '' AND parent_section != 'Trash' AND deleted_at IS NULL" ) params: list = [] query, params = _apply_source_filter(query, params, source_type) query += " ORDER BY updated_at DESC LIMIT 50" with get_conn() as conn: rows = conn.execute(query, params).fetchall() items = [_build_item(dict(r)) for r in rows] return {"items": items}