package service import ( "context" "io" "mime" "path/filepath" "strings" "github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7" "github.com/rs/zerolog" minioStorage "web-qudo-be/config/config" ) type CmsMediaService struct { Minio *minioStorage.MinioStorage Log zerolog.Logger } func NewCmsMediaService(minio *minioStorage.MinioStorage, log zerolog.Logger) *CmsMediaService { return &CmsMediaService{Minio: minio, Log: log} } // Viewer streams a CMS object from MinIO (same idea as article-files viewer). func (s *CmsMediaService) Viewer(c *fiber.Ctx) error { objectKey := strings.TrimSpace(c.Params("*")) objectKey = strings.TrimPrefix(objectKey, "/") if objectKey == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "messages": []string{"object key required"}, }) } if !strings.HasPrefix(objectKey, "cms/") || strings.Contains(objectKey, "..") { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "messages": []string{"invalid object key"}, }) } ctx := context.Background() bucket := s.Minio.Cfg.ObjectStorage.MinioStorage.BucketName client, err := s.Minio.ConnectMinio() if err != nil { s.Log.Error().Err(err).Msg("cms media viewer: minio connect") return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "messages": []string{"storage unavailable"}, }) } obj, err := client.GetObject(ctx, bucket, objectKey, minio.GetObjectOptions{}) if err != nil { s.Log.Error().Err(err).Str("key", objectKey).Msg("cms media viewer: get object") return c.Status(fiber.StatusNotFound).SendString("not found") } defer obj.Close() ext := strings.ToLower(filepath.Ext(objectKey)) contentType := mime.TypeByExtension(ext) if contentType == "" { contentType = "application/octet-stream" } c.Set("Content-Type", contentType) c.Set("Cache-Control", "public, max-age=86400") if _, err := io.Copy(c.Response().BodyWriter(), obj); err != nil { s.Log.Error().Err(err).Msg("cms media viewer: stream") return err } return nil }