170 lines
5.5 KiB
Python
Executable File
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",
|
|
) |