"""FastAPI application entrypoint. Thin by design: wire settings, routers, and error handling, and expose the OpenAPI contract. All domain logic lives in the service layer; the privacy engine is the single enforcement point for reads. """ import logging import sys from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from app.api.health import router as health_router from app.api.v1 import api_router from app.core.config import get_settings from app.services.exceptions import Conflict, Forbidden, NotFound def _configure_logging() -> None: """Emit the app's own ``provenance.*`` logs at INFO to stdout (uvicorn only configures its own loggers). The ConsoleMailer relies on this so self-hosters can read verification/reset links from the logs.""" app_logger = logging.getLogger("provenance") app_logger.setLevel(logging.INFO) if not app_logger.handlers: handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter("%(levelname)s [%(name)s] %(message)s")) app_logger.addHandler(handler) app_logger.propagate = False def _register_error_handlers(app: FastAPI) -> None: @app.exception_handler(NotFound) async def _not_found(request: Request, exc: NotFound) -> JSONResponse: return JSONResponse(status_code=404, content={"detail": str(exc) or "not found"}) @app.exception_handler(Forbidden) async def _forbidden(request: Request, exc: Forbidden) -> JSONResponse: return JSONResponse(status_code=403, content={"detail": str(exc) or "forbidden"}) @app.exception_handler(Conflict) async def _conflict(request: Request, exc: Conflict) -> JSONResponse: return JSONResponse(status_code=409, content={"detail": str(exc) or "conflict"}) def create_app() -> FastAPI: _configure_logging() settings = get_settings() app = FastAPI( title=settings.app_name, version=settings.version, description="Provenance API — family and land provenance.", ) app.include_router(health_router) app.include_router(api_router) _register_error_handlers(app) return app app = create_app()