197 lines
5.1 KiB
Go
197 lines
5.1 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"math/rand"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"netidhub-saas-be/config/config"
|
||
|
|
|
||
|
|
"github.com/gofiber/fiber/v2"
|
||
|
|
"github.com/minio/minio-go/v7"
|
||
|
|
"github.com/rs/zerolog"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ClientLogoUploadService handles client logo uploads to MinIO
|
||
|
|
type ClientLogoUploadService struct {
|
||
|
|
MinioStorage *config.MinioStorage
|
||
|
|
Log zerolog.Logger
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClientLogoUploadService creates a new client logo upload service
|
||
|
|
func NewClientLogoUploadService(minioStorage *config.MinioStorage, log zerolog.Logger) *ClientLogoUploadService {
|
||
|
|
return &ClientLogoUploadService{
|
||
|
|
MinioStorage: minioStorage,
|
||
|
|
Log: log,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// UploadLogo uploads client logo to MinIO and returns the image path
|
||
|
|
func (s *ClientLogoUploadService) UploadLogo(c *fiber.Ctx, clientId string) (string, error) {
|
||
|
|
// Get the uploaded file
|
||
|
|
file, err := c.FormFile("logo")
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to get uploaded file: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate file type
|
||
|
|
if !s.isValidImageType(file.Filename) {
|
||
|
|
return "", fmt.Errorf("invalid file type. Only jpg, jpeg, png, gif, webp are allowed")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate file size (max 5MB)
|
||
|
|
const maxSize = 5 * 1024 * 1024 // 5MB
|
||
|
|
if file.Size > maxSize {
|
||
|
|
return "", fmt.Errorf("file size too large. Maximum size is 5MB")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create MinIO connection
|
||
|
|
minioClient, err := s.MinioStorage.ConnectMinio()
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to connect to MinIO: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||
|
|
|
||
|
|
// Generate unique filename
|
||
|
|
filename := s.generateUniqueFilename(file.Filename)
|
||
|
|
objectName := fmt.Sprintf("clients/logos/%s/%s", clientId, filename)
|
||
|
|
|
||
|
|
// Open file
|
||
|
|
src, err := file.Open()
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to open uploaded file: %w", err)
|
||
|
|
}
|
||
|
|
defer src.Close()
|
||
|
|
|
||
|
|
// Upload to MinIO
|
||
|
|
_, err = minioClient.PutObject(
|
||
|
|
context.Background(),
|
||
|
|
bucketName,
|
||
|
|
objectName,
|
||
|
|
src,
|
||
|
|
file.Size,
|
||
|
|
minio.PutObjectOptions{
|
||
|
|
ContentType: s.getContentType(file.Filename),
|
||
|
|
},
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to upload file to MinIO: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.Log.Info().
|
||
|
|
Str("clientId", clientId).
|
||
|
|
Str("filename", filename).
|
||
|
|
Str("objectName", objectName).
|
||
|
|
Int64("fileSize", file.Size).
|
||
|
|
Msg("Client logo uploaded successfully")
|
||
|
|
|
||
|
|
return objectName, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DeleteLogo deletes client logo from MinIO
|
||
|
|
func (s *ClientLogoUploadService) DeleteLogo(clientId, imagePath string) error {
|
||
|
|
if imagePath == "" {
|
||
|
|
return nil // Nothing to delete
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create MinIO connection
|
||
|
|
minioClient, err := s.MinioStorage.ConnectMinio()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to connect to MinIO: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||
|
|
|
||
|
|
// Delete from MinIO
|
||
|
|
err = minioClient.RemoveObject(context.Background(), bucketName, imagePath, minio.RemoveObjectOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to delete file from MinIO: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.Log.Info().
|
||
|
|
Str("clientId", clientId).
|
||
|
|
Str("imagePath", imagePath).
|
||
|
|
Msg("Client logo deleted successfully")
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetLogoURL generates a presigned URL for the logo
|
||
|
|
func (s *ClientLogoUploadService) GetLogoURL(imagePath string, expiry time.Duration) (string, error) {
|
||
|
|
if imagePath == "" {
|
||
|
|
return "", nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create MinIO connection
|
||
|
|
minioClient, err := s.MinioStorage.ConnectMinio()
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to connect to MinIO: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||
|
|
|
||
|
|
// Generate presigned URL
|
||
|
|
url, err := minioClient.PresignedGetObject(context.Background(), bucketName, imagePath, expiry, nil)
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to generate presigned URL: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return url.String(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// isValidImageType checks if the file extension is a valid image type
|
||
|
|
func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
||
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
||
|
|
validExts := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||
|
|
|
||
|
|
for _, validExt := range validExts {
|
||
|
|
if ext == validExt {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// generateUniqueFilename generates a unique filename with timestamp and random number
|
||
|
|
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename string) string {
|
||
|
|
ext := filepath.Ext(originalFilename)
|
||
|
|
nameWithoutExt := strings.TrimSuffix(filepath.Base(originalFilename), ext)
|
||
|
|
|
||
|
|
// Clean filename (remove spaces and special characters)
|
||
|
|
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, " ", "_")
|
||
|
|
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, "-", "_")
|
||
|
|
|
||
|
|
// Generate unique suffix
|
||
|
|
now := time.Now()
|
||
|
|
rand.Seed(now.UnixNano())
|
||
|
|
randomNum := rand.Intn(1000000)
|
||
|
|
|
||
|
|
// Format: originalname_timestamp_random.ext
|
||
|
|
return fmt.Sprintf("%s_%d_%d%s",
|
||
|
|
nameWithoutExt,
|
||
|
|
now.Unix(),
|
||
|
|
randomNum,
|
||
|
|
ext)
|
||
|
|
}
|
||
|
|
|
||
|
|
// getContentType returns the MIME type based on file extension
|
||
|
|
func (s *ClientLogoUploadService) getContentType(filename string) string {
|
||
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
||
|
|
switch ext {
|
||
|
|
case ".jpg", ".jpeg":
|
||
|
|
return "image/jpeg"
|
||
|
|
case ".png":
|
||
|
|
return "image/png"
|
||
|
|
case ".gif":
|
||
|
|
return "image/gif"
|
||
|
|
case ".webp":
|
||
|
|
return "image/webp"
|
||
|
|
default:
|
||
|
|
return "application/octet-stream"
|
||
|
|
}
|
||
|
|
}
|