Files
service-finder/backend/app/services/storage_service.py
2026-03-22 11:02:05 +00:00

170 lines
5.5 KiB
Python
Executable File

# /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",
)