apps/posts/storage/r2.go 1.8 K raw
1
package storage
2
3
import (
4
	"bytes"
5
	"context"
6
	"fmt"
7
	"net/url"
8
	"strings"
9
10
	"github.com/aws/aws-sdk-go-v2/aws"
11
	"github.com/aws/aws-sdk-go-v2/credentials"
12
	"github.com/aws/aws-sdk-go-v2/service/s3"
13
)
14
15
type R2 struct {
16
	bucket    string
17
	publicURL string
18
	client    *s3.Client
19
}
20
21
func NewR2(accountID, accessKeyID, secretAccessKey, bucket, publicURL string) (*R2, error) {
22
	if strings.TrimSpace(accountID) == "" || strings.TrimSpace(accessKeyID) == "" || strings.TrimSpace(secretAccessKey) == "" || strings.TrimSpace(bucket) == "" || strings.TrimSpace(publicURL) == "" {
23
		return nil, fmt.Errorf("R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, and R2_PUBLIC_URL are required")
24
	}
25
	endpoint := "https://" + accountID + ".r2.cloudflarestorage.com"
26
	cfg := aws.Config{
27
		Region:      "auto",
28
		Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
29
	}
30
	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
31
		o.BaseEndpoint = aws.String(endpoint)
32
		o.UsePathStyle = true
33
	})
34
	return &R2{bucket: bucket, publicURL: strings.TrimRight(publicURL, "/"), client: client}, nil
35
}
36
37
func (r *R2) Put(ctx context.Context, key, contentType string, data []byte) error {
38
	_, err := r.client.PutObject(ctx, &s3.PutObjectInput{
39
		Bucket:      aws.String(r.bucket),
40
		Key:         aws.String(key),
41
		Body:        bytes.NewReader(data),
42
		ContentType: aws.String(contentType),
43
	})
44
	return err
45
}
46
47
func (r *R2) Delete(ctx context.Context, key string) error {
48
	_, err := r.client.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: aws.String(r.bucket), Key: aws.String(key)})
49
	return err
50
}
51
52
func (r *R2) PublicURL(key string) string {
53
	if r.publicURL == "" {
54
		return ""
55
	}
56
	return r.publicURL + "/" + url.PathEscape(key)
57
}
58
59
func (r *R2) Name() string { return "r2" }