Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout. Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package objectstore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (s *Store) ensureBucket(ctx context.Context, region string, chatExpireDays int) error {
|
|
_, err := s.client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: aws.String(s.bucket)})
|
|
if err != nil {
|
|
if !isMissingObject(err) {
|
|
return fmt.Errorf("objectstore ping: %w", err)
|
|
}
|
|
if err := s.createBucket(ctx, region); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.applyLifecycle(ctx, chatExpireDays)
|
|
s.applyCORS(ctx)
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) createBucket(ctx context.Context, region string) error {
|
|
in := &s3.CreateBucketInput{Bucket: aws.String(s.bucket)}
|
|
if region != "" && region != defaultRegion {
|
|
in.CreateBucketConfiguration = &types.CreateBucketConfiguration{
|
|
LocationConstraint: types.BucketLocationConstraint(region),
|
|
}
|
|
}
|
|
if _, err := s.client.CreateBucket(ctx, in); err != nil {
|
|
var owned *types.BucketAlreadyOwnedByYou
|
|
var exists *types.BucketAlreadyExists
|
|
if errors.As(err, &owned) || errors.As(err, &exists) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("objectstore make bucket: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) applyLifecycle(ctx context.Context, days int) {
|
|
if days < 1 {
|
|
return
|
|
}
|
|
_, err := s.client.PutBucketLifecycleConfiguration(ctx, &s3.PutBucketLifecycleConfigurationInput{
|
|
Bucket: aws.String(s.bucket),
|
|
LifecycleConfiguration: &types.BucketLifecycleConfiguration{
|
|
Rules: []types.LifecycleRule{{
|
|
ID: aws.String("expire-chat"),
|
|
Status: types.ExpirationStatusEnabled,
|
|
Filter: &types.LifecycleRuleFilter{Prefix: aws.String("chat/")},
|
|
Expiration: &types.LifecycleExpiration{
|
|
Days: aws.Int32(int32(days)),
|
|
},
|
|
}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
s.log.Warn("objectstore lifecycle", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func (s *Store) applyCORS(ctx context.Context) {
|
|
_, err := s.client.PutBucketCors(ctx, &s3.PutBucketCorsInput{
|
|
Bucket: aws.String(s.bucket),
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{"GET", "PUT", "HEAD"},
|
|
AllowedHeaders: []string{"*"},
|
|
ExposeHeaders: []string{"ETag", "Content-Length"},
|
|
MaxAgeSeconds: aws.Int32(3600),
|
|
}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
s.log.Warn("objectstore cors", zap.Error(err))
|
|
}
|
|
}
|