281 lines
7.5 KiB
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"
|
|
}
|
|
}
|