from io import BytesIO
from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi.responses import HTMLResponse, StreamingResponse
from app.config import settings
app = FastAPI(title="face-lock", version="0.3.0")
def require_auth(request: Request) -> None:
if not settings.auth_enabled:
return
header_name = settings.auth_header_name.lower()
provided = request.headers.get(header_name)
if not provided and request.headers.get("authorization", "").lower().startswith("bearer "):
provided = request.headers.get("authorization", "")[7:].strip()
if provided != settings.auth_token:
raise HTTPException(status_code=401, detail="unauthorized")
@app.get("/health")
def health():
return {
"ok": True,
"env": settings.env,
"test_ui_enabled": settings.test_ui_enabled,
"auth_enabled": settings.auth_enabled,
"auth_header": settings.auth_header_name if settings.auth_enabled else None,
}
@app.get("/", response_class=HTMLResponse)
def index():
if not settings.test_ui_enabled:
return HTMLResponse(
"
face-lock
Test UI is disabled.
Open API docs
"
)
return HTMLResponse(
"""
face-lock
face-lock
Square the subject, crop it, and keep the raw blobs out of sight.
Docs
Result
Crop
Annotated source
""".replace("__AUTH_HEADER_NAME__", settings.auth_header_name)
)
@app.post("/api/focus")
async def focus(
request: Request,
file: UploadFile = File(...),
buffer_ratio: float = Form(0.15),
detector: str = Form("subject"),
_auth: None = Depends(require_auth),
):
from app.vision import process_image
try:
payload = await file.read()
result = process_image(payload, file.filename or "upload", buffer_ratio=buffer_ratio, detector=detector)
return {
"filename": result["filename"],
"detector": result["detector"],
"method": result["method"],
"buffer_ratio": result["buffer_ratio"],
"detected_bbox": result["detected_bbox"],
"square_bbox": result["square_bbox"],
"source_size": result["source_size"],
"crop_data_url": result["crop_data_url"],
"annotated_data_url": result["annotated_data_url"],
}
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.post("/api/focus/image")
async def focus_image(
request: Request,
file: UploadFile = File(...),
buffer_ratio: float = Form(0.15),
detector: str = Form("subject"),
_auth: None = Depends(require_auth),
):
from app.vision import process_image
try:
payload = await file.read()
result = process_image(payload, file.filename or "upload", buffer_ratio=buffer_ratio, detector=detector)
return StreamingResponse(BytesIO(result["crop_bytes"]), media_type="image/jpeg")
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc