TreeTrack / supabase_client.py
RoyAalekh's picture
Security: remove sensitive key information from logs
fd93c1d
raw
history blame
5.46 kB
"""
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