"""Supabase Storage helpers — so avatars, products, clips and final videos all live in Supabase, not on local disk (SPEC_NEW §3). Uses the service-role key over the Storage REST API. Falls back to local disk at the call sites if not configured. """ from __future__ import annotations import httpx from .config import get_settings def enabled() -> bool: s = get_settings() return bool(s.supabase_url and s.supabase_service_role_key) def is_remote(path: str | None) -> bool: return bool(path) and str(path).startswith(("http://", "https://")) def public_url(bucket: str, path: str) -> str: base = get_settings().supabase_url.rstrip("/") return f"{base}/storage/v1/object/public/{bucket}/{path}" def upload_bytes(bucket: str, path: str, data: bytes, content_type: str) -> str: """Upload to a public bucket; return the public URL. Raises on failure.""" s = get_settings() base = s.supabase_url.rstrip("/") r = httpx.post( f"{base}/storage/v1/object/{bucket}/{path}", content=data, headers={"Authorization": f"Bearer {s.supabase_service_role_key}", "Content-Type": content_type, "x-upsert": "true"}, timeout=180, ) if r.status_code >= 400: raise RuntimeError(f"Supabase Storage {r.status_code}: {r.text[:200]}") return public_url(bucket, path)