mkkbms
refactor: remove workforce_profiles β€” use scm_employees as source of truth
450c304
"""
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"},
)