""" Database Configuration for DetectifAI Backend This module handles connections to MongoDB Atlas and S3-compatible object storage (Backblaze B2) for the DetectifAI system. It provides centralized configuration and connection management. """ import os from pymongo import MongoClient from minio import Minio from minio.error import S3Error from dotenv import load_dotenv import logging from datetime import timedelta # Load environment variables load_dotenv() logger = logging.getLogger(__name__) class DatabaseConfig: """Configuration class for database connections""" def __init__(self): # MongoDB Atlas connection (same as frontend) self.mongo_uri = os.getenv( 'MONGO_URI', 'mongodb+srv://detectifai_user:DetectifAI123@cluster0.6f9uj.mongodb.net/detectifai?retryWrites=true&w=majority&appName=Cluster0' ) self.mongo_db_name = 'detectifai' # S3-compatible object storage (Backblaze B2) self.minio_endpoint = os.getenv('MINIO_ENDPOINT', 's3.eu-central-003.backblazeb2.com') self.minio_access_key = os.getenv('MINIO_ACCESS_KEY', '00367479ffb7e4e0000000001') self.minio_secret_key = os.getenv('MINIO_SECRET_KEY', 'K003opTvf92ijRj5dM7H1dgrlwcGTdA') self.minio_video_bucket = os.getenv('MINIO_VIDEO_BUCKET', 'detectifai-videos') self.minio_keyframe_bucket = os.getenv('MINIO_KEYFRAME_BUCKET', 'detectifai-keyframes') self.minio_reports_bucket = os.getenv('MINIO_REPORTS_BUCKET', 'detectifai-reports') self.minio_secure = os.getenv('MINIO_SECURE', 'true').lower() == 'true' # Extract region from endpoint for S3 signing (e.g. 'eu-central-003') self.minio_region = os.getenv('MINIO_REGION', self._extract_region(self.minio_endpoint)) @staticmethod def _extract_region(endpoint: str) -> str: """Extract region from B2 S3 endpoint like s3.eu-central-003.backblazeb2.com""" parts = endpoint.split('.') if len(parts) >= 3 and parts[0] == 's3': return parts[1] # e.g. 'eu-central-003' return '' class DatabaseManager: """Central database manager for MongoDB and MinIO connections""" def __init__(self): self.config = DatabaseConfig() self._mongodb_client = None self._db = None self._minio_client = None @property def mongo_client(self): """Lazy loading MongoDB client""" if self._mongodb_client is None: try: self._mongodb_client = MongoClient(self.config.mongo_uri) # Test connection self._mongodb_client.admin.command('ping') logger.info("✅ MongoDB connection established successfully") except Exception as e: logger.error(f"❌ Failed to connect to MongoDB: {e}") raise return self._mongodb_client @property def db(self): """Get MongoDB database instance""" if self._db is None: self._db = self.mongo_client[self.config.mongo_db_name] return self._db @property def minio_client(self): """Lazy loading S3-compatible storage client — returns None when unavailable""" if self._minio_client is None: try: self._minio_client = Minio( self.config.minio_endpoint, access_key=self.config.minio_access_key, secret_key=self.config.minio_secret_key, secure=self.config.minio_secure, region=self.config.minio_region or None ) # Test connection and verify buckets exist self._ensure_bucket_exists() logger.info("✅ S3 storage connection established (Backblaze B2)") except Exception as e: logger.warning(f"⚠️ S3 storage unavailable (non-fatal): {e}") self._minio_client = None # keep it None so we can retry later return None return self._minio_client def _ensure_bucket_exists(self): """Verify that the required S3 buckets exist on Backblaze B2""" try: for bucket_name in [ self.config.minio_video_bucket, self.config.minio_keyframe_bucket, self.config.minio_reports_bucket, ]: if self._minio_client.bucket_exists(bucket_name): logger.info(f"✅ S3 bucket verified: {bucket_name}") else: logger.warning(f"⚠️ S3 bucket not found: {bucket_name} — create it in Backblaze B2 dashboard") except S3Error as e: logger.error(f"❌ Failed to verify S3 buckets: {e}") raise def test_connections(self): """Test both MongoDB and MinIO connections""" mongodb_success = False minio_success = False try: # Test MongoDB self.mongo_client.admin.command('ping') collections = self.db.list_collection_names() logger.info(f"✅ MongoDB test successful. Collections: {collections}") print(f"✅ MongoDB connected successfully. Collections: {collections}") mongodb_success = True except Exception as e: logger.error(f"❌ MongoDB connection failed: {e}") print(f"❌ MongoDB connection failed: {e}") try: # Test S3 storage (Backblaze B2) buckets = self.minio_client.list_buckets() bucket_names = [bucket.name for bucket in buckets] logger.info(f"✅ S3 storage test successful. Buckets: {bucket_names}") print(f"✅ S3 storage (Backblaze B2) connected successfully. Buckets: {bucket_names}") minio_success = True except Exception as e: logger.error(f"❌ S3 storage connection failed: {e}") print(f"❌ S3 storage connection failed: {e}") print("💡 Check MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY env vars.") return mongodb_success # At minimum, we need MongoDB working def close_connections(self): """Close database connections""" if self._mongodb_client: self._mongodb_client.close() logger.info("MongoDB connection closed") def get_presigned_url(minio_client, bucket_name: str, object_name: str, expires: timedelta = timedelta(hours=1)): """Generate presigned URL for S3 object access (works with Backblaze B2)""" try: return minio_client.presigned_get_object(bucket_name, object_name, expires=expires) except S3Error as e: logger.error(f"Failed to generate presigned URL for {object_name}: {e}") return None if __name__ == "__main__": # Test connections db_manager = DatabaseManager() if db_manager.test_connections(): print("✅ All database connections working!") else: print("❌ Database connection issues detected")