Initial commit: runnable Ask a Plumber First server.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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{}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
type spaces struct {
|
||||
client *s3.Client
|
||||
cfg SpacesConfig
|
||||
}
|
||||
|
||||
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-hosted–style 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
|
||||
}
|
||||
Reference in New Issue
Block a user