package storage import ( "context" "fmt" "mime" "mime/multipart" "path/filepath" "strings" "time" "github.com/google/uuid" "github.com/minio/minio-go/v7" appcfg "web-qudo-be/config/config" ) // CMSPreviewURL is the absolute URL served by this API (GET /cms-media/viewer/...) for DB image_url / media_url fields. func CMSPreviewURL(cfg *appcfg.Config, objectKey string) string { base := cfg.APIPublicBaseURL() key := strings.TrimPrefix(strings.TrimSpace(objectKey), "/") return base + "/cms-media/viewer/" + key } var imageExts = map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, } var mediaExts = map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, ".mp4": true, ".webm": true, } // mediaLibraryExts = images + video + audio + common documents (admin Media Library upload). var mediaLibraryExts = map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, ".svg": true, ".mp4": true, ".webm": true, ".mov": true, ".mp3": true, ".wav": true, ".ogg": true, ".m4a": true, ".pdf": true, ".doc": true, ".docx": true, ".txt": true, ".csv": true, } // UploadCMSObject stores a file in MinIO under cms/{folder}/YYYY/MM/{uuid}{ext} and returns object key + preview URL (API viewer, not direct MinIO). func UploadCMSObject(ms *appcfg.MinioStorage, folder string, file *multipart.FileHeader, allowVideo bool) (objectKey string, previewURL string, err error) { if file == nil { return "", "", fmt.Errorf("file is required") } ext := strings.ToLower(filepath.Ext(file.Filename)) if folder == "media-library" { if !mediaLibraryExts[ext] { return "", "", fmt.Errorf("unsupported file type for media library") } } else if allowVideo { if !mediaExts[ext] { return "", "", fmt.Errorf("unsupported file type (allowed: images, mp4, webm)") } } else if !imageExts[ext] { return "", "", fmt.Errorf("unsupported image type") } client, err := ms.ConnectMinio() if err != nil { return "", "", err } src, err := file.Open() if err != nil { return "", "", err } defer src.Close() bucket := ms.Cfg.ObjectStorage.MinioStorage.BucketName now := time.Now() objectKey = fmt.Sprintf("cms/%s/%d/%02d/%s%s", folder, now.Year(), int(now.Month()), uuid.New().String(), ext) contentType := mime.TypeByExtension(ext) if contentType == "" { contentType = "application/octet-stream" } _, err = client.PutObject(context.Background(), bucket, objectKey, src, file.Size, minio.PutObjectOptions{ ContentType: contentType, }) if err != nil { return "", "", err } return objectKey, CMSPreviewURL(ms.Cfg, objectKey), nil } // UploadMediaLibraryObject stores under cms/media-library/... with a broader MIME allowlist. func UploadMediaLibraryObject(ms *appcfg.MinioStorage, file *multipart.FileHeader) (objectKey string, previewURL string, err error) { if file == nil { return "", "", fmt.Errorf("file is required") } ext := strings.ToLower(filepath.Ext(file.Filename)) if !mediaLibraryExts[ext] { return "", "", fmt.Errorf("unsupported file type for media library") } return UploadCMSObject(ms, "media-library", file, false) }