File size: 4,264 Bytes
354fc59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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,
    )