CyberArena / app /services /file_storage.py
Hussien Haider
H
80a4a65
Raw
History Blame Contribute Delete
2.61 kB
"""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 ""