"""Per-category completion tracker — powers certificate eligibility. Idempotent via ``UNIQUE(user_id, category, challenge_id)`` so duplicate ``/api/training/solved`` calls never double-count. """ from typing import Optional import httpx from app.core.config import SUPABASE_URL, SUPABASE_ANON_KEY from app.services.supabase_service import supabase_headers async def record_user_completion( user_id: Optional[str], team_role: str, category: str, module: str, challenge_id: str, xp_awarded: int, ): """Insert (or ignore) a row in ``user_completions``.""" if not user_id: return if not (SUPABASE_URL and SUPABASE_ANON_KEY): return headers = supabase_headers(content_type=True) headers["Prefer"] = "return=minimal,resolution=ignore-duplicates" payload = { "user_id": user_id, "category": category, "module": module, "challenge_id": challenge_id, "xp_awarded": int(xp_awarded or 0), } try: async with httpx.AsyncClient(timeout=10) as client: r = await client.post( f"{SUPABASE_URL}/rest/v1/user_completions", json=payload, headers=headers, ) if r.status_code not in (200, 201): print(f"[completions] insert failed {r.status_code}: {r.text[:200]}") except Exception as e: print(f"[completions] insert exception: {e}") async def count_completions(user_id: str, category: str) -> int: if not (SUPABASE_URL and SUPABASE_ANON_KEY): return 0 url = ( f"{SUPABASE_URL}/rest/v1/user_completions" f"?user_id=eq.{user_id}&category=eq.{category}&select=id" ) try: async with httpx.AsyncClient(timeout=10) as client: r = await client.get(url, headers=supabase_headers()) if r.status_code == 200: return len(r.json()) except Exception as e: print(f"[completions] count exception: {e}") return 0