satupeta-main/app/core/minio_client.py

194 lines
6.2 KiB
Python
Raw Normal View History

2026-01-27 02:11:58 +00:00
from typing import Any, BinaryIO, Dict, List, Optional, Tuple
from urllib.parse import urlparse
import aiohttp
from fastapi import HTTPException, status
from miniopy_async import Minio
from miniopy_async.error import S3Error
from app.core.config import settings
class MinioClient:
"""
Client class untuk interaksi dengan MinIO Object Storage secara asinkron.
"""
def __init__(self):
self.client = Minio(
endpoint=settings.MINIO_ENDPOINT_URL,
access_key=settings.MINIO_ROOT_USER,
secret_key=settings.MINIO_ROOT_PASSWORD,
secure=settings.MINIO_SECURE,
region=settings.MINIO_REGION,
)
self.bucket_name = settings.MINIO_BUCKET_NAME
async def init_bucket(self) -> None:
"""
Inisialisasi bucket jika belum ada.
"""
try:
if not await self.client.bucket_exists(self.bucket_name):
await self.client.make_bucket(self.bucket_name)
# Set bucket policy agar dapat diakses publik jika diperlukan
# policy = {...} # Define your policy if needed
# await self.client.set_bucket_policy(self.bucket_name, json.dumps(policy))
except S3Error as err:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error initializing MinIO bucket: {str(err)}",
)
async def upload_file(
self,
file_data: BinaryIO,
object_name: str,
content_type: str,
content_length: int,
metadata: Optional[Dict[str, str]] = None,
) -> str:
"""
Upload file ke MinIO.
Args:
file_data: File-like object untuk diupload
object_name: Nama objek di MinIO
content_type: Tipe konten file
metadata: Metadata tambahan untuk objek
Returns:
URL objek yang telah diupload
"""
try:
await self.init_bucket()
# Upload file
await self.client.put_object(
bucket_name=self.bucket_name,
object_name=object_name,
data=file_data,
length=content_length,
content_type=content_type,
metadata=metadata,
)
# Generate URL
url = await self.get_file_url(object_name)
return url
except S3Error as err:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error uploading file to MinIO: {str(err)}"
)
async def get_file(self, object_name: str) -> Tuple[BinaryIO, Dict[str, Any]]:
"""
Ambil file dari MinIO.
Args:
object_name: Nama objek di MinIO
Returns:
Tuple dari (file data, object info)
"""
try:
stat = await self.client.stat_object(bucket_name=self.bucket_name, object_name=object_name)
session = aiohttp.ClientSession()
response = await self.client.get_object(
bucket_name=self.bucket_name, object_name=object_name, session=session
)
return response, stat.__dict__
except S3Error as err:
if err.code == "NoSuchKey":
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error retrieving file from MinIO: {str(err)}",
)
async def delete_file(self, object_name: str) -> bool:
"""
Hapus file dari MinIO.
Args:
object_name: Nama objek di MinIO
Returns:
Boolean yang menunjukkan keberhasilan operasi
"""
try:
await self.client.remove_object(self.bucket_name, object_name)
return True
except S3Error as err:
if err.code == "NoSuchKey":
return False
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error deleting file from MinIO: {str(err)}"
)
async def get_file_url(self, object_name: str) -> str:
"""
Dapatkan URL untuk mengakses file.
Args:
object_name: Nama objek di MinIO
Returns:
URL untuk mengakses file
"""
try:
# For public access
if settings.MINIO_SECURE:
protocol = "https"
else:
protocol = "http"
parsed_endpoint = urlparse(settings.MINIO_ENDPOINT_URL)
host = parsed_endpoint.netloc or settings.MINIO_ENDPOINT_URL
return f"{protocol}://{host}/{self.bucket_name}/{object_name}"
# For presigned URL (time-limited access):
# return await self.client.presigned_get_object(
# bucket_name=self.bucket_name,
# object_name=object_name,
# expires=timedelta(hours=1)
# )
except S3Error as err:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error generating URL: {str(err)}"
)
async def list_files(self, prefix: str = "", recursive: bool = True) -> List[Dict[str, Any]]:
"""
Daftar semua file di dalam bucket dengan prefix tertentu.
Args:
prefix: Awalan objek yang dicari
recursive: Jika True, juga mencari di subdirektori
Returns:
List dari item objek
"""
try:
objects = []
async for obj in self.client.list_objects(self.bucket_name, prefix=prefix, recursive=recursive):
objects.append(
{
"name": obj.object_name,
"size": obj.size,
"last_modified": obj.last_modified,
"etag": obj.etag,
}
)
return objects
except S3Error as err:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error listing files: {str(err)}"
)