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 }