Spaces:
Runtime error
Runtime error
File size: 5,456 Bytes
54404f6 ef3133a 54404f6 d06b99c 54404f6 d06b99c 54404f6 d06b99c 54404f6 7f8f9a6 54404f6 7f8f9a6 54404f6 7f8f9a6 54404f6 ef3133a 9e903d1 54404f6 fd93c1d 54404f6 fd93c1d 54404f6 2a70b2f 54404f6 2a70b2f 54404f6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """
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
|