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 ""