import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.config import settings from app.core.exceptions import AppException from app.core.logging import setup_logging from app.core.middleware import RequestIDMiddleware, get_request_id logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): setup_logging() logger.info("Starting AI Assistant API") # Note: Alembic migrations run via Dockerfile CMD before uvicorn starts from app.services.scheduler_service import start_scheduler, shutdown_scheduler start_scheduler() yield shutdown_scheduler() logger.info("Shutting down AI Assistant API") def create_app() -> FastAPI: app = FastAPI( title="AI Assistant API", description="Personal AI health assistant with document management, chat, and notifications.", version="0.1.0", lifespan=lifespan, docs_url="/api/docs" if settings.DOCS_ENABLED else None, redoc_url="/api/redoc" if settings.DOCS_ENABLED else None, openapi_url="/api/openapi.json" if settings.DOCS_ENABLED else None, openapi_tags=[ {"name": "auth", "description": "Authentication and registration"}, {"name": "chats", "description": "AI chat conversations"}, {"name": "documents", "description": "Health document management"}, {"name": "memory", "description": "Health memory entries"}, {"name": "skills", "description": "AI specialist skills"}, {"name": "notifications", "description": "User notifications"}, {"name": "pdf", "description": "PDF report generation"}, {"name": "admin", "description": "Admin management"}, {"name": "users", "description": "User profile and context"}, {"name": "websocket", "description": "WebSocket endpoints"}, ], ) # Middleware (order matters: outermost first) app.add_middleware(RequestIDMiddleware) app.add_middleware( CORSMiddleware, allow_origins=settings.BACKEND_CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Exception handlers @app.exception_handler(AppException) async def app_exception_handler(request: Request, exc: AppException): return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.code, "message": exc.detail, "request_id": get_request_id(), } }, ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={ "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": exc.errors(), "request_id": get_request_id(), } }, ) @app.exception_handler(Exception) async def generic_exception_handler(request: Request, exc: Exception): logger.exception("Unhandled exception", extra={"request_id": get_request_id()}) return JSONResponse( status_code=500, content={ "error": { "code": "INTERNAL_ERROR", "message": "An internal error occurred", "request_id": get_request_id(), } }, ) from app.api.v1.router import api_v1_router app.include_router(api_v1_router) return app app = create_app()