from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import JSONResponse from server.auth import require_agent from server.config_loader import load_meds_config, slot_to_dict, today_str from server.db import get_db, new_id, utc_now_iso from server.migrations import current_version from server.push import send_push from server.schemas import AgentLogEntry, AgentNotifyRequest from server.settings import settings from server.slots import build_today from server.stats import build_history, compute_stats router = APIRouter(prefix="/agent", tags=["agent"]) logger = logging.getLogger(__name__) NO_STORE = {"Cache-Control": "no-store"} @router.get("/health") async def agent_health(_: dict = Depends(require_agent)) -> JSONResponse: """Liveness-Check der App.""" db = await get_db() try: db_version = await current_version(db) finally: await db.close() return JSONResponse( {"status": "ok", "version": settings.app_version, "db_version": db_version}, headers=NO_STORE, ) @router.get("/status") async def agent_status(_: dict = Depends(require_agent)) -> JSONResponse: """Heutige Slot-Daten plus aktuelle Stats.""" config = load_meds_config() db = await get_db() try: today_data = await build_today(db, config) stats = await compute_stats(db) finally: await db.close() return JSONResponse({"today": today_data, "stats": stats}, headers=NO_STORE) @router.post("/log") async def agent_log( payload: AgentLogEntry, _: dict = Depends(require_agent) ) -> JSONResponse: """Eine Einnahme für einen Slot loggen.""" config = load_meds_config() if payload.slot_id not in {s.id for s in config.slots}: raise HTTPException(status_code=400, detail="Unknown slot") day = payload.day.isoformat() if payload.day else today_str(config.timezone) logged_at = payload.logged_at or utc_now_iso() status = payload.status.value db = await get_db() try: await db.execute( "DELETE FROM intake_log WHERE slot_id = ? AND day = ?", (payload.slot_id, day), ) await db.execute( "INSERT INTO intake_log (id, slot_id, day, status, logged_at, source) VALUES (?, ?, ?, ?, ?, ?)", (new_id(), payload.slot_id, day, status, logged_at, payload.source), ) if status == "taken": await db.execute( "DELETE FROM snooze WHERE slot_id = ? AND day = ?", (payload.slot_id, day), ) await db.commit() finally: await db.close() logger.info("Agent logged slot=%s status=%s day=%s", payload.slot_id, status, day) return JSONResponse( {"ok": True, "slot_id": payload.slot_id, "day": day, "status": status}, headers=NO_STORE, ) @router.get("/history") async def agent_history( days: int = 90, _: dict = Depends(require_agent) ) -> JSONResponse: """Einnahme-Historie der letzten N Tage (max. 365).""" db = await get_db() try: data = await build_history(db, days=min(days, 365)) finally: await db.close() return JSONResponse(data, headers=NO_STORE) @router.get("/config") async def agent_config(_: dict = Depends(require_agent)) -> JSONResponse: """Aktive Medikamenten-Konfiguration (Zeitzone, Slots).""" config = load_meds_config() return JSONResponse( { "timezone": str(config.timezone), "slots": [slot_to_dict(s) for s in config.slots], "version": settings.app_version, }, headers=NO_STORE, ) @router.post("/notify") async def agent_notify( payload: AgentNotifyRequest, _: dict = Depends(require_agent) ) -> JSONResponse: """Push-Benachrichtigung an alle aktiven Subscriptions senden.""" db = await get_db() try: sent = await send_push( db, { "title": payload.title, "body": payload.body, "slot_id": payload.slot_id or "agent", "tag": payload.tag or "agent", }, ) finally: await db.close() return JSONResponse({"ok": True, "sent": sent}, headers=NO_STORE)