Spaces:
Sleeping
Sleeping
| """FastAPI application entry point | |
| Main application setup with middleware, CORS, and route configuration. | |
| """ | |
| from contextlib import asynccontextmanager | |
| from typing import AsyncGenerator | |
| from fastapi import FastAPI, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.middleware.trustedhost import TrustedHostMiddleware | |
| # Temporarily disabled for debugging | |
| from secure import Secure | |
| # Secure middleware configuration | |
| # Temporarily disabled for debugging | |
| secure_headers = Secure() | |
| from fastapi.responses import JSONResponse | |
| # Temporarily disabled for debugging | |
| from slowapi import Limiter, _rate_limit_exceeded_handler | |
| from slowapi.util import get_remote_address | |
| from slowapi.errors import RateLimitExceeded | |
| from src.config.settings import settings | |
| from src.config.database import init_qdrant_collection, close_database_connections | |
| from src.utils.logger import setup_logging, get_logger | |
| from src.api.routes import health, auth, chat | |
| # Setup logging | |
| setup_logging( | |
| level=settings.log_level, | |
| use_json=settings.is_production | |
| ) | |
| logger = get_logger(__name__) | |
| # Rate limiter configuration (using the one from rate_limit middleware) | |
| # limiter = Limiter( | |
| # key_func=get_remote_address, | |
| # default_limits=[f"{settings.rate_limit_per_minute}/minute"] | |
| # ) | |
| async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: | |
| """Application lifespan manager | |
| Handles startup and shutdown events. | |
| """ | |
| # Startup | |
| logger.info("Starting up application...") | |
| logger.info(f"Environment: {settings.environment}") | |
| # Initialize Qdrant collection | |
| try: | |
| await init_qdrant_collection() | |
| except Exception as e: | |
| logger.error(f"Failed to initialize Qdrant collection: {e}") | |
| logger.info("Application startup complete") | |
| yield | |
| # Shutdown | |
| logger.info("Shutting down application...") | |
| await close_database_connections() | |
| logger.info("Application shutdown complete") | |
| # Create FastAPI application | |
| app = FastAPI( | |
| title="RAG Chatbot API", | |
| description="Retrieval-Augmented Generation chatbot for humanoid robotics textbook", | |
| version="1.0.0", | |
| docs_url="/api/docs" if not settings.is_production else None, | |
| redoc_url="/api/redoc" if not settings.is_production else None, | |
| lifespan=lifespan | |
| ) | |
| # Temporarily disabled for debugging | |
| from src.api.middleware.logging_middleware import LoggingMiddleware | |
| from src.api.middleware.rate_limit import limiter, rate_limit_exceeded_handler | |
| # Add rate limiter to app state | |
| # Temporarily disabled for debugging | |
| # app.state.limiter = limiter | |
| app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler) | |
| # Add logging middleware | |
| # Temporarily disabled due to middleware stack build error | |
| app.add_middleware(LoggingMiddleware) | |
| # Add secure headers middleware | |
| # Temporarily disabled for debugging | |
| async def secure_headers_middleware(request: Request, call_next): | |
| response = await call_next(request) | |
| try: | |
| # If the Secure instance exposes an integration helper named "framework" | |
| # that supports FastAPI/Starlette, use it. | |
| if getattr(secure_headers, "framework", None) is not None: | |
| fw = secure_headers.framework | |
| # defensive: some versions expose attributes differently | |
| if hasattr(fw, "fastapi"): | |
| fw.fastapi(response) | |
| elif hasattr(fw, "starlette"): | |
| fw.starlette(response) | |
| else: | |
| # fallback to a generic method if one exists | |
| if hasattr(secure_headers, "apply"): | |
| secure_headers.apply(response) | |
| elif hasattr(secure_headers, "add"): | |
| secure_headers.add(response) | |
| else: | |
| raise AttributeError("Secure instance has no recognized integration methods") | |
| else: | |
| # library not integrated or missing; apply safe default headers manually | |
| # These are conservative, common security headers. | |
| response.headers.setdefault("X-Content-Type-Options", "nosniff") | |
| response.headers.setdefault("X-Frame-Options", "DENY") | |
| response.headers.setdefault("Referrer-Policy", "no-referrer") | |
| response.headers.setdefault("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload") | |
| response.headers.setdefault("Permissions-Policy", "geolocation=()") | |
| response.headers.setdefault("X-XSS-Protection", "1; mode=block") | |
| except Exception: | |
| # log the failure but do not crash the request pipeline | |
| logger.exception("Failed to apply secure headers") | |
| return response | |
| # CORS middleware configuration | |
| # Temporarily disabled for debugging | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=settings.allowed_origins, | |
| allow_credentials=True, | |
| allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], | |
| allow_headers=["*"], | |
| expose_headers=["X-Request-ID"], | |
| ) | |
| # Exception handlers | |
| async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse: | |
| """Global exception handler for unhandled errors | |
| Args: | |
| request: FastAPI request | |
| exc: Exception that was raised | |
| Returns: | |
| JSONResponse with error details | |
| """ | |
| logger.error( | |
| f"Unhandled exception: {exc}", | |
| extra={ | |
| "path": request.url.path, | |
| "method": request.method, | |
| "client": request.client.host if request.client else "unknown" | |
| } | |
| ) | |
| return JSONResponse( | |
| status_code=500, | |
| content={ | |
| "error": "Internal server error", | |
| "message": "An unexpected error occurred" if settings.is_production else str(exc) | |
| } | |
| ) | |
| # Include routers | |
| app.include_router(health.router, prefix="/api") | |
| app.include_router(auth.router, prefix="/api") | |
| app.include_router(chat.router, prefix="/api") | |
| # Root endpoint | |
| async def root() -> dict[str, str]: | |
| """Root endpoint | |
| Returns: | |
| Welcome message | |
| """ | |
| return { | |
| "message": "RAG Chatbot API", | |
| "status": "running", | |
| "docs": "/api/docs" if not settings.is_production else "disabled" | |
| } | |