Spaces:
Sleeping
Sleeping
File size: 15,668 Bytes
fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 c9a0fae fb5dc87 970e192 fb5dc87 c9a0fae fb5dc87 c9a0fae 970e192 fb5dc87 970e192 fb5dc87 | 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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | import os
import json
import streamlit as st
from typing import Optional
try:
from utils.secrets_utils import get_secret_section, has_secret
except ImportError:
from secrets_utils import get_secret_section, has_secret
# Optional Firebase Admin imports
try:
import firebase_admin
from firebase_admin import credentials, firestore, auth, storage
from google.cloud.firestore import FieldFilter
FIREBASE_AVAILABLE = True
except ImportError as e:
firebase_admin = None
credentials = None
firestore = None
auth = None
storage = None
FieldFilter = None
FIREBASE_AVAILABLE = False
print(f"Warning: Firebase Admin SDK not available: {e}")
# Do not load from .env in deployment; rely on Streamlit secrets only
# Avoid global monkey patching of Google Auth entirely
def initialize_firebase() -> bool:
"""Initialize Firebase Admin SDK
Returns:
bool: True if initialization successful, False otherwise
"""
if not FIREBASE_AVAILABLE:
error_msg = "Firebase Admin SDK is not installed. Please install it with: pip install firebase-admin"
st.error(error_msg)
print(error_msg)
return False
try:
# Prefer project_id from Streamlit secrets
project_id = None
firebase_secrets = get_secret_section('firebase')
if firebase_secrets is not None:
project_id = firebase_secrets.get('project_id')
# Check if Firebase is already initialized
if firebase_admin and firebase_admin._apps:
print("Firebase already initialized")
return True
print("Initializing Firebase...")
# Get Firebase credentials from environment or Streamlit secrets
firebase_creds = get_firebase_credentials()
if firebase_creds:
print(f"Found Firebase credentials for project: {firebase_creds.get('project_id', 'unknown')}")
if not credentials:
print("Firebase credentials module not available")
return False
# Initialize Firebase with credentials
cred = credentials.Certificate(firebase_creds)
# Get storage bucket from Streamlit secrets
storage_bucket = None
if firebase_secrets is not None:
storage_bucket = (
firebase_secrets.get('firebase_storage_bucket') or
firebase_secrets.get('storage_bucket')
)
if not storage_bucket and firebase_creds.get('project_id'):
storage_bucket = f"{firebase_creds.get('project_id')}.firebasestorage.app"
# Initialize Firebase with explicit configuration
if firebase_admin:
firebase_admin.initialize_app(cred, {
'storageBucket': storage_bucket,
'projectId': firebase_creds.get('project_id', project_id or 'agriai-cbd8b')
})
else:
print("Firebase Admin module not available")
return False
# Do not modify global google.auth defaults; rely on initialized app
print(f"Firebase initialized successfully with storage bucket: {storage_bucket}")
return True
else:
# Provide more specific guidance about missing credentials
if not has_secret('firebase') and not os.getenv("FIREBASE_SERVICE_ACCOUNT_KEY"):
error_msg = (
"Firebase credentials not found. Set FIREBASE_SERVICE_ACCOUNT_KEY "
"in Hugging Face Space secrets, or add a [firebase] section."
)
elif has_secret('firebase'):
firebase_cfg = get_secret_section('firebase') or {}
missing_fields = []
if not firebase_cfg.get('project_id'):
missing_fields.append('project_id')
if not firebase_cfg.get('firebase_private_key'):
missing_fields.append('firebase_private_key')
if not firebase_cfg.get('firebase_client_email'):
missing_fields.append('firebase_client_email')
if missing_fields:
error_msg = (
"Missing required Firebase credentials in Space secrets: "
f"{', '.join(missing_fields)}."
)
else:
error_msg = (
"Firebase credentials found but failed validation. "
"Check private_key, client_email, and project_id."
)
else:
error_msg = (
"Firebase credentials not configured. "
"Add FIREBASE_SERVICE_ACCOUNT_KEY to your Hugging Face Space secrets."
)
print(error_msg)
return False
except Exception as e:
error_type = type(e).__name__
error_details = str(e)
# Provide more specific error messages
if "Certificate" in error_type or "private_key" in error_details.lower():
error_msg = f"Firebase credential error: Invalid private key or certificate. Please check your Firebase service account credentials in Streamlit secrets."
elif "project_id" in error_details.lower() or "project" in error_details.lower():
error_msg = f"Firebase project error: {error_details}. Please verify your project_id in Streamlit secrets."
elif "already exists" in error_details.lower() or "already initialized" in error_details.lower():
# Firebase already initialized - this is actually OK
print(f"Firebase already initialized: {error_details}")
return True
else:
error_msg = f"Failed to initialize Firebase: {error_details}. Please check your configuration in Streamlit secrets."
print(f"Failed to initialize Firebase ({error_type}): {error_details}")
return False
def get_firebase_credentials() -> Optional[dict]:
"""Get Firebase credentials from environment variables or Streamlit secrets
Returns:
dict: Firebase service account credentials or None
"""
try:
# 1) Prefer JSON from environment variable (e.g. on Hugging Face Spaces)
env_creds = os.getenv("FIREBASE_SERVICE_ACCOUNT_KEY")
if env_creds:
try:
print("Loading Firebase credentials from FIREBASE_SERVICE_ACCOUNT_KEY env var")
return json.loads(env_creds)
except Exception as e:
print(f"Error parsing FIREBASE_SERVICE_ACCOUNT_KEY: {e}")
# 2) Fallback to Streamlit secrets
try:
firebase_secrets = get_secret_section('firebase')
if firebase_secrets is not None:
print("Loading Firebase credentials from Streamlit secrets")
# Construct credentials from individual fields in secrets
firebase_config = {
"type": firebase_secrets.get('firebase_type', 'service_account'),
"project_id": firebase_secrets.get('project_id'),
"private_key_id": firebase_secrets.get('firebase_private_key_id'),
"private_key": firebase_secrets.get('firebase_private_key', '').replace('\\n', '\n'),
"client_email": firebase_secrets.get('firebase_client_email'),
"client_id": firebase_secrets.get('firebase_client_id'),
"auth_uri": firebase_secrets.get('firebase_auth_uri', 'https://accounts.google.com/o/oauth2/auth'),
"token_uri": firebase_secrets.get('firebase_token_uri', 'https://oauth2.googleapis.com/token'),
"auth_provider_x509_cert_url": firebase_secrets.get('firebase_auth_provider_x509_cert_url', 'https://www.googleapis.com/oauth2/v1/certs'),
"client_x509_cert_url": firebase_secrets.get('firebase_client_x509_cert_url'),
"universe_domain": firebase_secrets.get('firebase_universe_domain', 'googleapis.com')
}
# Check if all required fields are present
required_fields = ['project_id', 'private_key', 'client_email']
missing_fields = [field for field in required_fields if not firebase_config.get(field)]
if not missing_fields:
print("Successfully loaded Firebase credentials from Streamlit secrets")
return firebase_config
else:
print(f"Missing required Firebase credentials in secrets: {missing_fields}")
elif has_secret('FIREBASE_SERVICE_ACCOUNT_KEY'):
print("Loading Firebase credentials from st.secrets['FIREBASE_SERVICE_ACCOUNT_KEY']")
return dict(st.secrets['FIREBASE_SERVICE_ACCOUNT_KEY'])
except Exception as e:
print(f"Error loading Firebase credentials from Streamlit secrets: {e}")
# If nothing found, return None to signal missing credentials
return None
except Exception as e:
print(f"Error loading Firebase credentials: {e}")
return None
def get_firestore_client():
"""Get Firestore client instance
Returns:
firestore.Client: Firestore client instance
"""
if not FIREBASE_AVAILABLE:
return None
try:
# Ensure Firebase is initialized before creating the client
if firebase_admin and not firebase_admin._apps:
initialize_firebase()
# Return Firestore client from the initialized Firebase app
return firestore.client() if firestore else None
except Exception as e:
# Firebase initialization handled elsewhere, silently return None
return None
def get_storage_bucket():
"""Get Firebase Storage bucket instance
Returns:
storage.Bucket: Storage bucket instance
"""
if not FIREBASE_AVAILABLE:
return None
try:
# Get credentials explicitly to avoid metadata service
firebase_creds = get_firebase_credentials()
if firebase_creds and credentials:
cred = credentials.Certificate(firebase_creds)
return storage.bucket(credentials=cred) if storage else None
else:
# Fallback to default bucket (should work if Firebase is initialized)
return storage.bucket() if storage else None
except Exception as e:
st.error(f"Failed to get Storage bucket: {str(e)}")
return None
def get_auth_client():
"""Get Firebase Auth client
Returns:
auth: Firebase Auth client
"""
if not FIREBASE_AVAILABLE:
return None
try:
return auth if auth else None
except Exception as e:
st.error(f"Failed to get Auth client: {str(e)}")
return None
# Collection names
COLLECTIONS = {
'users': 'users',
'analyses': 'analyses',
'analysis_results': 'analysis_results',
'analysis_prompts': 'analysis_prompts',
'feedback': 'feedback',
'admin_requests': 'admin_requests',
'admin_codes': 'admin_codes',
'reference_documents': 'reference_documents',
'ai_configuration': 'ai_configuration',
'reference_materials': 'reference_materials',
'output_formats': 'output_formats',
'tagging_config': 'tagging_config',
'prompt_templates': 'prompt_templates'
}
# Default MPOB standards - Accurate values for Malaysian Oil Palm cultivation (matching actual data format)
DEFAULT_MPOB_STANDARDS = {
'leaf_standards': {
'N (%)': {'min': 2.4, 'max': 2.8, 'unit': '%', 'optimal': 2.6},
'P (%)': {'min': 0.14, 'max': 0.20, 'unit': '%', 'optimal': 0.17},
'K (%)': {'min': 0.9, 'max': 1.3, 'unit': '%', 'optimal': 1.1},
'Mg (%)': {'min': 0.25, 'max': 0.45, 'unit': '%', 'optimal': 0.35},
'Ca (%)': {'min': 0.5, 'max': 0.9, 'unit': '%', 'optimal': 0.7},
'B (mg/kg)': {'min': 18, 'max': 28, 'unit': 'mg/kg', 'optimal': 23},
'Cu (mg/kg)': {'min': 8, 'max': 18, 'unit': 'mg/kg', 'optimal': 13},
'Zn (mg/kg)': {'min': 18, 'max': 35, 'unit': 'mg/kg', 'optimal': 26}
},
'soil_standards': {
'pH': {'min': 4.5, 'max': 6.0, 'unit': '', 'optimal': 5.25},
'N (%)': {'min': 0.15, 'max': 0.25, 'unit': '%', 'optimal': 0.20},
'Org. C (%)': {'min': 1.5, 'max': 2.5, 'unit': '%', 'optimal': 2.0},
'Total P (mg/kg)': {'min': 15, 'max': 25, 'unit': 'mg/kg', 'optimal': 20},
'Avail P (mg/kg)': {'min': 10, 'max': 20, 'unit': 'mg/kg', 'optimal': 15},
'Exch. K (meq%)': {'min': 0.20, 'max': 0.40, 'unit': 'meq%', 'optimal': 0.30},
'Exch. Ca (meq%)': {'min': 2.0, 'max': 4.0, 'unit': 'meq%', 'optimal': 3.0},
'Exch. Mg (meq%)': {'min': 0.6, 'max': 1.2, 'unit': 'meq%', 'optimal': 0.9},
'CEC (meq%)': {'min': 15, 'max': 25, 'unit': 'meq%', 'optimal': 20}
}
}
def initialize_admin_codes():
"""Initialize default admin codes in Firestore"""
import secrets
import string
from datetime import datetime, timedelta, timezone
try:
db = get_firestore_client()
if not db:
print("Failed to get Firestore client")
return None
admin_codes_ref = db.collection(COLLECTIONS['admin_codes'])
# Check if default admin code already exists
if FieldFilter:
existing_codes = admin_codes_ref.where(filter=FieldFilter('is_default', '==', True)).limit(1).get()
else:
# Fallback if FieldFilter is not available
existing_codes = admin_codes_ref.where('is_default', '==', True).limit(1).get()
if existing_codes:
# Return existing default code
for doc in existing_codes:
code_data = doc.to_dict()
expires_at = code_data.get('expires_at')
# Handle both timezone-aware and naive datetime objects
if expires_at:
if hasattr(expires_at, 'tzinfo') and expires_at.tzinfo is not None:
current_time = datetime.now(timezone.utc)
else:
current_time = datetime.now()
if expires_at > current_time:
print("Default admin code already exists")
return code_data['code']
# Generate new default admin code
alphabet = string.ascii_uppercase + string.digits
default_code = ''.join(secrets.choice(alphabet) for _ in range(8))
# Set expiration to 1 year from now (timezone-aware)
current_time = datetime.now(timezone.utc)
expires_at = current_time + timedelta(days=365)
# Store in Firestore
admin_code_data = {
'code': default_code,
'is_default': True,
'created_at': current_time,
'expires_at': expires_at,
'used': False,
'created_by': 'system',
'description': 'Default admin registration code'
}
admin_codes_ref.add(admin_code_data)
print("Default admin code created successfully")
return default_code
except Exception as e:
print(f"Error initializing admin codes: {str(e)}")
return None |