from __future__ import annotations from datetime import datetime, timezone from fastapi import APIRouter, Depends, Request from fastapi.responses import JSONResponse from app_dependencies import get_runtime from card_store import ( delete_card, load_card, load_cards, normalize_card_id, parse_iso_datetime, write_card, ) from route_helpers import read_json_request from session_store import normalize_session_chat_id from web_runtime import WebAppRuntime router = APIRouter() @router.get("/cards") async def get_cards(request: Request) -> JSONResponse: chat_id = normalize_session_chat_id(str(request.query_params.get("chat_id", "web") or "web")) if not chat_id: return JSONResponse({"error": "invalid chat id"}, status_code=400) return JSONResponse(load_cards(chat_id)) @router.delete("/cards/{card_id}") async def delete_card_route( card_id: str, runtime: WebAppRuntime = Depends(get_runtime), ) -> JSONResponse: if not normalize_card_id(card_id): return JSONResponse({"error": "invalid card id"}, status_code=400) card = load_card(card_id) delete_card(card_id) await runtime.publish_cards_changed(card.get("chat_id") if isinstance(card, dict) else None) return JSONResponse({"status": "ok"}) @router.post("/cards/{card_id}/snooze") async def snooze_card( card_id: str, request: Request, runtime: WebAppRuntime = Depends(get_runtime), ) -> JSONResponse: if not normalize_card_id(card_id): return JSONResponse({"error": "invalid card id"}, status_code=400) try: payload = await read_json_request(request) except ValueError as exc: return JSONResponse({"error": str(exc)}, status_code=400) until_raw = str(payload.get("until", "")).strip() until_dt = parse_iso_datetime(until_raw) if until_dt is None: return JSONResponse({"error": "until must be a valid ISO datetime"}, status_code=400) card = load_card(card_id) if card is None: return JSONResponse({"error": "card not found"}, status_code=404) card["snooze_until"] = until_dt.isoformat() card["updated_at"] = datetime.now(timezone.utc).isoformat() persisted = write_card(card) if persisted is None: return JSONResponse({"error": "failed to snooze card"}, status_code=500) await runtime.publish_cards_changed(persisted.get("chat_id")) return JSONResponse({"status": "ok", "card": persisted}) @router.post("/cards/{card_id}/state") async def update_card_state( card_id: str, request: Request, runtime: WebAppRuntime = Depends(get_runtime), ) -> JSONResponse: if not normalize_card_id(card_id): return JSONResponse({"error": "invalid card id"}, status_code=400) try: payload = await read_json_request(request) except ValueError as exc: return JSONResponse({"error": str(exc)}, status_code=400) template_state = payload.get("template_state") if not isinstance(template_state, dict): return JSONResponse({"error": "template_state must be an object"}, status_code=400) card = load_card(card_id) if card is None: return JSONResponse({"error": "card not found"}, status_code=404) if str(card.get("kind", "")) != "text": return JSONResponse({"error": "only text cards support template_state"}, status_code=400) card["template_state"] = template_state card["updated_at"] = datetime.now(timezone.utc).isoformat() persisted = write_card(card) if persisted is None: return JSONResponse({"error": "failed to update card state"}, status_code=500) await runtime.publish_cards_changed(persisted.get("chat_id")) return JSONResponse({"status": "ok", "card": persisted})