from pathlib import Path import boto3 from supabase import Client class SupabaseStorage: """Storage adapter backed by Supabase Storage. Same interface as R2Storage.""" def __init__(self, *, client: Client, bucket: str, public_base_url: str): self.bucket = bucket self.public_base_url = public_base_url.rstrip("/") self._client = client def upload(self, data: bytes, key: str, *, content_type: str = "application/octet-stream") -> None: self._client.storage.from_(self.bucket).upload( path=key, file=data, file_options={"content-type": content_type, "upsert": "true"}, ) def upload_file(self, path: Path, key: str, *, content_type: str = "audio/mpeg") -> None: with open(path, "rb") as f: self._client.storage.from_(self.bucket).upload( path=key, file=f.read(), file_options={"content-type": content_type, "upsert": "true"}, ) def delete(self, key: str) -> None: self._client.storage.from_(self.bucket).remove([key]) def public_url(self, key: str) -> str: return f"{self.public_base_url}/{key}" class R2Storage: def __init__(self, *, account_id: str, access_key: str, secret_key: str, bucket: str, public_base_url: str): self.bucket = bucket self.public_base_url = public_base_url.rstrip("/") self._s3 = boto3.client( "s3", endpoint_url=f"https://{account_id}.r2.cloudflarestorage.com", aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name="auto", ) def upload(self, data: bytes, key: str, *, content_type: str = "application/octet-stream") -> None: self._s3.put_object(Bucket=self.bucket, Key=key, Body=data, ContentType=content_type) def upload_file(self, path: Path, key: str, *, content_type: str = "audio/mpeg") -> None: self._s3.upload_file( Filename=str(path), Bucket=self.bucket, Key=key, ExtraArgs={"ContentType": content_type}, ) def delete(self, key: str) -> None: self._s3.delete_object(Bucket=self.bucket, Key=key) def public_url(self, key: str) -> str: return f"{self.public_base_url}/{key}"