"""Desktop session — persistent state + terminal management.""" import logging from fastapi import APIRouter, Depends, HTTPException, Request, Query from pydantic import BaseModel from backend.auth import get_current_user, decode_token from backend.sessions import manager router = APIRouter(prefix="/api/session", tags=["session"]) log = logging.getLogger("atlus.session") # --------------------------------------------------------------------------- # Models # --------------------------------------------------------------------------- class DesktopState(BaseModel): open_apps: list[str] = [] active_app: str | None = None terminal_tabs: list[dict] = [] # [{terminal_id, title}] class TerminalCreate(BaseModel): cols: int = 120 rows: int = 30 # --------------------------------------------------------------------------- # Session endpoints # --------------------------------------------------------------------------- @router.get("") async def get_session(user: str = Depends(get_current_user)): """Get or create the user's desktop session.""" session = manager.get_or_create(user) return session.to_dict() @router.put("/state") async def save_state( state: DesktopState, request: Request, token: str = Query(default=None), user: str = Depends(get_current_user), ): """Save desktop state (open apps, active app, terminal tab info).""" manager.save_state(user, state.model_dump()) return {"ok": True} @router.post("/state") async def save_state_beacon( request: Request, token: str = Query(default=None), ): """Save desktop state via sendBeacon (POST with token in query param).""" if not token: raise HTTPException(401, "Missing token") try: payload = decode_token(token) except Exception: raise HTTPException(401, "Invalid token") user = payload["sub"] body = await request.json() state = DesktopState(**body) manager.save_state(user, state.model_dump()) return {"ok": True} # --------------------------------------------------------------------------- # Terminal endpoints # --------------------------------------------------------------------------- @router.get("/terminals") async def list_terminals(user: str = Depends(get_current_user)): """List all alive terminals for the user.""" return manager.list_terminals(user) @router.post("/terminals") async def create_terminal( req: TerminalCreate, user: str = Depends(get_current_user), ): """Create a new persistent terminal.""" try: terminal = manager.create_terminal(user, cols=req.cols, rows=req.rows) except Exception as e: log.exception("Failed to create terminal for %s", user) raise HTTPException(500, f"Failed to spawn terminal: {e}") return terminal.to_dict() @router.delete("/terminals/{terminal_id}") async def close_terminal( terminal_id: str, user: str = Depends(get_current_user), ): """Close and kill a terminal.""" if not manager.close_terminal(user, terminal_id): raise HTTPException(404, "Terminal not found") return {"ok": True}