2023-02-13 19:36:48 +08:00
|
|
|
package s3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
2024-01-29 21:12:29 +08:00
|
|
|
"time"
|
2023-02-13 19:36:48 +08:00
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
2024-05-02 21:28:06 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/config"
|
2023-02-13 19:36:48 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
2024-05-02 21:28:06 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
2024-01-29 23:15:47 +08:00
|
|
|
"github.com/pkg/errors"
|
2023-02-13 19:36:48 +08:00
|
|
|
|
2024-05-02 21:28:06 +08:00
|
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
|
|
)
|
2024-01-29 21:12:29 +08:00
|
|
|
|
2023-02-13 19:36:48 +08:00
|
|
|
type Client struct {
|
2024-05-02 21:28:06 +08:00
|
|
|
Client *s3.Client
|
|
|
|
Bucket *string
|
2023-02-13 19:36:48 +08:00
|
|
|
}
|
|
|
|
|
2024-05-17 08:50:02 +08:00
|
|
|
func NewClient(ctx context.Context, s3Config *storepb.StorageS3Config) (*Client, error) {
|
2023-03-18 22:34:22 +08:00
|
|
|
resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...any) (aws.Endpoint, error) {
|
2023-02-13 19:36:48 +08:00
|
|
|
return aws.Endpoint{
|
2024-05-02 21:28:06 +08:00
|
|
|
URL: s3Config.Endpoint,
|
2023-02-13 19:36:48 +08:00
|
|
|
}, nil
|
|
|
|
})
|
2024-05-02 21:28:06 +08:00
|
|
|
cfg, err := config.LoadDefaultConfig(ctx,
|
|
|
|
config.WithEndpointResolverWithOptions(resolver),
|
|
|
|
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(s3Config.AccessKeyId, s3Config.AccessKeySecret, "")),
|
|
|
|
config.WithRegion(s3Config.Region),
|
2023-02-13 19:36:48 +08:00
|
|
|
)
|
|
|
|
if err != nil {
|
2024-04-28 21:36:22 +08:00
|
|
|
return nil, errors.Wrap(err, "failed to load s3 config")
|
2023-02-13 19:36:48 +08:00
|
|
|
}
|
|
|
|
|
2024-05-02 21:28:06 +08:00
|
|
|
client := s3.NewFromConfig(cfg)
|
2023-02-13 19:36:48 +08:00
|
|
|
return &Client{
|
2023-02-24 00:02:51 +08:00
|
|
|
Client: client,
|
2024-05-02 21:28:06 +08:00
|
|
|
Bucket: aws.String(s3Config.Bucket),
|
2023-02-13 19:36:48 +08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-05-02 21:28:06 +08:00
|
|
|
// UploadObject uploads an object to S3.
|
2024-05-02 21:44:17 +08:00
|
|
|
func (c *Client) UploadObject(ctx context.Context, key string, fileType string, content io.Reader) (string, error) {
|
|
|
|
uploader := manager.NewUploader(c.Client)
|
2024-05-02 21:28:06 +08:00
|
|
|
putInput := s3.PutObjectInput{
|
2024-05-02 21:44:17 +08:00
|
|
|
Bucket: c.Bucket,
|
2024-05-02 21:28:06 +08:00
|
|
|
Key: aws.String(key),
|
2023-02-13 19:36:48 +08:00
|
|
|
ContentType: aws.String(fileType),
|
2024-05-02 21:28:06 +08:00
|
|
|
Body: content,
|
2023-11-23 23:20:11 +08:00
|
|
|
}
|
2024-05-02 21:28:06 +08:00
|
|
|
result, err := uploader.Upload(ctx, &putInput)
|
2023-02-13 19:36:48 +08:00
|
|
|
if err != nil {
|
2023-02-15 22:54:46 +08:00
|
|
|
return "", err
|
2023-02-13 19:36:48 +08:00
|
|
|
}
|
2023-02-27 21:26:50 +08:00
|
|
|
|
2024-05-02 21:28:06 +08:00
|
|
|
resultKey := result.Key
|
|
|
|
if resultKey == nil || *resultKey == "" {
|
|
|
|
return "", errors.New("failed to get file key")
|
|
|
|
}
|
|
|
|
return *resultKey, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PresignGetObject presigns an object in S3.
|
2024-05-02 21:44:17 +08:00
|
|
|
func (c *Client) PresignGetObject(ctx context.Context, key string) (string, error) {
|
|
|
|
presignClient := s3.NewPresignClient(c.Client)
|
2024-05-02 21:46:47 +08:00
|
|
|
presignResult, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
2024-05-02 21:44:17 +08:00
|
|
|
Bucket: aws.String(*c.Bucket),
|
2024-05-02 21:28:06 +08:00
|
|
|
Key: aws.String(key),
|
|
|
|
}, func(opts *s3.PresignOptions) {
|
2024-05-12 08:03:56 +08:00
|
|
|
// Set the expiration time of the presigned URL to 5 days.
|
|
|
|
// Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
|
|
|
|
opts.Expires = time.Duration(5 * 24 * time.Hour)
|
2024-05-02 21:28:06 +08:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "failed to presign put object")
|
2023-03-04 20:06:32 +08:00
|
|
|
}
|
2024-05-02 21:28:06 +08:00
|
|
|
return presignResult.URL, nil
|
2023-02-13 19:36:48 +08:00
|
|
|
}
|
2024-05-02 21:44:17 +08:00
|
|
|
|
|
|
|
// DeleteObject deletes an object in S3.
|
|
|
|
func (c *Client) DeleteObject(ctx context.Context, key string) error {
|
|
|
|
_, err := c.Client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
|
|
Bucket: c.Bucket,
|
|
|
|
Key: aws.String(key),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to delete object")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|