Spaces:
Runtime error
Runtime error
| """ | |
| Supabase client configuration and database operations for TreeTrack | |
| """ | |
| import os | |
| import logging | |
| from typing import Dict, List, Optional, Any | |
| from supabase import create_client, Client | |
| from pathlib import Path | |
| logger = logging.getLogger(__name__) | |
| # Supabase configuration - all must be provided via environment variables | |
| SUPABASE_URL = os.getenv("SUPABASE_URL") | |
| SUPABASE_ANON_KEY = os.getenv("SUPABASE_ANON_KEY") | |
| SUPABASE_SERVICE_ROLE_KEY = os.getenv("SUPABASE_SERVICE_ROLE_KEY") | |
| # Validate required environment variables (defer validation to runtime) | |
| if not SUPABASE_URL: | |
| logger.warning("SUPABASE_URL not found - will cause connection failures") | |
| if not SUPABASE_ANON_KEY: | |
| logger.warning("SUPABASE_ANON_KEY not found - will cause connection failures") | |
| if not SUPABASE_SERVICE_ROLE_KEY: | |
| logger.warning("SUPABASE_SERVICE_ROLE_KEY not provided - admin functions will be limited") | |
| # Initialize Supabase client | |
| supabase: Optional[Client] = None | |
| def get_supabase_client() -> Client: | |
| """Get Supabase client instance with better error handling""" | |
| global supabase | |
| if supabase is None: | |
| if not SUPABASE_URL: | |
| raise ValueError("SUPABASE_URL environment variable is required") | |
| if not SUPABASE_ANON_KEY: | |
| raise ValueError("SUPABASE_ANON_KEY environment variable is required") | |
| try: | |
| supabase = create_client(SUPABASE_URL, SUPABASE_ANON_KEY) | |
| logger.info("Supabase client initialized successfully") | |
| except Exception as e: | |
| logger.error(f"Failed to initialize Supabase client: {e}") | |
| raise ValueError(f"Supabase client initialization failed: {e}") | |
| return supabase | |
| def get_service_client() -> Client: | |
| """Get Supabase client with service role (admin) permissions""" | |
| if not SUPABASE_SERVICE_ROLE_KEY: | |
| raise ValueError("SUPABASE_SERVICE_ROLE_KEY environment variable is required") | |
| return create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY) | |
| # Initialize database table (run once) | |
| def create_trees_table(): | |
| """Create the trees table in Supabase""" | |
| client = get_service_client() | |
| # SQL to create trees table | |
| sql = """ | |
| CREATE TABLE IF NOT EXISTS trees ( | |
| id BIGSERIAL PRIMARY KEY, | |
| latitude DECIMAL(10, 8) NOT NULL CHECK (latitude >= -90 AND latitude <= 90), | |
| longitude DECIMAL(11, 8) NOT NULL CHECK (longitude >= -180 AND longitude <= 180), | |
| location_name VARCHAR(200), | |
| local_name VARCHAR(200), | |
| scientific_name VARCHAR(200), | |
| common_name VARCHAR(200), | |
| tree_code VARCHAR(20), | |
| height DECIMAL(6, 2) CHECK (height > 0 AND height <= 1000), -- feet | |
| width DECIMAL(6, 2) CHECK (width > 0 AND width <= 200), -- feet (girth/DBH) | |
| utility JSONB, | |
| storytelling_text TEXT CHECK (LENGTH(storytelling_text) <= 5000), | |
| storytelling_audio VARCHAR(500), | |
| phenology_stages JSONB, | |
| photographs JSONB, | |
| notes TEXT CHECK (LENGTH(notes) <= 2000), | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW(), | |
| created_by VARCHAR(100) DEFAULT 'system' | |
| ); | |
| -- Create indexes for better performance | |
| CREATE INDEX IF NOT EXISTS idx_trees_location ON trees(latitude, longitude); | |
| CREATE INDEX IF NOT EXISTS idx_trees_scientific_name ON trees(scientific_name); | |
| CREATE INDEX IF NOT EXISTS idx_trees_local_name ON trees(local_name); | |
| CREATE INDEX IF NOT EXISTS idx_trees_location_name ON trees(location_name); | |
| CREATE INDEX IF NOT EXISTS idx_trees_tree_code ON trees(tree_code); | |
| CREATE INDEX IF NOT EXISTS idx_trees_created_at ON trees(created_at); | |
| -- Create updated_at trigger | |
| CREATE OR REPLACE FUNCTION update_updated_at_column() | |
| RETURNS TRIGGER AS $$ | |
| BEGIN | |
| NEW.updated_at = NOW(); | |
| RETURN NEW; | |
| END; | |
| $$ language 'plpgsql'; | |
| DROP TRIGGER IF EXISTS update_trees_updated_at ON trees; | |
| CREATE TRIGGER update_trees_updated_at | |
| BEFORE UPDATE ON trees | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); | |
| """ | |
| try: | |
| client.rpc('exec_sql', {'sql': sql}).execute() | |
| logger.info("Trees table created successfully") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Error creating trees table: {e}") | |
| return False | |
| # Storage buckets setup | |
| def create_storage_buckets(): | |
| """Create storage buckets for files""" | |
| client = get_service_client() | |
| buckets = [ | |
| {"name": "tree-images", "public": False}, | |
| {"name": "tree-audios", "public": False} | |
| ] | |
| for bucket in buckets: | |
| try: | |
| result = client.storage.create_bucket(bucket["name"], {"public": bucket["public"]}) | |
| logger.info(f"Created storage bucket: {bucket['name']}") | |
| except Exception as e: | |
| logger.info(f"Bucket {bucket['name']} might already exist: {e}") | |
| return True | |
| # Test connection | |
| def test_supabase_connection() -> bool: | |
| """Test Supabase connection""" | |
| try: | |
| logger.info("Testing Supabase connection...") | |
| client = get_supabase_client() | |
| result = client.table('trees').select("id").limit(1).execute() | |
| logger.info("Supabase connection successful") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Supabase connection failed: {type(e).__name__}: {e}") | |
| return False | |