from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.exceptions import RequestValidationError from contextlib import asynccontextmanager from app.core.config import settings from app.core.database import db from app.api import endpoints import uvicorn import os import sys import logging # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("face-service-main") @asynccontextmanager async def lifespan(app: FastAPI): # --- BONUS FIX #1: Empty API Key Bypass --- is_prod = os.environ.get("NODE_ENV") == "production" api_key = settings.FACE_API_KEY if not api_key: if is_prod: logger.error("❌ FATAL: FACE_API_KEY is not set. Refusing to start in production mode.") sys.exit(1) else: logger.warning("⚠️ WARNING: FACE_API_KEY is missing. Service is UNSECURED. Recommended only for development.") # Startup db.connect_db() # Initial config fetch settings.update_from_server() # Start background sync settings.start_config_sync() yield # Shutdown db.close_db() app = FastAPI( title="Face Recognition Attendance Service", description="High-performance, CPU-optimized face recognition API for EduSync.", version="1.2.0", lifespan=lifespan ) # CORS Middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # --- FIX #10 (COMPLETE): Python Global Error Handlers --- @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): """Handles 422 Unprocessable Entity (Input Validation Error)""" return JSONResponse( status_code=400, content={ "success": False, "error": "Input validation failed", "code": "VALIDATION_ERROR", "details": exc.errors() } ) @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """Handles specific HTTP Exceptions""" return JSONResponse( status_code=exc.status_code, content={ "success": False, "error": exc.detail, "code": "HTTP_ERROR" } ) @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Catches ALL unhandled exceptions and standardizes the response""" is_dev = os.environ.get("NODE_ENV") != "production" # Log the full error internally logger.error(f"UNHANDLED EXCEPTION: {str(exc)}", exc_info=True) error_message = str(exc) if is_dev else "An internal server error occurred. Please contact administrator." return JSONResponse( status_code=500, content={ "success": False, "error": error_message, "code": "INTERNAL_ERROR" } ) # SECURITY MIDDLEWARE: API Key Validation @app.middleware("http") async def validate_api_key_middleware(request: Request, call_next): # Skip validation for health check and docs if request.url.path in ["/health", "/", "/docs", "/openapi.json"]: return await call_next(request) api_key = request.headers.get("X-API-Key") if not api_key: return JSONResponse( status_code=403, content={ "success": False, "error": "Forbidden", "code": "AUTH_REQUIRED", "message": "X-API-Key header is missing" } ) if api_key != settings.FACE_API_KEY: return JSONResponse( status_code=403, content={ "success": False, "error": "Forbidden", "code": "INVALID_KEY", "message": "Invalid API Key provided" } ) return await call_next(request) # Routes app.include_router(endpoints.router, prefix="/api/v1", tags=["Face Recognition"]) @app.get("/health") async def health_check(): return {"status": "ok", "success": True, "message": "Service is running optimally."} @app.get("/") async def root(): return {"success": True, "message": "EduSync Face Recognition Service Running", "status": "active"} if __name__ == "__main__": # Ensure port is an integer port = int(os.environ.get("PORT", 8000)) is_dev = os.environ.get("NODE_ENV") != "production" # In production, we should ideally use GUNICORN. # This direct run is for testing/development. logger.info(f"🚀 Starting Uvicorn (reload={is_dev}) on port {port}") uvicorn.run("app.main:app", host="0.0.0.0", port=port, reload=is_dev)