narasiahli-be/app/module/chat/service/file_upload.service.go

144 lines
3.6 KiB
Go

package service
import (
"context"
"crypto/md5"
"fmt"
"mime/multipart"
"path/filepath"
"strings"
"time"
"github.com/minio/minio-go/v7"
)
type fileUploadService struct {
minioClient *minio.Client
bucketName string
}
type FileUploadService interface {
UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error)
DeleteFile(filePath string) error
GetFileURL(filePath string) (string, error)
ValidateFile(file *multipart.FileHeader) error
}
func NewFileUploadService(minioClient *minio.Client, bucketName string) FileUploadService {
return &fileUploadService{
minioClient: minioClient,
bucketName: bucketName,
}
}
// UploadFile - Upload file to MinIO
func (f *fileUploadService) UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error) {
// Validate file
if err := f.ValidateFile(file); err != nil {
return "", 0, err
}
// Open file
src, err := file.Open()
if err != nil {
return "", 0, err
}
defer src.Close()
// Generate unique filename
ext := filepath.Ext(file.Filename)
fileName := strings.TrimSuffix(file.Filename, ext)
hasher := md5.New()
hasher.Write([]byte(fmt.Sprintf("%s-%d", fileName, time.Now().UnixNano())))
uniqueFileName := fmt.Sprintf("%x%s", hasher.Sum(nil), ext)
// Create file path
filePath = fmt.Sprintf("%s/%s", folder, uniqueFileName)
// Upload file to MinIO
ctx := context.Background()
_, err = f.minioClient.PutObject(ctx, f.bucketName, filePath, src, file.Size, minio.PutObjectOptions{
ContentType: file.Header.Get("Content-Type"),
})
if err != nil {
return "", 0, err
}
return filePath, file.Size, nil
}
// DeleteFile - Delete file from MinIO
func (f *fileUploadService) DeleteFile(filePath string) error {
ctx := context.Background()
return f.minioClient.RemoveObject(ctx, f.bucketName, filePath, minio.RemoveObjectOptions{})
}
// GetFileURL - Get file URL from MinIO
func (f *fileUploadService) GetFileURL(filePath string) (string, error) {
ctx := context.Background()
// Generate presigned URL (valid for 7 days)
url, err := f.minioClient.PresignedGetObject(ctx, f.bucketName, filePath, 7*24*time.Hour, nil)
if err != nil {
return "", err
}
return url.String(), nil
}
// ValidateFile - Validate uploaded file
func (f *fileUploadService) ValidateFile(file *multipart.FileHeader) error {
// Check file size (max 50MB)
const maxFileSize = 50 * 1024 * 1024 // 50MB
if file.Size > maxFileSize {
return fmt.Errorf("file size exceeds maximum limit of 50MB")
}
// Check file extension
ext := strings.ToLower(filepath.Ext(file.Filename))
allowedExts := []string{".pdf", ".doc", ".docx", ".txt", ".mp4", ".avi", ".mov", ".mp3", ".wav", ".jpg", ".jpeg", ".png", ".gif"}
isAllowed := false
for _, allowedExt := range allowedExts {
if ext == allowedExt {
isAllowed = true
break
}
}
if !isAllowed {
return fmt.Errorf("file type not allowed. Allowed types: %s", strings.Join(allowedExts, ", "))
}
// Check MIME type
contentType := file.Header.Get("Content-Type")
allowedMimeTypes := []string{
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain",
"video/mp4",
"video/avi",
"video/quicktime",
"audio/mpeg",
"audio/wav",
"image/jpeg",
"image/png",
"image/gif",
}
isValidMimeType := false
for _, allowedMimeType := range allowedMimeTypes {
if contentType == allowedMimeType {
isValidMimeType = true
break
}
}
if !isValidMimeType {
return fmt.Errorf("invalid file type. Content-Type: %s", contentType)
}
return nil
}