| 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 |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger("face-service-main") |
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| |
| 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.") |
|
|
| |
| db.connect_db() |
| |
| settings.update_from_server() |
| |
| settings.start_config_sync() |
| |
| yield |
| |
| 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 |
| ) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
|
|
| @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" |
| |
| |
| 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" |
| } |
| ) |
|
|
| |
| @app.middleware("http") |
| async def validate_api_key_middleware(request: Request, call_next): |
| |
| 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) |
|
|
| |
| 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__": |
| |
| port = int(os.environ.get("PORT", 8000)) |
| is_dev = os.environ.get("NODE_ENV") != "production" |
| |
| |
| |
| 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) |
|
|