102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
)
|
|
|
|
type MinIOStorage struct {
|
|
client *minio.Client
|
|
bucket string
|
|
}
|
|
|
|
type MinIOConfig struct {
|
|
Endpoint string
|
|
AccessKey string
|
|
SecretKey string
|
|
Bucket string
|
|
UseSSL bool
|
|
}
|
|
|
|
func NewMinIOStorage(cfg MinIOConfig) (*MinIOStorage, error) {
|
|
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
|
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
|
Secure: cfg.UseSSL,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create minio client: %w", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("check bucket: %w", err)
|
|
}
|
|
if !exists {
|
|
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{}); err != nil {
|
|
return nil, fmt.Errorf("create bucket: %w", err)
|
|
}
|
|
log.Printf("created minio bucket: %s", cfg.Bucket)
|
|
|
|
// Set bucket policy to allow public read
|
|
policy := fmt.Sprintf(`{
|
|
"Version": "2012-10-17",
|
|
"Statement": [{
|
|
"Effect": "Allow",
|
|
"Principal": {"AWS": ["*"]},
|
|
"Action": ["s3:GetObject"],
|
|
"Resource": ["arn:aws:s3:::%s/*"]
|
|
}]
|
|
}`, cfg.Bucket)
|
|
if err := client.SetBucketPolicy(ctx, cfg.Bucket, policy); err != nil {
|
|
log.Printf("warning: set bucket policy: %v", err)
|
|
}
|
|
}
|
|
|
|
return &MinIOStorage{client: client, bucket: cfg.Bucket}, nil
|
|
}
|
|
|
|
// Upload stores a file in MinIO and returns the object path (without bucket prefix).
|
|
// The returned path can be used to construct the public URL: /uploads/{path}
|
|
func (s *MinIOStorage) Upload(ctx context.Context, pathPrefix string, filename string, reader io.Reader, size int64, contentType string) (string, error) {
|
|
ext := filepath.Ext(filename)
|
|
if ext == "" {
|
|
ext = guessExt(contentType)
|
|
}
|
|
objectName := fmt.Sprintf("%s/%s%s", pathPrefix, uuid.New().String(), ext)
|
|
|
|
_, err := s.client.PutObject(ctx, s.bucket, objectName, reader, size, minio.PutObjectOptions{
|
|
ContentType: contentType,
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("put object: %w", err)
|
|
}
|
|
|
|
return objectName, nil
|
|
}
|
|
|
|
func guessExt(contentType string) string {
|
|
switch {
|
|
case strings.HasPrefix(contentType, "image/jpeg"):
|
|
return ".jpg"
|
|
case strings.HasPrefix(contentType, "image/png"):
|
|
return ".png"
|
|
case strings.HasPrefix(contentType, "image/gif"):
|
|
return ".gif"
|
|
case strings.HasPrefix(contentType, "image/webp"):
|
|
return ".webp"
|
|
case strings.HasPrefix(contentType, "image/svg"):
|
|
return ".svg"
|
|
default:
|
|
return ".bin"
|
|
}
|
|
}
|