"""FastAPI application factory.""" from __future__ import annotations from contextlib import asynccontextmanager import sentry_sdk from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from slowapi.util import get_remote_address from app.api.v1.router import api_router from app.core.config import get_settings from app.core.exceptions import AppException from app.core.logging import get_logger, setup_logging logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """Startup / shutdown events.""" setup_logging() settings = get_settings() # Sentry if settings.SENTRY_DSN: sentry_sdk.init( dsn=settings.SENTRY_DSN, traces_sample_rate=0.1, environment=settings.APP_ENV, ) logger.info( "application_started", app_name=settings.APP_NAME, env=settings.APP_ENV, ) yield logger.info("application_shutdown") def create_app() -> FastAPI: settings = get_settings() app = FastAPI( title=f"{settings.APP_NAME} API", description="Marketplace API for Deals24Togo — listings, agencies, categories, and more.", version="1.0.0", docs_url="/docs" if settings.DEBUG else None, redoc_url="/redoc" if settings.DEBUG else None, lifespan=lifespan, ) # ── CORS ── app.add_middleware( CORSMiddleware, allow_origins=settings.allowed_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── Rate limiter ── limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # ── Exception handlers ── @app.exception_handler(AppException) async def app_exception_handler(_request: Request, exc: AppException): return JSONResponse( status_code=exc.status_code, content={"detail": exc.detail}, ) @app.exception_handler(Exception) async def global_exception_handler(_request: Request, exc: Exception): logger.error("unhandled_exception", error=str(exc), exc_info=True) return JSONResponse( status_code=500, content={"detail": "Internal server error"}, ) # ── Routes ── app.include_router(api_router) @app.get("/health", tags=["Health"]) def health_check(): return {"status": "healthy", "app": settings.APP_NAME, "env": settings.APP_ENV} return app