(request: Request)
| 57 | modal.Secret.from_name("webhook-secret"), |
| 58 | ] |
| 59 | ) |
| 60 | @modal.fastapi_endpoint(method="POST") |
| 61 | async def process_video(request: Request): |
| 62 | auth_header = request.headers.get("Authorization", "") |
| 63 | expected_secret = os.environ.get("MODAL_WEBHOOK_SECRET", "") |
| 64 | # hmac.compare_digest avoids the byte-by-byte short-circuit that lets a |
| 65 | # network-adjacent attacker enumerate the secret via response timing. |
| 66 | if not expected_secret or not hmac.compare_digest( |
| 67 | auth_header, f"Bearer {expected_secret}" |
| 68 | ): |
| 69 | raise HTTPException(status_code=401, detail="Unauthorized") |
| 70 | |
| 71 | try: |
| 72 | payload = await request.json() |
| 73 | if not isinstance(payload, dict): |
| 74 | raise ValueError("Expected an object") |
| 75 | match_id = payload.get("match_id") |
| 76 | file_key = payload.get("file_key") |
| 77 | if not isinstance(match_id, str): |
| 78 | raise ValueError("Missing recording ID") |
| 79 | uuid.UUID(match_id) |
| 80 | if not isinstance(file_key, str) or not file_key.startswith(f"{match_id}/"): |
| 81 | raise ValueError("Storage key does not belong to the recording") |
| 82 | if any(part in ("", ".", "..") for part in file_key.split("/")): |
| 83 | raise ValueError("Invalid storage key") |
| 84 | except (ValueError, TypeError): |
| 85 | return JSONResponse({"error": "Invalid recording ID or storage key."}, status_code=400) |
| 86 | |
| 87 | supabase = None |
| 88 | |
| 89 | def _stage(stage: str, progress: float, *, failure: str | None = None) -> None: |
| 90 | """Write an early-stage breadcrumb to Supabase so the UI shows what's |
| 91 | happening during the 15-90s window between the trigger-process webhook |
| 92 | and run_pipeline's heartbeat. Without this the page sat on |
| 93 | 'Calibrating the court · STARTING' (derived stage from progress=0) |
| 94 | for the entire Modal cold-start + download + import phase. |
| 95 | Defensive on column-missing so an un-migrated environment still |
| 96 | runs to completion.""" |
| 97 | nonlocal supabase |
| 98 | payload: dict = {"progress": round(float(progress), 3), "processing_stage": stage} |
| 99 | if failure: |
| 100 | payload = {**payload, "status": "failed", "error": failure} |
| 101 | try: |
| 102 | if supabase is None: |
| 103 | from supabase import create_client |
| 104 | supabase = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_ROLE_KEY"]) |
| 105 | supabase.table("matches").update(payload).eq("id", match_id).execute() |
| 106 | print(f"[Stage] {stage} ({progress:.3f})", flush=True) |
| 107 | except Exception as e: |
| 108 | payload.pop("processing_stage", None) |
| 109 | try: |
| 110 | supabase.table("matches").update(payload).eq("id", match_id).execute() |
| 111 | print(f"[Stage] {stage} ({progress:.3f}) — stage col missing", flush=True) |
| 112 | except Exception as e2: |
| 113 | print(f"[Stage] {stage} write failed: {e2}", flush=True) |
| 114 | |
| 115 | try: |
| 116 | from supabase import create_client |
nothing calls this directly
no test coverage detected