nanobot-voice-interface/routes/cards.py

109 lines
3.7 KiB
Python
Raw Permalink Normal View History

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})