""" Main FastAPI application for Workforce Microservice. """ import os import time from contextlib import asynccontextmanager from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError from jose import JWTError from app.cache import connect_to_redis, close_redis_connection from app.core.config import settings from app.core.logging import get_logger, setup_logging from app.nosql import connect_to_mongo, close_mongo_connection from app.postgres import connect_to_postgres, close_postgres_connection from app.live_tracking.controllers.router import router as live_tracking_router from app.attendance.controllers.router import router as attendance_router # Logging setup log_level = settings.LOG_LEVEL.strip().upper() if log_level not in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"): log_level = "INFO" setup_logging(level=log_level) logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): logger.info("Starting Workforce Microservice", extra={"event": "service_starting"}) await connect_to_mongo() await connect_to_postgres() await connect_to_redis() logger.info("Workforce Microservice started", extra={"event": "service_ready"}) yield logger.info("Shutting down Workforce Microservice", extra={"event": "service_stopping"}) await close_mongo_connection() await close_postgres_connection() await close_redis_connection() logger.info("Workforce Microservice stopped", extra={"event": "service_stopped"}) app = FastAPI( title="Workforce Microservice", description="Workforce management — profiles, scheduling, and attendance.", version="1.0.0", docs_url="/docs", redoc_url="/redoc", root_path=os.getenv("ROOT_PATH", ""), lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["*"], ) @app.middleware("http") async def log_requests(request: Request, call_next): start = time.time() response = await call_next(request) logger.info( "HTTP request", extra={ "event": "http_request", "method": request.method, "path": request.url.path, "status_code": response.status_code, "duration_ms": round((time.time() - start) * 1000, 2), "client_ip": request.client.host if request.client else None, }, ) return response @app.get("/health", tags=["health"]) async def health_check(): return {"status": "healthy", "service": "workforce-ms", "version": app.version} # ── Routers ────────────────────────────────────────────────────────────────── app.include_router(live_tracking_router) app.include_router(attendance_router) # ── Exception handlers ─────────────────────────────────────────────────────── @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): errors = [ {"field": " -> ".join(str(loc) for loc in e["loc"]), "message": e["msg"], "type": e["type"]} for e in exc.errors() ] logger.warning("Validation error", extra={"event": "request_validation_failure", "errors": errors}) return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={"success": False, "error": "Validation Error", "errors": errors}, ) @app.exception_handler(JWTError) async def jwt_exception_handler(request: Request, exc: JWTError): return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content={"success": False, "error": "Unauthorized", "detail": str(exc)}, ) @app.exception_handler(Exception) async def generic_exception_handler(request: Request, exc: Exception): logger.error("Unhandled exception", extra={"path": request.url.path}, exc_info=exc) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"success": False, "error": "Internal Server Error"}, )