165 lines
5.6 KiB
Python
Executable File
165 lines
5.6 KiB
Python
Executable File
import io
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Any, BinaryIO, Dict, List, Tuple
|
|
from uuid import UUID
|
|
|
|
from fastapi import HTTPException, UploadFile, status
|
|
from pytz import timezone
|
|
|
|
from app.core.config import settings
|
|
from app.core.minio_client import MinioClient
|
|
from app.models.file_model import FileModel
|
|
from app.repositories.file_repository import FileRepository
|
|
|
|
from . import BaseService
|
|
|
|
|
|
class FileService(BaseService[FileModel, FileRepository]):
|
|
def __init__(self, repository: FileRepository, minio_client: MinioClient):
|
|
super().__init__(FileModel, repository)
|
|
|
|
self.minio_client = minio_client
|
|
|
|
async def validate_file_extension(self, filename: str) -> bool:
|
|
"""Validasi ekstensi file."""
|
|
ext = os.path.splitext(filename)[1][1:].lower()
|
|
if ext not in settings.ALLOWED_EXTENSIONS:
|
|
allowed_exts = ", ".join(settings.ALLOWED_EXTENSIONS)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Ekstensi file tidak diperbolehkan. Ekstensi yang diperbolehkan: {allowed_exts}",
|
|
)
|
|
return True
|
|
|
|
async def validate_file_size(self, file_size: int) -> bool:
|
|
"""Cek ukuran file."""
|
|
if file_size > settings.MAX_UPLOAD_SIZE:
|
|
max_size_mb = settings.MAX_UPLOAD_SIZE / (1024 * 1024)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
|
detail=f"File terlalu besar. Ukuran maksimum adalah {max_size_mb:.1f}MB",
|
|
)
|
|
return True
|
|
|
|
async def upload_file(self, file: UploadFile, description: str = None, user_id: str = None) -> FileModel:
|
|
"""
|
|
Upload file ke MinIO dan simpan metadata ke database.
|
|
|
|
Args:
|
|
file: File yang diupload
|
|
description: Deskripsi file (opsional)
|
|
user_id: ID pengguna yang mengupload
|
|
|
|
Returns:
|
|
Model file yang telah disimpan
|
|
"""
|
|
try:
|
|
await self.validate_file_extension(file.filename)
|
|
|
|
object_name = f"{datetime.now(timezone(settings.TIMEZONE)).strftime('%Y%m%d%S')}-{file.filename}"
|
|
|
|
content = await file.read()
|
|
content_length = len(content)
|
|
|
|
await self.validate_file_size(content_length)
|
|
|
|
file_data = io.BytesIO(content)
|
|
|
|
metadata = {"filename": file.filename, "description": description or "", "uploaded_by": str(user_id)}
|
|
|
|
url = await self.minio_client.upload_file(
|
|
file_data=file_data,
|
|
object_name=object_name,
|
|
content_type=file.content_type,
|
|
content_length=content_length,
|
|
metadata=metadata,
|
|
)
|
|
|
|
file_data = {
|
|
"filename": file.filename,
|
|
"object_name": object_name,
|
|
"content_type": file.content_type,
|
|
"size": content_length,
|
|
"description": description,
|
|
"url": url,
|
|
"user_id": user_id,
|
|
}
|
|
|
|
db_file = await self.create(file_data)
|
|
return db_file
|
|
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception:
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Gagal mengupload file")
|
|
|
|
async def get_file_content(self, file_id: UUID) -> Tuple[BinaryIO, Dict[str, Any], FileModel]:
|
|
"""
|
|
Ambil konten file dari MinIO.
|
|
|
|
Args:
|
|
file_id: ID file di database
|
|
|
|
Returns:
|
|
Tuple dari (file content, object info, file model)
|
|
"""
|
|
try:
|
|
file_model = await self.find_by_id(file_id)
|
|
if not file_model:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File tidak ditemukan")
|
|
|
|
object_content, object_info = await self.minio_client.get_file(file_model.object_name)
|
|
|
|
return object_content, object_info, file_model
|
|
|
|
except HTTPException as e:
|
|
raise e
|
|
|
|
async def delete_file_with_content(self, file_id: str, user_id: str) -> bool:
|
|
"""
|
|
Hapus file dari MinIO dan database.
|
|
|
|
Args:
|
|
file_id: ID file di database
|
|
user_id: ID pengguna yang ingin menghapus file
|
|
|
|
Returns:
|
|
Boolean yang menunjukkan keberhasilan operasi
|
|
"""
|
|
try:
|
|
file_model = await self.find_by_id(file_id)
|
|
if not file_model:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File tidak ditemukan")
|
|
|
|
if str(file_model.uploaded_by) != user_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN, detail="Anda tidak memiliki akses untuk menghapus file ini"
|
|
)
|
|
|
|
await self.minio_client.delete_file(file_model.object_name)
|
|
|
|
await self.delete(file_id)
|
|
|
|
return True
|
|
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception:
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Gagal menghapus file")
|
|
|
|
async def get_files_by_user(self, user_id: str, limit: int = 100, offset: int = 0) -> Tuple[List[FileModel], int]:
|
|
"""
|
|
Ambil daftar file yang diupload oleh user tertentu.
|
|
|
|
Args:
|
|
user_id: ID pengguna
|
|
limit: Jumlah maksimum hasil yang dikembalikan
|
|
offset: Offset untuk paginasi
|
|
|
|
Returns:
|
|
Tuple dari (list file, total_count)
|
|
"""
|
|
filter_params = {"uploaded_by": user_id}
|
|
return await self.find_all(filter=filter_params, limit=limit, offset=offset)
|