"""Upload AI-generated challenge files to Supabase Storage. The backend NEVER writes files to its own disk — every ``fileToGenerate`` payload is uploaded to the public ``challenge-files`` Supabase Storage bucket and the resulting URL is returned in the training payload (``logData.downloadable_url``) so the frontend can offer a direct download link. If the upload fails for any reason, the function still returns a best-effort relative path string and the rest of the challenge still loads — the file just becomes a no-op for that one challenge. """ import time import httpx from app.core.config import ( SUPABASE_URL, SUPABASE_ANON_KEY, CHALLENGE_FILES_BUCKET, ) def _public_url(storage_path: str) -> str: """Build the public URL for an object in the bucket.""" return f"{SUPABASE_URL}/storage/v1/object/public/{CHALLENGE_FILES_BUCKET}/{storage_path}" async def upload_challenge_file(file_data: dict, module: str, team_role: str) -> str: """Upload the ``fileToGenerate`` payload to Supabase Storage. Returns the **public URL** of the uploaded object, or ``""`` if nothing was saved. """ if not file_data or not file_data.get("fileName") or not file_data.get("content"): return "" file_name = str(file_data.get("fileName")).replace(" ", "_") content = file_data.get("content") # Sanitize the filename: strip path components + traversal sequences. safe_name = file_name.replace("\\", "/").split("/")[-1].replace("..", "_") if not safe_name: return "" # Object key: //_ so two solves # with the same filename never overwrite each other. storage_path = f"{team_role}/{module}/{int(time.time())}_{safe_name}" upload_url = f"{SUPABASE_URL}/storage/v1/object/{CHALLENGE_FILES_BUCKET}/{storage_path}" headers = { "apikey": SUPABASE_ANON_KEY, "Authorization": f"Bearer {SUPABASE_ANON_KEY}", "Content-Type": "application/octet-stream", } try: async with httpx.AsyncClient(timeout=15) as client: resp = await client.post(upload_url, content=content.encode("utf-8"), headers=headers) if resp.status_code in (200, 201): print(f" [+] تم رفع ملف التحدي إلى Storage: {storage_path}") return _public_url(storage_path) print(f" [-] فشل رفع الملف إلى Storage: {resp.status_code} {resp.text[:200]}") except Exception as e: print(f" [-] استثناء أثناء رفع الملف إلى Storage: {e}") return ""