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" } }