# /opt/docker/dev/service_finder/backend/app/services/storage_service.py import uuid import socket from io import BytesIO from datetime import timedelta from minio import Minio from minio.error import S3Error from app.core.config import settings import logging logger = logging.getLogger(__name__) class StorageService: """MinIO S3 objektumtároló szolgáltatás.""" @classmethod def _resolve_endpoint(cls, endpoint: str) -> str: """ Resolve hostname to IP address if endpoint contains a hostname. This helps with MinIO 'invalid hostname' issues. """ if "://" in endpoint: # Remove protocol endpoint = endpoint.split("://")[1] if ":" in endpoint: host, port = endpoint.split(":", 1) else: host, port = endpoint, "9000" # Try to resolve hostname to IP try: ip = socket.gethostbyname(host) resolved_endpoint = f"{ip}:{port}" logger.debug(f"Resolved endpoint {endpoint} -> {resolved_endpoint}") return resolved_endpoint except socket.gaierror: logger.warning(f"Could not resolve hostname {host}, using original endpoint") return endpoint # MinIO kliens inicializálása a konfigurációból @classmethod def _get_client(cls): """Get MinIO client with resolved endpoint.""" resolved_endpoint = cls._resolve_endpoint(settings.MINIO_ENDPOINT) return Minio( endpoint=resolved_endpoint, access_key=settings.MINIO_ACCESS_KEY, secret_key=settings.MINIO_SECRET_KEY, secure=settings.MINIO_SECURE, ) @classmethod def _get_client_instance(cls): """Get client instance (cached).""" if not hasattr(cls, '_client_instance'): cls._client_instance = cls._get_client() return cls._client_instance # Client property @classmethod def client(cls): """Get MinIO client instance.""" return cls._get_client_instance() @classmethod async def ensure_bucket_exists(cls, bucket_name: str) -> bool: """ Ellenőrzi, hogy a megadott vödör létezik-e, ha nem, létrehozza. Args: bucket_name: A vödör neve Returns: True ha a vödör létezik vagy sikeresen létrejött, False ha hiba történt. """ try: client = cls.client() if not client.bucket_exists(bucket_name): client.make_bucket(bucket_name) logger.info(f"Bucket '{bucket_name}' created.") else: logger.debug(f"Bucket '{bucket_name}' already exists.") return True except S3Error as e: logger.error(f"Error ensuring bucket '{bucket_name}': {e}") return False @classmethod async def upload_image( cls, file_bytes: bytes, bucket_name: str, object_name: str, content_type: str = "application/octet-stream", ) -> str: """ Feltölt egy fájlt a MinIO tárolóba. Args: file_bytes: A fájl tartalma bájtokban bucket_name: Cél vödör neve object_name: Objektum neve (pl. 'images/photo.jpg') content_type: MIME típus (alapértelmezett: 'application/octet-stream') Returns: Az objektum teljes elérési útja (bucket/object_name) Raises: S3Error: Ha a feltöltés sikertelen """ await cls.ensure_bucket_exists(bucket_name) # Feltöltés client = cls.client() client.put_object( bucket_name=bucket_name, object_name=object_name, data=BytesIO(file_bytes), length=len(file_bytes), content_type=content_type, ) logger.info(f"Uploaded object '{object_name}' to bucket '{bucket_name}'.") return f"{bucket_name}/{object_name}" @classmethod def get_presigned_url( cls, bucket_name: str, object_name: str, expires: timedelta = timedelta(hours=1), ) -> str: """ Generál egy előjegyzett URL-t a fájl letöltéséhez. Args: bucket_name: A vödör neve object_name: Az objektum neve expires: Az URL érvényességi ideje (alapértelmezett: 1 óra) Returns: Az előjegyzett URL string """ try: client = cls.client() url = client.presigned_get_object( bucket_name=bucket_name, object_name=object_name, expires=int(expires.total_seconds()), ) logger.debug(f"Generated presigned URL for '{bucket_name}/{object_name}'.") return url except S3Error as e: logger.error(f"Error generating presigned URL: {e}") raise # Kompatibilitás a régi kóddal BUCKET_NAME = "vehicle-documents" @classmethod async def upload_document(cls, file_bytes: bytes, file_name: str, folder: str) -> str: """Kompatibilitási metódus a régi kóddal.""" object_name = f"{folder}/{uuid.uuid4()}_{file_name}" return await cls.upload_image( file_bytes=file_bytes, bucket_name=cls.BUCKET_NAME, object_name=object_name, content_type="application/octet-stream", )