"""FastAPI application - LeoCRM backend.""" from __future__ import annotations import time import traceback from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from app.config import get_settings from app.core.db import close_engine, get_engine from app.core.middleware import CSRFMiddleware from app.core.monitoring import record_error, record_request from app.core.service_container import get_container from app.plugins.registry import get_registry from app.routes import ( ai_copilot, auth, companies, contacts, health, import_export, metrics, notifications, plugins, roles, tenants, users, workflows, ) class RequestLoggingMiddleware(BaseHTTPMiddleware): """Structured logging + Prometheus metrics for every HTTP request.""" async def dispatch(self, request: Request, call_next): start_time = time.perf_counter() method = request.method path = request.url.path # Extract tenant_id from session cookie if available (best-effort) tenant_id = None try: response = await call_next(request) except Exception as exc: duration_ms = (time.perf_counter() - start_time) * 1000 tb_str = traceback.format_exc() record_error( event="unhandled_exception", method=method, path=path, status_code=500, error=str(exc), traceback_str=tb_str, tenant_id=tenant_id, ) raise duration_ms = (time.perf_counter() - start_time) * 1000 status_code = response.status_code # Try to get tenant_id from response headers or request state # (set by auth middleware/dependency — best-effort, never log credentials) record_request( method=method, path=path, status_code=status_code, duration_ms=duration_ms, tenant_id=tenant_id, ) return response @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan: startup and shutdown.""" # Initialize service container container = get_container() await container.initialize() # Initialize plugin registry and discover built-in plugins registry = get_registry() registry.initialize(get_engine(), app) registry.discover_builtins() yield await close_engine() def create_app() -> FastAPI: """Create and configure the FastAPI application.""" settings = get_settings() app = FastAPI(title="LeoCRM", version="1.0.0", lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origin_list, allow_credentials=True, allow_methods=["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"], allow_headers=["Authorization", "Content-Type", "X-CSRF-Token", "X-Tenant-ID"], max_age=3600, ) app.add_middleware(CSRFMiddleware) app.add_middleware(RequestLoggingMiddleware) app.include_router(health.router) app.include_router(metrics.router) app.include_router(auth.router) app.include_router(users.router) app.include_router(roles.router) app.include_router(tenants.router) app.include_router(notifications.router) app.include_router(companies.router) app.include_router(contacts.router) app.include_router(import_export.router) app.include_router(plugins.router) app.include_router(ai_copilot.router) app.include_router(workflows.router) return app app = create_app()