diff --git a/go.mod b/go.mod index 08e11c9..0cadd5d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,10 @@ go 1.25rc3 require ( github.com/99designs/gqlgen v0.17.77 github.com/aws/aws-sdk-go v1.55.8 + github.com/aws/aws-sdk-go-v2 v1.38.2 + github.com/aws/aws-sdk-go-v2/config v1.31.4 + github.com/aws/aws-sdk-go-v2/credentials v1.18.8 + github.com/aws/aws-sdk-go-v2/service/s3 v1.87.2 github.com/caarlos0/env/v8 v8.0.0 github.com/go-chi/chi/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.3.0 @@ -22,6 +26,20 @@ require ( require ( github.com/agnivade/levenshtein v1.2.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.28.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.1 // indirect + github.com/aws/smithy-go v1.23.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index b7879e8..5cc5221 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,42 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= +github.com/aws/aws-sdk-go-v2 v1.38.2 h1:QUkLO1aTW0yqW95pVzZS0LGFanL71hJ0a49w4TJLMyM= +github.com/aws/aws-sdk-go-v2 v1.38.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2/config v1.31.4 h1:aY2IstXOfjdLtr1lDvxFBk5DpBnHgS5GS3jgR/0BmPw= +github.com/aws/aws-sdk-go-v2/config v1.31.4/go.mod h1:1IAykiegrTp6n+CbZoCpW6kks1I74fEDgl2BPQSkLSU= +github.com/aws/aws-sdk-go-v2/credentials v1.18.8 h1:0FfdP0I9gs/f1rwtEdkcEdsclTEkPB8o6zWUG2Z8+IM= +github.com/aws/aws-sdk-go-v2/credentials v1.18.8/go.mod h1:9UReQ1UmGooX93JKzHyr7PRF3F+p3r+PmRwR7+qHJYA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.5 h1:ul7hICbZ5Z/Pp9VnLVGUVe7rqYLXCyIiPU7hQ0sRkow= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.5/go.mod h1:5cIWJ0N6Gjj+72Q6l46DeaNtcxXHV42w/Uq3fIfeUl4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5 h1:d45S2DqHZOkHu0uLUW92VdBoT5v0hh3EyR+DzMEh3ag= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.5/go.mod h1:G6e/dR2c2huh6JmIo9SXysjuLuDDGWMeYGibfW2ZrXg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5 h1:ENhnQOV3SxWHplOqNN1f+uuCNf9n4Y/PKpl6b1WRP0Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.5/go.mod h1:csQLMI+odbC0/J+UecSTztG70Dc4aTCOu4GyPNDNpVo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.5 h1:ovHE1XM53pMGOwINf8Mas4FMl5XRRMAihNokV1YViZ8= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.5/go.mod h1:Cmu/DOSYwcr0xYTFk7sA9NJ5HF3ND0EqNUBdoK16nPI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.5 h1:gC3YW8AojITDXfI5avcKZst5iOg6v5aQEU4HIcxwAss= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.5/go.mod h1:z5OdVolKifM0NpEel6wLkM/TQ0eodWB2dmDFoj3WCbw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.5 h1:Cx1M/UUgYu9UCQnIMKaOhkVaFvLy1HneD6T4sS/DlKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.5/go.mod h1:fTRNLgrTvPpEzGqc9QkeO4hu/3ng+mdtUbL8shUwXz4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.5 h1:IM2yO5Dd9bzCmYEvLU6Di5kduRKh4O93TjrZ47hxLhQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.5/go.mod h1:0nXagJIQFWms6GJ1jvPJLwr8r3hN6f+kTwt17Q2NrPQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.2 h1:HNAbIp6VXmtKR+JuDmywGcRc3kYoIGT9y4a2Zg9bSTQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.2/go.mod h1:6VSEglrPCTx7gi7Z7l/CtqSgbnFr1N6UJ6+Ik+vjuEo= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.3 h1:z6lajFT/qGlLRB/I8V5CCklqSuWZKUkdwRAn9leIkiQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.3/go.mod h1:BnyjuIX0l+KXJVl2o9Ki3Zf0M4pA2hQYopFCRUj9ADU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.1 h1:8yI3jK5JZ310S8RpgdZdzwvlvBu3QbG8DP7Be/xJ6yo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.1/go.mod h1:HPzXfFgrLd02lYpcFYdDz5xZs94LOb+lWlvbAGaeMsk= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.1 h1:3kWmIg5iiWPMBJyq/I55Fki5fyfoMtrn/SkUIpxPwHQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.1/go.mod h1:yi0b3Qez6YamRVJ+Rbi19IgvjfjPODgVRhkWA6RTMUM= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= diff --git a/pkg/S3/s3.go b/pkg/S3/s3.go index 12f23cb..629d47c 100644 --- a/pkg/S3/s3.go +++ b/pkg/S3/s3.go @@ -1,16 +1,48 @@ package S3 import ( + "context" "fmt" "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go-v2/aws" + awsConfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" ) +// Получаем S3 клиент +func getS3Client() (*s3.Client, error) { + cfg, err := awsConfig.LoadDefaultConfig(context.TODO(), + awsConfig.WithRegion("ru-central1"), + awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( + "TJ946G2S1Z5FEI3I7DQQ", + "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", + "", + )), + awsConfig.WithEndpointResolver(aws.EndpointResolverFunc( + func(service, region string) (aws.Endpoint, error) { + return aws.Endpoint{ + URL: "https://s3.regru.cloud", + SigningRegion: "ru-central1", + Source: aws.EndpointSourceCustom, + }, nil + }, + )), + ) + if err != nil { + return nil, err + } + + // Создаем клиент с кастомным эндпоинтом + client := s3.NewFromConfig(cfg, func(o *s3.Options) { + o.UsePathStyle = true // Важно для некоторых S3 провайдеров + }) + + return client, nil +} + func DeleteFromS3(imageURL string) error { const op = "s3.DeleteFromS3" @@ -21,20 +53,12 @@ func DeleteFromS3(imageURL string) error { bucket := parts[0] key := parts[1] - sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String("ru-central1"), - Endpoint: aws.String("https://s3.regru.cloud"), - S3ForcePathStyle: aws.Bool(true), - Credentials: credentials.NewStaticCredentials( - "TJ946G2S1Z5FEI3I7DQQ", - "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", - "", - ), - })) + client, err := getS3Client() + if err != nil { + return fmt.Errorf("%s: failed to create S3 client: %w", op, err) + } - svc := s3.New(sess) - - _, err := svc.DeleteObject(&s3.DeleteObjectInput{ + _, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }) @@ -42,69 +66,56 @@ func DeleteFromS3(imageURL string) error { return fmt.Errorf("%s: failed to delete object from S3: %w", op, err) } - err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + // Ждем пока объект исчезнет + waiter := s3.NewObjectNotExistsWaiter(client) + err = waiter.Wait(context.TODO(), &s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), - }) + }, 30*time.Second) if err != nil { return fmt.Errorf("%s: failed to wait for object deletion: %w", op, err) } return nil } + func GeneratePresignedUploadURL(userID int, filename string) (string, string, error) { const op = "s3.GeneratePresignedUploadURL" - sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String("ru-central1"), - Endpoint: aws.String("https://s3.regru.cloud"), - Credentials: credentials.NewStaticCredentials( - "TJ946G2S1Z5FEI3I7DQQ", - "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", - "", - ), - })) + client, err := getS3Client() + if err != nil { + return "", "", fmt.Errorf("%s: failed to create S3 client: %w", op, err) + } - svc := s3.New(sess) - - // Генерируем уникальный ключ с userID для безопасности uniqueKey := fmt.Sprintf("temp/%d/%d_%s", userID, time.Now().UnixNano(), filename) - req, _ := svc.PutObjectRequest(&s3.PutObjectInput{ - Bucket: aws.String("tailly"), - Key: aws.String(uniqueKey), - ContentLength: aws.Int64(10 * 1024 * 1024), // Макс 10MB - }) + presignClient := s3.NewPresignClient(client) - req.HTTPRequest.Header.Set("Access-Control-Allow-Origin", "https://tailly.ru") - req.HTTPRequest.Header.Set("Access-Control-Allow-Methods", "PUT, OPTIONS") - req.HTTPRequest.Header.Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, x-amz-date") - // URL действителен 15 минут - url, err := req.Presign(15 * time.Minute) + result, err := presignClient.PresignPutObject(context.TODO(), + &s3.PutObjectInput{ + Bucket: aws.String("tailly"), + Key: aws.String(uniqueKey), + ContentLength: aws.Int64(10 * 1024 * 1024), + }, + s3.WithPresignExpires(15*time.Minute), + ) if err != nil { return "", "", fmt.Errorf("%s: failed to generate presigned URL: %w", op, err) } - return url, uniqueKey, nil + return result.URL, uniqueKey, nil } func MoveFile(sourceKey, destinationKey string) error { const op = "s3.MoveFile" - sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String("ru-central1"), - Endpoint: aws.String("https://s3.regru.cloud"), - Credentials: credentials.NewStaticCredentials( - "TJ946G2S1Z5FEI3I7DQQ", - "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", - "", - ), - })) - - svc := s3.New(sess) + client, err := getS3Client() + if err != nil { + return fmt.Errorf("%s: failed to create S3 client: %w", op, err) + } // Копируем файл - _, err := svc.CopyObject(&s3.CopyObjectInput{ + _, err = client.CopyObject(context.TODO(), &s3.CopyObjectInput{ Bucket: aws.String("tailly"), CopySource: aws.String(fmt.Sprintf("tailly/%s", sourceKey)), Key: aws.String(destinationKey), @@ -120,19 +131,12 @@ func MoveFile(sourceKey, destinationKey string) error { func DeleteTempFile(key string) error { const op = "s3.DeleteTempFile" - sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String("ru-central1"), - Endpoint: aws.String("https://s3.regru.cloud"), - Credentials: credentials.NewStaticCredentials( - "TJ946G2S1Z5FEI3I7DQQ", - "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", - "", - ), - })) + client, err := getS3Client() + if err != nil { + return fmt.Errorf("%s: failed to create S3 client: %w", op, err) + } - svc := s3.New(sess) - - _, err := svc.DeleteObject(&s3.DeleteObjectInput{ + _, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ Bucket: aws.String("tailly"), Key: aws.String(key), })