Spaces:
Sleeping
Sleeping
File size: 2,605 Bytes
80a4a65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | """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: <team>/<module>/<timestamp>_<name> 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 ""
|