|
"""Supabase storage helper for uploading files. |
|
|
|
This module provides functions to upload files to Supabase storage |
|
using HTTP requests, avoiding the dependency issues with the supabase client library. |
|
""" |
|
import os |
|
import requests |
|
import logging |
|
from typing import Optional, Dict, Any |
|
from pathlib import Path |
|
|
|
|
|
from dotenv import load_dotenv |
|
load_dotenv() |
|
|
|
SUPABASE_URL = os.getenv("SUPABASE_URL") |
|
SUPABASE_KEY = os.getenv("SUPABASE_KEY") |
|
|
|
if not SUPABASE_URL or not SUPABASE_KEY: |
|
raise ImportError("Could not load Supabase credentials from environment variables") |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
STORAGE_BASE_URL = f"{SUPABASE_URL}/storage/v1" |
|
HEADERS = { |
|
"apikey": SUPABASE_KEY, |
|
"Authorization": f"Bearer {SUPABASE_KEY}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
def upload_file_to_bucket( |
|
file_path: str, |
|
bucket_name: str = "images", |
|
storage_path: Optional[str] = None, |
|
file_options: Optional[Dict[str, Any]] = None |
|
) -> Dict[str, Any]: |
|
""" |
|
Upload a file to Supabase storage bucket. |
|
|
|
Args: |
|
file_path: Local path to the file to upload |
|
bucket_name: Name of the storage bucket (default: "images") |
|
storage_path: Path in the bucket where to store the file (default: filename) |
|
file_options: Optional file options like cache-control, upsert, etc. |
|
|
|
Returns: |
|
Dictionary with upload result information |
|
""" |
|
try: |
|
|
|
if not os.path.exists(file_path): |
|
raise FileNotFoundError(f"File not found: {file_path}") |
|
|
|
|
|
file_path_obj = Path(file_path) |
|
file_name = file_path_obj.name |
|
|
|
|
|
if storage_path is None: |
|
storage_path = file_name |
|
|
|
|
|
upload_url = f"{STORAGE_BASE_URL}/object/{bucket_name}/{storage_path}" |
|
|
|
|
|
upload_headers = { |
|
"apikey": SUPABASE_KEY, |
|
"Authorization": f"Bearer {SUPABASE_KEY}" |
|
} |
|
|
|
|
|
if file_options: |
|
for key, value in file_options.items(): |
|
upload_headers[f"x-upsert"] = str(value).lower() if key == "upsert" else str(value) |
|
|
|
|
|
with open(file_path, "rb") as f: |
|
response = requests.post( |
|
upload_url, |
|
headers=upload_headers, |
|
data=f |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
logger.info(f"✅ File uploaded successfully: {file_name}") |
|
logger.info(f"📁 Storage path: {storage_path}") |
|
logger.info(f"🔗 Public URL: {result.get('publicUrl', 'N/A')}") |
|
return { |
|
"success": True, |
|
"file_name": file_name, |
|
"storage_path": storage_path, |
|
"public_url": result.get('publicUrl'), |
|
"response": result |
|
} |
|
else: |
|
logger.error(f"❌ Upload failed with status {response.status_code}") |
|
logger.error(f"Response: {response.text}") |
|
return { |
|
"success": False, |
|
"error": f"Upload failed: {response.status_code}", |
|
"response": response.text |
|
} |
|
|
|
except Exception as e: |
|
logger.error(f"❌ Error uploading file: {e}") |
|
return { |
|
"success": False, |
|
"error": str(e) |
|
} |
|
|
|
def get_file_url(bucket_name: str, file_path: str) -> str: |
|
""" |
|
Get the public URL for a file in Supabase storage. |
|
|
|
Args: |
|
bucket_name: Name of the storage bucket |
|
file_path: Path to the file in the bucket |
|
|
|
Returns: |
|
Public URL for the file |
|
""" |
|
return f"{SUPABASE_URL}/storage/v1/object/public/{bucket_name}/{file_path}" |
|
|
|
def list_bucket_files(bucket_name: str = "images") -> Dict[str, Any]: |
|
""" |
|
List all files in a storage bucket. |
|
|
|
Args: |
|
bucket_name: Name of the storage bucket |
|
|
|
Returns: |
|
Dictionary with list of files |
|
""" |
|
try: |
|
list_url = f"{STORAGE_BASE_URL}/object/list/{bucket_name}" |
|
response = requests.get(list_url, headers=HEADERS) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
logger.info(f"✅ Listed {len(result)} files in bucket '{bucket_name}'") |
|
return { |
|
"success": True, |
|
"files": result |
|
} |
|
else: |
|
logger.error(f"❌ Failed to list files: {response.status_code}") |
|
return { |
|
"success": False, |
|
"error": f"Failed to list files: {response.status_code}" |
|
} |
|
except Exception as e: |
|
logger.error(f"❌ Error listing files: {e}") |
|
return { |
|
"success": False, |
|
"error": str(e) |
|
} |
|
|
|
def test_upload_image_png(): |
|
"""Test function to upload Image.png to the images bucket.""" |
|
try: |
|
logger.info("🚀 Testing file upload to Supabase storage...") |
|
|
|
|
|
logger.info("📋 Testing bucket connectivity...") |
|
list_result = list_bucket_files("images") |
|
if list_result["success"]: |
|
logger.info(f"✅ Bucket connectivity successful! Found {len(list_result['files'])} files") |
|
else: |
|
logger.warning(f"⚠️ Bucket connectivity issue: {list_result['error']}") |
|
|
|
|
|
logger.info("📤 Testing file upload...") |
|
result = upload_file_to_bucket( |
|
file_path="Image.png", |
|
bucket_name="images", |
|
storage_path="test/Image.png", |
|
file_options={"cache-control": "3600", "upsert": "false"} |
|
) |
|
|
|
if result["success"]: |
|
logger.info("✅ Upload test successful!") |
|
logger.info(f"📁 File uploaded to: {result['storage_path']}") |
|
logger.info(f"🔗 Public URL: {result['public_url']}") |
|
return True |
|
else: |
|
logger.error(f"❌ Upload test failed: {result['error']}") |
|
|
|
|
|
if "row-level security policy" in result.get('response', ''): |
|
logger.error("🔧 This is a storage permissions issue.") |
|
logger.error("📋 To fix this, you need to configure your Supabase storage bucket:") |
|
logger.error(""" |
|
1. Go to your Supabase dashboard |
|
2. Navigate to Storage > Policies |
|
3. For the 'images' bucket, add a policy like: |
|
|
|
CREATE POLICY "Allow public uploads" ON storage.objects |
|
FOR INSERT WITH CHECK (bucket_id = 'images'); |
|
|
|
CREATE POLICY "Allow public reads" ON storage.objects |
|
FOR SELECT USING (bucket_id = 'images'); |
|
|
|
Or make the bucket public in Storage > Settings |
|
""") |
|
|
|
return False |
|
|
|
except Exception as e: |
|
logger.error(f"❌ Upload test error: {e}") |
|
return False |