from __future__ import annotations from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.api.routes import analysis, areas, assistant, datasets, demo, detection, exports, external, health, jobs, projects, qa, quality_checks, segmentation, temporal from app.core.config import get_settings from app.core.errors import AppError from app.core.logging import configure_logging def _to_error_payload( code: str, message: str, details: dict | list | None = None, request_id: str | None = None, ) -> dict: return { "error": code, "message": message, "details": details or {}, "request_id": request_id, } def create_app() -> FastAPI: settings = get_settings() configure_logging(settings.log_level) app = FastAPI( title="GeoIntel Kempen", version=settings.app_version, docs_url="/docs", redoc_url="/redoc", ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_methods=["*"], allow_headers=["*"], allow_credentials=True, ) app.include_router(health.router) app.include_router(analysis.router, prefix=settings.api_prefix) app.include_router(projects.router, prefix=settings.api_prefix) app.include_router(areas.router, prefix=settings.api_prefix) app.include_router(datasets.router, prefix=settings.api_prefix) app.include_router(jobs.router, prefix=settings.api_prefix) app.include_router(quality_checks.router, prefix=settings.api_prefix) app.include_router(exports.router, prefix=settings.api_prefix) app.include_router(external.router, prefix=settings.api_prefix) app.include_router(demo.router, prefix=settings.api_prefix) app.include_router(qa.router, prefix=settings.api_prefix) app.include_router(detection.router, prefix=settings.api_prefix) app.include_router(segmentation.router, prefix=settings.api_prefix) app.include_router(temporal.router, prefix=settings.api_prefix) app.include_router(assistant.router, prefix=settings.api_prefix) @app.exception_handler(AppError) async def app_error(request: Request, exc: AppError): # noqa: ARG001 return JSONResponse( status_code=exc.status_code, content=_to_error_payload( exc.code, exc.message, exc.details, request_id=request.headers.get("x-request-id"), ), ) @app.exception_handler(HTTPException) async def http_error(request: Request, exc: HTTPException): # noqa: ARG001 code = "HTTP_ERROR" message = str(exc.detail) details = {} if isinstance(exc.detail, dict): code = str(exc.detail.get("error") or exc.detail.get("code") or code) message = str(exc.detail.get("message") or message) raw_details = exc.detail.get("details") details = raw_details if isinstance(raw_details, (dict, list)) else {} return JSONResponse( status_code=exc.status_code, content=_to_error_payload( code, message, details, request_id=request.headers.get("x-request-id"), ), ) @app.exception_handler(RequestValidationError) async def validation_error(request: Request, exc: RequestValidationError): # noqa: ARG001 return JSONResponse( status_code=422, content=_to_error_payload( "VALIDATION_ERROR", "Validation failed", exc.errors(), request_id=request.headers.get("x-request-id"), ), ) @app.exception_handler(Exception) async def unexpected_error(request: Request, exc: Exception): # noqa: ARG001 return JSONResponse( status_code=500, content=_to_error_payload( "INTERNAL_ERROR", "Unexpected server error", {"type": exc.__class__.__name__}, request_id=request.headers.get("x-request-id"), ), ) return app app = create_app() def main() -> None: import uvicorn settings = get_settings() uvicorn.run( "app.main:app", host="0.0.0.0", port=8000, reload=settings.app_env == "development", )