"""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 # Import Supabase credentials from environment variables 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__) # Supabase storage API configuration 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: # Check if file exists if not os.path.exists(file_path): raise FileNotFoundError(f"File not found: {file_path}") # Get file info file_path_obj = Path(file_path) file_name = file_path_obj.name # Use provided storage_path or default to filename if storage_path is None: storage_path = file_name # Prepare upload URL upload_url = f"{STORAGE_BASE_URL}/object/{bucket_name}/{storage_path}" # Prepare headers for file upload upload_headers = { "apikey": SUPABASE_KEY, "Authorization": f"Bearer {SUPABASE_KEY}" } # Add file options if provided if file_options: for key, value in file_options.items(): upload_headers[f"x-upsert"] = str(value).lower() if key == "upsert" else str(value) # Read and upload file 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...") # First, test if we can list files (this tests basic connectivity) 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']}") # Test upload 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']}") # Provide helpful error information 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