feat(meals): enforce three lower-calorie dinners with media
CI / backend (pytest + alembic) (push) Canceled after 0s
CI / frontend (build) (push) Canceled after 0s

This commit is contained in:
2026-09-01 10:11:36 -07:00
parent 7838c49721
commit 2695ba0232
85 changed files with 8103 additions and 795 deletions
+27 -4
View File
@@ -1,3 +1,4 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
@@ -11,10 +12,34 @@ import logging
logging.basicConfig(level=settings.LOG_LEVEL)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Auto-migrate on boot. Idempotent: alembic upgrade head is a no-op
# when the schema is already at head. Catches the recurring failure
# where the DB is at revision N but the image expects N+1 (e.g. a
# new column like denial_expires_at), which would otherwise 500 the
# first request that touches the new column. Skip when SKIP_AUTO_MIGRATE
# is set (tests / read-only replicas).
import os
if not os.getenv("SKIP_AUTO_MIGRATE"):
try:
from alembic.config import Config
from alembic import command
cfg = Config("alembic.ini")
command.upgrade(cfg, "head")
logger.info("auto-migrate: alembic upgrade head OK")
except Exception as e:
# Don't crash boot — log loud so it's visible in `docker logs`.
logger.error(f"auto-migrate FAILED: {e!r}")
yield
app = FastAPI(
title="MealPlanner",
description="Self-hosted meal planning system",
version="0.1.0",
lifespan=lifespan,
)
# Serve generated recipe images from local filesystem.
@@ -77,10 +102,8 @@ app.include_router(shopping_list.router, prefix="/api/shopping-list", tags=["sho
app.include_router(pantry.router, prefix="/api/pantry", tags=["pantry"])
app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
app.include_router(orchestrate_api.router, prefix="/api/orchestrate", tags=["orchestrate"])
# Sprint 12: external recipe search (Spoonacular) + import.
# Mounted BEFORE recipes_api.public_router so /search and /import are
# not shadowed by the WIP's GET /{recipe_id} (which would 422 on a
# non-UUID "search" path segment). See sprint15-verification.md.
# Retired external search/import routes stay mounted before /{recipe_id}
# so legacy clients receive 410 rather than UUID validation errors.
app.include_router(recipe_search_api.router, prefix="/api/recipes", tags=["recipes"])
app.include_router(ingredients_api.public_router)
app.include_router(ingredients_api.admin_router)