Olympus/backend/hermes/app/main.py

170 lines
5 KiB
Python

import logging
import time
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from starlette import status
from starlette.requests import Request
from app.api.auth import router as auth_router
from app.api.audit import router as audit_router
from app.api.backups import router as backups_router
from app.api.customers import router as customers_router
from app.api.dashboard import router as dashboard_router
from app.api.inventory import router as inventory_router
from app.api.knowledge import router as knowledge_router
from app.api.lexware import router as lexware_router
from app.api.permissions import router as permissions_router
from app.api.repairs import router as repairs_router
from app.api.repair_estimates import router as repair_estimates_router
from app.api.roles import router as roles_router
from app.api.system_settings import router as system_settings_router
from app.api.users import router as users_router
from app.db.database import SessionLocal
from app.db.health import check_database
from app.core.logging import configure_logging
from app.knowledge_seed import seed_knowledge_manufacturers
from app.rbac.seed import seed_rbac
from app.services.initial_admin_bootstrap import bootstrap_initial_admin
configure_logging()
app = FastAPI(
title="Hermes API",
version="0.9.1",
description="Backend von Olympus",
)
app.include_router(auth_router)
app.include_router(audit_router)
app.include_router(backups_router)
app.include_router(users_router)
app.include_router(roles_router)
app.include_router(permissions_router)
app.include_router(customers_router)
app.include_router(inventory_router)
app.include_router(knowledge_router)
app.include_router(repairs_router)
app.include_router(repair_estimates_router)
app.include_router(dashboard_router)
app.include_router(system_settings_router)
app.include_router(lexware_router)
logger = logging.getLogger(__name__)
@app.middleware("http")
async def request_logging_middleware(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration_ms = round((time.perf_counter() - start) * 1000, 2)
logger.info(
"request.completed",
extra={
"method": request.method,
"path": request.url.path,
"status_code": response.status_code,
"duration_ms": duration_ms,
},
)
return response
def error_code_for_status(status_code: int) -> str:
return {
400: "BAD_REQUEST",
401: "UNAUTHORIZED",
403: "FORBIDDEN",
404: "NOT_FOUND",
409: "CONFLICT",
422: "VALIDATION_ERROR",
}.get(status_code, "HTTP_ERROR")
@app.on_event("startup")
def startup_seed_rbac():
db = SessionLocal()
try:
seed_rbac(db)
seed_knowledge_manufacturers(db)
bootstrap_initial_admin(db)
finally:
db.close()
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.warning(
"http_exception",
extra={
"path": request.url.path,
"method": request.method,
"status_code": exc.status_code,
},
)
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"message": str(exc.detail),
"error_code": error_code_for_status(exc.status_code),
"details": [],
"detail": exc.detail,
},
headers=exc.headers,
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
logger.warning(
"validation_exception",
extra={"path": request.url.path, "method": request.method},
)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"success": False,
"message": "Validierung fehlgeschlagen",
"error_code": "VALIDATION_ERROR",
"details": jsonable_encoder(exc.errors()),
"detail": jsonable_encoder(exc.errors()),
},
)
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
logger.exception(
"unhandled_exception",
extra={"path": request.url.path, "method": request.method},
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"success": False,
"message": "Interner Serverfehler",
"error_code": "INTERNAL_SERVER_ERROR",
"details": [],
"detail": "Interner Serverfehler",
},
)
@app.get("/")
def root():
return {
"service": "Hermes",
"status": "running",
}
@app.get("/health")
def health():
return {
"status": "healthy",
"database": check_database(),
}