from __future__ import annotations import asyncio import logging import pathlib from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from server.ai import generate_daily_motivation, generate_roast_of_the_day from server.api import agent, auth, config, content, health, intake, push, stats from server.config_loader import load_meds_config from server.db import ensure_schema, get_db from server.scheduler import start_scheduler from server.settings import settings from server.static_cache import CacheControlMiddleware NO_STORE = {"Cache-Control": "no-store"} def _configure_logging() -> None: fmt = logging.Formatter("%(levelname)s: %(message)s") server_log = logging.getLogger("server") if not server_log.handlers: handler = logging.StreamHandler() handler.setFormatter(fmt) server_log.addHandler(handler) server_log.setLevel(logging.INFO) server_log.propagate = False _configure_logging() logger = logging.getLogger(__name__) async def _ensure_today_ai_texts() -> None: config = load_meds_config() db = await get_db() try: await generate_daily_motivation(db, config.timezone) await generate_roast_of_the_day(db, config.timezone) except Exception: logger.exception("Startup AI text generation failed") finally: await db.close() @asynccontextmanager async def lifespan(app: FastAPI): db = await get_db() await ensure_schema(db) await db.close() start_scheduler() asyncio.create_task(_ensure_today_ai_texts()) yield app = FastAPI( title="TakeYourMeds", description="Persönliche Medikamenten-PWA mit Push-Erinnerungen, Einnahme-Logging und Agent-API.", version=settings.app_version, lifespan=lifespan, ) app.add_middleware(CacheControlMiddleware) # API routers app.include_router(auth.router) app.include_router(config.router) app.include_router(intake.router) app.include_router(push.router) app.include_router(content.router) app.include_router(stats.router) app.include_router(health.router) app.include_router(agent.router) # Backwards-compatible VAPID endpoint used by older clients @app.get("/api/vapid-public-key") async def vapid_public_key_legacy() -> JSONResponse: return JSONResponse({"key": settings.vapid_public_key}, headers=NO_STORE) # Static files — must be after API routes PUBLIC = pathlib.Path(settings.public_dir) if PUBLIC.exists(): app.mount("/", StaticFiles(directory=str(PUBLIC), html=True), name="static")