Files
MobilityOps/backend/app/main.py
T

128 lines
3.6 KiB
Python

import uuid
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy import text
from app.api.routers import (
audit,
auth,
bookings,
customers,
dashboard,
data_quality,
demo,
integration_status,
integrations,
knowledge,
mcp_integrations,
search,
users,
vehicles,
workflows,
)
from app.api.routers.auth import bootstrap_initial_admin
from app.core.config import PRODUCT_NAME, get_settings
from app.core.db import SessionLocal
from app.core.errors import AppError, error_body
from app.services.dispatcher import start_background_dispatcher, stop_background_dispatcher
settings = get_settings()
@asynccontextmanager
async def lifespan(_app: FastAPI):
with SessionLocal() as db:
bootstrap_initial_admin(db)
start_background_dispatcher()
yield
stop_background_dispatcher()
app = FastAPI(title=f"{PRODUCT_NAME} API", version="0.1.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in settings.cors_allow_origins.split(",")],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(AppError)
def handle_app_error(_request: Request, exc: AppError) -> JSONResponse:
return JSONResponse(
status_code=exc.status_code,
content=error_body(exc.code, exc.message, exc.correlation_id, exc.details),
)
@app.exception_handler(HTTPException)
def handle_http_exception(_request: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse(
status_code=exc.status_code,
content=error_body(
code=str(exc.status_code),
message=str(exc.detail),
correlation_id=str(uuid.uuid4()),
details={},
),
)
@app.get("/health")
def health() -> dict[str, str]:
return {"status": "ok", "service": "mobilityops-api"}
@app.get("/health/live")
def liveness() -> dict[str, str]:
"""Process liveness only; external dependencies deliberately do not affect it."""
return {"status": "ok", "service": "mobilityops-api"}
@app.get("/health/ready")
def readiness() -> JSONResponse:
"""Traffic readiness: the API is useful only while its canonical database responds."""
try:
with SessionLocal() as db:
db.execute(text("SELECT 1"))
except Exception: # noqa: BLE001 -- readiness must convert infrastructure errors to 503
return JSONResponse(
status_code=503,
content={"status": "not_ready", "service": "mobilityops-api", "database": "down"},
)
return JSONResponse(
content={"status": "ready", "service": "mobilityops-api", "database": "up"}
)
@app.get("/api/v1/system/status")
def system_status() -> dict[str, object]:
return {
"service": "mobilityops-api",
"environment": settings.mobilityops_env,
"demo_mode": settings.mobilityops_demo_mode,
"knowledge_provider": settings.knowledge_provider,
}
app.include_router(demo.router)
app.include_router(auth.router)
app.include_router(dashboard.router)
app.include_router(vehicles.router)
app.include_router(bookings.router)
app.include_router(customers.router)
app.include_router(audit.router)
app.include_router(data_quality.router)
app.include_router(workflows.router)
app.include_router(integrations.router)
app.include_router(knowledge.router)
app.include_router(mcp_integrations.router)
app.include_router(search.router)
app.include_router(integration_status.router)
app.include_router(users.router)