143 lines
4.6 KiB
Python
143 lines
4.6 KiB
Python
# backend/utils/minio_client.py
|
|
from datetime import timedelta
|
|
import os
|
|
from minio import Minio
|
|
from minio.error import S3Error
|
|
from django.conf import settings
|
|
from typing import Optional, BinaryIO
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MinIOClient:
|
|
"""Cliente singleton para MinIO con operaciones avanzadas"""
|
|
|
|
_instance = None
|
|
_client = None
|
|
_bucket_name = None
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
if self._client is None and settings.STORAGE_BACKEND == 'minio':
|
|
self._initialize_client()
|
|
|
|
def _initialize_client(self):
|
|
"""Inicializa el cliente de MinIO"""
|
|
try:
|
|
endpoint = os.getenv('MINIO_ENDPOINT', 'minio:9000')
|
|
access_key = os.getenv('MINIO_ACCESS_KEY')
|
|
secret_key = os.getenv('MINIO_SECRET_KEY')
|
|
secure = os.getenv('MINIO_SECURE', 'false').lower() == 'true'
|
|
|
|
self._client = Minio(
|
|
endpoint=endpoint,
|
|
access_key=access_key,
|
|
secret_key=secret_key,
|
|
secure=secure
|
|
)
|
|
|
|
self._bucket_name = os.environ.get('MINIO_BUCKET_NAME', 'efc-backend-dev')
|
|
|
|
# Asegurar que el bucket existe
|
|
if not self._client.bucket_exists(self._bucket_name):
|
|
self._client.make_bucket(self._bucket_name)
|
|
|
|
except Exception as e:
|
|
raise
|
|
|
|
def upload_file(
|
|
self,
|
|
object_name: str,
|
|
file_path: str = None,
|
|
file_data: BinaryIO = None,
|
|
content_type: str = None,
|
|
metadata: dict = None
|
|
) -> bool:
|
|
"""
|
|
Sube un archivo a MinIO
|
|
|
|
Args:
|
|
object_name: Ruta del objeto en el bucket (ej: 'documents/archivo.xml')
|
|
file_path: Ruta local del archivo (opcional)
|
|
file_data: Datos del archivo en memoria (opcional)
|
|
content_type: MIME type del archivo
|
|
metadata: Metadatos adicionales
|
|
|
|
Returns:
|
|
bool: True si se subió correctamente
|
|
"""
|
|
try:
|
|
if file_path:
|
|
self._client.fput_object(
|
|
bucket_name=self._bucket_name,
|
|
object_name=object_name,
|
|
file_path=file_path,
|
|
content_type=content_type,
|
|
metadata=metadata
|
|
)
|
|
elif file_data:
|
|
self._client.put_object(
|
|
bucket_name=self._bucket_name,
|
|
object_name=object_name,
|
|
data=file_data,
|
|
length=-1,
|
|
part_size=10*1024*1024, # 10MB
|
|
content_type=content_type,
|
|
metadata=metadata
|
|
)
|
|
else:
|
|
raise ValueError("You must provide file_path or file_data")
|
|
|
|
return True
|
|
|
|
except S3Error as e:
|
|
return False
|
|
|
|
def get_file_url(self, object_name: str, expires: int = 3600) -> Optional[str]:
|
|
"""Genera una URL firmada para acceder al archivo"""
|
|
try:
|
|
url = self._client.presigned_get_object(
|
|
bucket_name=self._bucket_name,
|
|
object_name=object_name,
|
|
expires=timedelta(seconds=expires)
|
|
)
|
|
|
|
# Reemplazar endpoint interno por público si está configurado
|
|
public_endpoint = os.getenv('MINIO_PUBLIC_ENDPOINT')
|
|
if public_endpoint and url:
|
|
internal_endpoint = os.getenv('MINIO_ENDPOINT', 'minio:9000')
|
|
url = url.replace(internal_endpoint, public_endpoint)
|
|
|
|
return url
|
|
except S3Error as e:
|
|
return None
|
|
|
|
def delete_file(self, object_name: str) -> bool:
|
|
"""Elimina un archivo del bucket"""
|
|
try:
|
|
self._client.remove_object(
|
|
bucket_name=self._bucket_name,
|
|
object_name=object_name
|
|
)
|
|
return True
|
|
except S3Error as e:
|
|
return False
|
|
|
|
def file_exists(self, object_name: str) -> bool:
|
|
"""Verifica si un archivo existe en el bucket"""
|
|
try:
|
|
self._client.stat_object(
|
|
bucket_name=self._bucket_name,
|
|
object_name=object_name
|
|
)
|
|
return True
|
|
except S3Error:
|
|
return False
|
|
|
|
# Singleton para uso global
|
|
minio_client = MinIOClient() |