# app.py from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import logging import time from api.health import router as health_router from api.extract import router as extract_router from api.enroll import router as enroll_router from api.recognize import router as recognize_router logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = FastAPI( title="SchoolMaster Face Recognition Service", description="AI face recognition for school attendance", version="1.0.0" ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.middleware("http") async def log_requests(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time logger.info(f"{request.method} {request.url.path} - {response.status_code} - {process_time:.3f}s") return response @app.get("/") async def root(): return { "service": "SchoolMaster Face Recognition", "status": "running", "version": "1.0.0", "endpoints": [ "GET /api/health", "POST /api/extract", "POST /api/enroll", "POST /api/recognize" ] } app.include_router(health_router, prefix="/api", tags=["Health"]) app.include_router(extract_router, prefix="/api", tags=["Extraction"]) app.include_router(enroll_router, prefix="/api", tags=["Enrollment"]) app.include_router(recognize_router, prefix="/api", tags=["Recognition"]) @app.exception_handler(HTTPException) async def http_exception_handler(request, exc): return JSONResponse( status_code=exc.status_code, content={"success": False, "message": exc.detail} ) # ============================================================ # Minimal Gradio status page, mounted onto the SAME app/port. # This is only here so the Space genuinely uses the Gradio SDK # (keeping it eligible for free hosting) - your PHP backend # talks to the /api/* routes above directly and never touches this UI. # # Wrapped defensively: if Gradio itself fails to import or mount for # any reason (version mismatches inside HF's build environment have # happened before), the actual API - the thing PHP depends on - must # still start and serve requests. A broken status page should never # be able to take down the real service. # ============================================================ try: import gradio as gr import requests as _requests def check_health(): try: r = _requests.get(f"http://127.0.0.1:{7860}/api/health", timeout=5) return r.json() except Exception as e: return {"error": str(e)} with gr.Blocks(title="Face Recognition API") as demo: gr.Markdown( "# Face Recognition API\n\n" "This is a status page only. Your school system's PHP backend talks " "directly to the `/api/*` endpoints below - nothing here.\n\n" "**Endpoints:** `/api/health` (GET), `/api/extract`, `/api/enroll`, " "`/api/recognize` (POST, require `X-API-Key` header + multipart photos)" ) btn = gr.Button("Check API Health", variant="primary") out = gr.JSON(label="Result") btn.click(fn=check_health, outputs=out) app = gr.mount_gradio_app(app, demo, path="/ui") logger.info("Gradio status page mounted at /ui") except Exception as e: logger.warning(f"Gradio status page failed to load, continuing without it: {e}") if __name__ == "__main__": import uvicorn from config import Config # Hugging Face Spaces health-checks port 7860 specifically - hardcoded # here (not from Config.PORT/env) so a stray PORT variable in the # Space's settings can never silently break this again. SPACES_PORT = 7860 uvicorn.run( app, host=Config.HOST, port=SPACES_PORT, )