kontenhumas-be/app/module/clients/service/client_logo_upload.service.go

281 lines
7.5 KiB
Go

package service
import (
"context"
"fmt"
"io"
"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, clientName string) (string, error) {
s.Log.Info().
Str("UploadLogo", clientId).
Msg("Client upload logo process")
// Get the uploaded file
file, err := c.FormFile("logo")
if err != nil {
return "", fmt.Errorf("failed to get uploaded file: %w", err)
}
s.Log.Info().
Str("Upload Logo File", clientId).Interface("file", file).Msg("Client upload logo files")
// Validate file type
if !s.isValidImageType(file.Filename) {
return "", fmt.Errorf("invalid file type. Only jpg, jpeg, png, gif, webp are allowed")
}
s.Log.Info().
Str("Upload Logo File", clientId).Interface("file", s.isValidImageType(file.Filename)).Msg("Client upload logo files")
// 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
s.Log.Info().
Str("Upload Logo bucketName", clientId).Interface("bucketName", bucketName).Msg("Client upload logo files")
// Generate unique filename using client name
filename := s.generateUniqueFilename(file.Filename, clientName)
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
}
// GetLogoFile retrieves logo file from MinIO and returns file data and content type
func (s *ClientLogoUploadService) GetLogoFile(clientId, filename string) ([]byte, string, error) {
if filename == "" {
return nil, "", fmt.Errorf("filename is required")
}
// Create MinIO connection
minioClient, err := s.MinioStorage.ConnectMinio()
if err != nil {
return nil, "", fmt.Errorf("failed to connect to MinIO: %w", err)
}
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := fmt.Sprintf("clients/logos/%s/%s", clientId, filename)
// Get object from MinIO
object, err := minioClient.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
return nil, "", fmt.Errorf("failed to get object from MinIO: %w", err)
}
defer object.Close()
// Read object data
data, err := io.ReadAll(object)
if err != nil {
return nil, "", fmt.Errorf("failed to read object data: %w", err)
}
// Get content type based on file extension
contentType := s.getContentType(filename)
s.Log.Info().
Str("filename", filename).
Str("contentType", contentType).
Int("dataSize", len(data)).
Msg("Logo file retrieved successfully")
return data, contentType, 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 using client name slug + Unix timestamp
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename, clientName string) string {
ext := filepath.Ext(originalFilename)
// Create slug from client name
clientSlug := s.createSlug(clientName)
// Get Unix timestamp
now := time.Now()
timestamp := now.Unix()
// Format: clientname_unix_timestamp.ext
return fmt.Sprintf("%s_%d%s", clientSlug, timestamp, ext)
}
// createSlug converts client name to URL-friendly slug
func (s *ClientLogoUploadService) createSlug(name string) string {
// Convert to lowercase
slug := strings.ToLower(name)
// Replace spaces with underscores
slug = strings.ReplaceAll(slug, " ", "_")
// Replace hyphens with underscores
slug = strings.ReplaceAll(slug, "-", "_")
// Remove special characters (keep only alphanumeric and underscores)
var result strings.Builder
for _, char := range slug {
if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '_' {
result.WriteRune(char)
}
}
slug = result.String()
// Remove multiple consecutive underscores
for strings.Contains(slug, "__") {
slug = strings.ReplaceAll(slug, "__", "_")
}
// Remove leading/trailing underscores
slug = strings.Trim(slug, "_")
// If empty after cleaning, use "client"
if slug == "" {
slug = "client"
}
return slug
}
// 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"
}
}