"""ecomm backend — FastAPI app factory + the REST BFF. SLICE-1 mounts only /healthz; the auth and storefront endpoints (§6.4) grow here in later slices. create_app() opens the connection pool and self-migrates the database at startup (INV-1, INV-7): the app boots against empty persistence and applies its own schema — there is no seed step. """ from __future__ import annotations from contextlib import asynccontextmanager import psycopg from fastapi import Depends, FastAPI, Response from app.platform import config, db from app.platform.deps import get_conn def create_app(database_url: str | None = None) -> FastAPI: dsn = database_url or config.database_url() @asynccontextmanager async def lifespan(app: FastAPI): app.state.pool = db.open_pool(dsn) with app.state.pool.connection() as conn: db.migrate(conn) # self-migrate at startup (INV-1, INV-7) try: yield finally: app.state.pool.close() app = FastAPI(title="ecomm", version="0.1", lifespan=lifespan) @app.get("/healthz") def healthz(response: Response, conn: psycopg.Connection = Depends(get_conn)): """Liveness + readiness: process up, DB reachable, migrations current (§6.4).""" try: conn.execute("SELECT 1") pending = db.pending_migrations(conn) except Exception: response.status_code = 503 return {"status": "unavailable", "reason": "database_unreachable"} if pending: response.status_code = 503 return {"status": "unavailable", "reason": "migrations_pending"} return {"status": "ok"} return app app = create_app()