receipter-pro / backend /storage.py
M8ion deploy
Deploy backend
45dc2a1
Raw History Blame Contribute Delete
2.15 kB
"""
Where the raw copy of an uploaded audience file is kept. An upload is never just parsed and
discarded, so the original file is stored as well.
With SUPABASE_URL and SUPABASE_SERVICE_KEY set the file goes to a private Supabase Storage
bucket (SUPABASE_BUCKET, default "uploads"). Without them it goes to a local folder, which is
right for development and wrong for a host whose disk is wiped on restart.
"""
import os
from pathlib import Path
import httpx
TIMEOUT_SECONDS = 30
CONTENT_TYPES = {
"csv": "text/csv",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}
class StorageError(Exception):
"""The file could not be stored. The upload is refused rather than kept only in memory."""
def _remote() -> tuple[str, str, str] | None:
url = os.getenv("SUPABASE_URL", "").strip().rstrip("/")
key = os.getenv("SUPABASE_SERVICE_KEY", "").strip()
if not url or not key:
return None
return url, key, os.getenv("SUPABASE_BUCKET", "uploads").strip() or "uploads"
def save_upload(org_id: str, database_id: str, ext: str, data: bytes) -> str:
"""Stores the original file as {org_id}/{database_id}.{ext}; returns where it went."""
name = f"{org_id}/{database_id}.{ext}"
remote = _remote()
if remote is None:
path = Path(os.getenv("UPLOADS_DIR", "uploads")) / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return str(path)
url, key, bucket = remote
try:
res = httpx.post(
f"{url}/storage/v1/object/{bucket}/{name}",
content=data,
headers={
"Authorization": f"Bearer {key}",
"Content-Type": CONTENT_TYPES.get(ext.lower(), "application/octet-stream"),
"x-upsert": "true",
},
timeout=TIMEOUT_SECONDS,
)
except httpx.HTTPError as err:
raise StorageError(f"Could not reach file storage: {err.__class__.__name__}") from err
if res.status_code >= 300:
raise StorageError(f"File storage refused the file ({res.status_code})")
return f"{bucket}/{name}"