package storage import ( "context" "fmt" "io" "net/url" "strings" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/sundynix/pets-be/internal/config" ) type Storage struct { client *minio.Client bucket string baseURL string } func New(cfg config.MinIOConfig) (*Storage, error) { endpoint, secure := cfg.Endpoint, cfg.UseSSL if strings.Contains(endpoint, "://") { parsed, err := url.Parse(endpoint) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Path != "" { return nil, fmt.Errorf("invalid minio endpoint %q", cfg.Endpoint) } endpoint, secure = parsed.Host, parsed.Scheme == "https" } client, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), Secure: secure, }) if err != nil { return nil, fmt.Errorf("init minio: %w", err) } scheme := "http" if secure { scheme = "https" } s := &Storage{client: client, bucket: cfg.Bucket, baseURL: scheme + "://" + endpoint} return s, nil } func (s *Storage) Bucket() string { return s.bucket } func (s *Storage) Put(ctx context.Context, key string, reader io.Reader, size int64, contentType string) error { _, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{ContentType: contentType}) return err } func (s *Storage) Remove(ctx context.Context, key string) error { return s.client.RemoveObject(ctx, s.bucket, key, minio.RemoveObjectOptions{}) } // PublicURL builds the URL stored on the file row; other models only store file IDs. func (s *Storage) PublicURL(key string) string { parts := strings.Split(key, "/") for i, part := range parts { parts[i] = url.PathEscape(part) } return s.baseURL + "/" + url.PathEscape(s.bucket) + "/" + strings.Join(parts, "/") }