from contextlib import asynccontextmanager import asyncio from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from sqlalchemy import text from database import engine, Base from config import settings from routers import pantry, meals, recipes, menus, grocery @asynccontextmanager async def lifespan(app: FastAPI): # Startup Base.metadata.create_all(bind=engine) with engine.connect() as conn: result = conn.execute(text("PRAGMA table_info(menu_plans)")) if 'notes' not in [row[1] for row in result]: conn.execute(text("ALTER TABLE menu_plans ADD COLUMN notes TEXT")) conn.commit() result = conn.execute(text("PRAGMA table_info(recipes)")) if 'description' not in [row[1] for row in result]: conn.execute(text("ALTER TABLE recipes ADD COLUMN description TEXT")) conn.commit() yield # Shutdown pass app = FastAPI(title="Commis", lifespan=lifespan) # Include routers first so API routes take priority over the static catch-all app.include_router(pantry.router) app.include_router(meals.router) app.include_router(recipes.router) app.include_router(menus.router) app.include_router(grocery.router) app.include_router(grocery.ai_router) @app.get("/api/health") async def health(): try: import ollama client = ollama.Client(host=settings.ollama_host) loop = asyncio.get_event_loop() models_resp = await loop.run_in_executor(None, client.list) model_names = [m["name"] for m in models_resp.get("models", [])] ollama_status = "connected" except Exception: model_names = [] ollama_status = "disconnected" return { "status": "ok", "ollama": ollama_status, "model": settings.model_name, "available_models": model_names, } # Static files mount last — acts as a catch-all, must come after all API routes app.mount("/", StaticFiles(directory="static", html=True), name="static")