Files
plumber/internal/blob/spaces.go
T

89 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package blob
import (
"context"
"fmt"
"io"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
// Uploader stores public avatar objects.
type Uploader interface {
Enabled() bool
Upload(ctx context.Context, key string, body io.Reader, contentType string, size int64) (publicURL string, err error)
}
// Disabled is a no-op uploader used when Spaces is not configured.
type Disabled struct{}
// SpacesConfig holds DigitalOcean Spaces settings.
type SpacesConfig struct {
Key string
Secret string
Region string
Bucket string
Endpoint string // e.g. https://nyc3.digitaloceanspaces.com
CDNBase string // optional public base URL without trailing slash
}
type spaces struct {
client *s3.Client
cfg SpacesConfig
}
func (Disabled) Enabled() bool { return false }
func (Disabled) Upload(context.Context, string, io.Reader, string, int64) (string, error) {
return "", fmt.Errorf("avatar uploads are not configured")
}
// NewSpaces returns an Uploader when required env is present; otherwise Disabled.
func NewSpaces(cfg SpacesConfig) Uploader {
cfg.Key = strings.TrimSpace(cfg.Key)
cfg.Secret = strings.TrimSpace(cfg.Secret)
cfg.Region = strings.TrimSpace(cfg.Region)
cfg.Bucket = strings.TrimSpace(cfg.Bucket)
cfg.Endpoint = strings.TrimSpace(cfg.Endpoint)
cfg.CDNBase = strings.TrimRight(strings.TrimSpace(cfg.CDNBase), "/")
if cfg.Key == "" || cfg.Secret == "" || cfg.Region == "" || cfg.Bucket == "" || cfg.Endpoint == "" {
return Disabled{}
}
client := s3.New(s3.Options{
Region: cfg.Region,
Credentials: credentials.NewStaticCredentialsProvider(cfg.Key, cfg.Secret, ""),
BaseEndpoint: aws.String(cfg.Endpoint),
})
return &spaces{client: client, cfg: cfg}
}
func (s *spaces) Enabled() bool { return true }
func (s *spaces) Upload(ctx context.Context, key string, body io.Reader, contentType string, size int64) (string, error) {
key = strings.TrimPrefix(key, "/")
input := &s3.PutObjectInput{
Bucket: aws.String(s.cfg.Bucket),
Key: aws.String(key),
Body: body,
ContentType: aws.String(contentType),
ACL: types.ObjectCannedACLPublicRead,
}
if size > 0 {
input.ContentLength = aws.Int64(size)
}
if _, err := s.client.PutObject(ctx, input); err != nil {
return "", err
}
if s.cfg.CDNBase != "" {
return s.cfg.CDNBase + "/" + key, nil
}
// Virtual-hostedstyle Spaces URL.
host := strings.TrimPrefix(s.cfg.Endpoint, "https://")
host = strings.TrimPrefix(host, "http://")
return fmt.Sprintf("https://%s.%s/%s", s.cfg.Bucket, host, key), nil
}