From 0647b620487d89adfc504d541c3b2de6fa821fb2 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 28 Aug 2025 13:59:48 +0300 Subject: [PATCH] v0.0.26 --- go.mod | 18 -- internal/http/graph/generated.go | 389 +------------------------- internal/http/graph/models_gen.go | 11 - internal/http/graph/post_resolvers.go | 24 +- internal/http/graph/schema.graphql | 22 +- internal/service/post_service.go | 61 ---- pkg/S3/s3.go | 90 ++---- pkg/moderation/moderation.go | 13 +- 8 files changed, 40 insertions(+), 588 deletions(-) diff --git a/go.mod b/go.mod index 0cadd5d..08e11c9 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,6 @@ 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 @@ -26,20 +22,6 @@ 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/internal/http/graph/generated.go b/internal/http/graph/generated.go index 66ef022..799118d 100644 --- a/internal/http/graph/generated.go +++ b/internal/http/graph/generated.go @@ -140,7 +140,7 @@ type ComplexityRoot struct { ConfirmEmail func(childComplexity int, token string) int CreateChat func(childComplexity int, user1Id int, user2Id int) int CreateComment func(childComplexity int, postID int, content string) int - CreatePost func(childComplexity int, input CreatePostInput) int + CreatePost func(childComplexity int, title string, content string) int DeletePost func(childComplexity int, id int) int FollowUser func(childComplexity int, followingID int) int LikePost func(childComplexity int, postID int) int @@ -180,7 +180,6 @@ type ComplexityRoot struct { Query struct { Comments func(childComplexity int, postID int) int - GenerateUploadURL func(childComplexity int, filename string) int GetChat func(childComplexity int, user1Id int, user2Id int) int GetChatMessages func(childComplexity int, chatID int, limit int, offset int) int GetFollowers func(childComplexity int, userID int, limit *int, offset *int) int @@ -235,12 +234,6 @@ type ComplexityRoot struct { Success func(childComplexity int) int } - UploadURL struct { - ExpiresAt func(childComplexity int) int - Key func(childComplexity int) int - URL func(childComplexity int) int - } - User struct { Avatar func(childComplexity int) int CreatedAt func(childComplexity int) int @@ -284,7 +277,7 @@ type MutationResolver interface { Register(ctx context.Context, input domain.RegisterInput) (*domain.User, error) Login(ctx context.Context, input domain.LoginInput) (*domain.Tokens, error) RefreshTokens(ctx context.Context, refreshToken string) (*domain.Tokens, error) - CreatePost(ctx context.Context, input CreatePostInput) (*domain.Post, error) + CreatePost(ctx context.Context, title string, content string) (*domain.Post, error) CreateComment(ctx context.Context, postID int, content string) (*domain.Comment, error) LikePost(ctx context.Context, postID int) (*domain.Like, error) UnlikePost(ctx context.Context, postID int) (bool, error) @@ -317,7 +310,6 @@ type QueryResolver interface { Post(ctx context.Context, id int) (*domain.Post, error) Posts(ctx context.Context) ([]*domain.Post, error) PostsPaginated(ctx context.Context, limit int, offset int) ([]*domain.Post, error) - GenerateUploadURL(ctx context.Context, filename string) (*UploadURL, error) GetUserPosts(ctx context.Context, userID int) ([]*domain.Post, error) User(ctx context.Context, id int) (*domain.User, error) Users(ctx context.Context) ([]*domain.User, error) @@ -741,7 +733,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Mutation.CreatePost(childComplexity, args["input"].(CreatePostInput)), true + return e.complexity.Mutation.CreatePost(childComplexity, args["title"].(string), args["content"].(string)), true case "Mutation.deletePost": if e.complexity.Mutation.DeletePost == nil { @@ -1028,18 +1020,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Query.Comments(childComplexity, args["postID"].(int)), true - case "Query.generateUploadURL": - if e.complexity.Query.GenerateUploadURL == nil { - break - } - - args, err := ec.field_Query_generateUploadURL_args(ctx, rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.GenerateUploadURL(childComplexity, args["filename"].(string)), true - case "Query.getChat": if e.complexity.Query.GetChat == nil { break @@ -1369,27 +1349,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.UnfollowResult.Success(childComplexity), true - case "UploadURL.expiresAt": - if e.complexity.UploadURL.ExpiresAt == nil { - break - } - - return e.complexity.UploadURL.ExpiresAt(childComplexity), true - - case "UploadURL.key": - if e.complexity.UploadURL.Key == nil { - break - } - - return e.complexity.UploadURL.Key(childComplexity), true - - case "UploadURL.url": - if e.complexity.UploadURL.URL == nil { - break - } - - return e.complexity.UploadURL.URL(childComplexity), true - case "User.avatar": if e.complexity.User.Avatar == nil { break @@ -1509,7 +1468,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { opCtx := graphql.GetOperationContext(ctx) ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( - ec.unmarshalInputCreatePostInput, ec.unmarshalInputLoginInput, ec.unmarshalInputRegisterInput, ) @@ -1727,11 +1685,16 @@ func (ec *executionContext) field_Mutation_createComment_args(ctx context.Contex func (ec *executionContext) field_Mutation_createPost_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := processArgField(ctx, rawArgs, "input", ec.unmarshalNCreatePostInput2tailly_back_v2ᚋinternalᚋhttpᚋgraphᚐCreatePostInput) + arg0, err := processArgField(ctx, rawArgs, "title", ec.unmarshalNString2string) if err != nil { return nil, err } - args["input"] = arg0 + args["title"] = arg0 + arg1, err := processArgField(ctx, rawArgs, "content", ec.unmarshalNString2string) + if err != nil { + return nil, err + } + args["content"] = arg1 return args, nil } @@ -1936,17 +1899,6 @@ func (ec *executionContext) field_Query_comments_args(ctx context.Context, rawAr return args, nil } -func (ec *executionContext) field_Query_generateUploadURL_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { - var err error - args := map[string]any{} - arg0, err := processArgField(ctx, rawArgs, "filename", ec.unmarshalNString2string) - if err != nil { - return nil, err - } - args["filename"] = arg0 - return args, nil -} - func (ec *executionContext) field_Query_getChatMessages_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -4567,7 +4519,7 @@ func (ec *executionContext) _Mutation_createPost(ctx context.Context, field grap }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().CreatePost(rctx, fc.Args["input"].(CreatePostInput)) + return ec.resolvers.Mutation().CreatePost(rctx, fc.Args["title"].(string), fc.Args["content"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -6589,69 +6541,6 @@ func (ec *executionContext) fieldContext_Query_postsPaginated(ctx context.Contex return fc, nil } -func (ec *executionContext) _Query_generateUploadURL(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_generateUploadURL(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GenerateUploadURL(rctx, fc.Args["filename"].(string)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(*UploadURL) - fc.Result = res - return ec.marshalNUploadURL2ᚖtailly_back_v2ᚋinternalᚋhttpᚋgraphᚐUploadURL(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_generateUploadURL(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Query", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "url": - return ec.fieldContext_UploadURL_url(ctx, field) - case "key": - return ec.fieldContext_UploadURL_key(ctx, field) - case "expiresAt": - return ec.fieldContext_UploadURL_expiresAt(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type UploadURL", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_generateUploadURL_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Query_getUserPosts(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_getUserPosts(ctx, field) if err != nil { @@ -8632,138 +8521,6 @@ func (ec *executionContext) fieldContext_UnfollowResult_message(_ context.Contex return fc, nil } -func (ec *executionContext) _UploadURL_url(ctx context.Context, field graphql.CollectedField, obj *UploadURL) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_UploadURL_url(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return obj.URL, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_UploadURL_url(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "UploadURL", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _UploadURL_key(ctx context.Context, field graphql.CollectedField, obj *UploadURL) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_UploadURL_key(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return obj.Key, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_UploadURL_key(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "UploadURL", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _UploadURL_expiresAt(ctx context.Context, field graphql.CollectedField, obj *UploadURL) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_UploadURL_expiresAt(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return obj.ExpiresAt, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_UploadURL_expiresAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "UploadURL", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *domain.User) (ret graphql.Marshaler) { fc, err := ec.fieldContext_User_id(ctx, field) if err != nil { @@ -11348,40 +11105,6 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field // region **************************** input.gotpl ***************************** -func (ec *executionContext) unmarshalInputCreatePostInput(ctx context.Context, obj any) (CreatePostInput, error) { - var it CreatePostInput - asMap := map[string]any{} - for k, v := range obj.(map[string]any) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"title", "s3TempKey"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "title": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title")) - data, err := ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - it.Title = data - case "s3TempKey": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("s3TempKey")) - data, err := ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - it.S3TempKey = data - } - } - - return it, nil -} - func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj any) (domain.LoginInput, error) { var it domain.LoginInput asMap := map[string]any{} @@ -13073,28 +12796,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) - case "generateUploadURL": - field := field - - innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_generateUploadURL(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&fs.Invalids, 1) - } - return res - } - - rrm := func(ctx context.Context) graphql.Marshaler { - return ec.OperationContext.RootResolverMiddleware(ctx, - func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "getUserPosts": field := field @@ -13810,55 +13511,6 @@ func (ec *executionContext) _UnfollowResult(ctx context.Context, sel ast.Selecti return out } -var uploadURLImplementors = []string{"UploadURL"} - -func (ec *executionContext) _UploadURL(ctx context.Context, sel ast.SelectionSet, obj *UploadURL) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, uploadURLImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("UploadURL") - case "url": - out.Values[i] = ec._UploadURL_url(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "key": - out.Values[i] = ec._UploadURL_key(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "expiresAt": - out.Values[i] = ec._UploadURL_expiresAt(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - var userImplementors = []string{"User"} func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *domain.User) graphql.Marshaler { @@ -14701,11 +14353,6 @@ func (ec *executionContext) marshalNComment2ᚖtailly_back_v2ᚋinternalᚋdomai return ec._Comment(ctx, sel, v) } -func (ec *executionContext) unmarshalNCreatePostInput2tailly_back_v2ᚋinternalᚋhttpᚋgraphᚐCreatePostInput(ctx context.Context, v any) (CreatePostInput, error) { - res, err := ec.unmarshalInputCreatePostInput(ctx, v) - return res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) marshalNDevice2tailly_back_v2ᚋinternalᚋdomainᚐDevice(ctx context.Context, sel ast.SelectionSet, v domain.Device) graphql.Marshaler { return ec._Device(ctx, sel, &v) } @@ -15260,20 +14907,6 @@ func (ec *executionContext) marshalNUnfollowResult2ᚖtailly_back_v2ᚋinternal return ec._UnfollowResult(ctx, sel, v) } -func (ec *executionContext) marshalNUploadURL2tailly_back_v2ᚋinternalᚋhttpᚋgraphᚐUploadURL(ctx context.Context, sel ast.SelectionSet, v UploadURL) graphql.Marshaler { - return ec._UploadURL(ctx, sel, &v) -} - -func (ec *executionContext) marshalNUploadURL2ᚖtailly_back_v2ᚋinternalᚋhttpᚋgraphᚐUploadURL(ctx context.Context, sel ast.SelectionSet, v *UploadURL) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "the requested element is null which the schema does not allow") - } - return graphql.Null - } - return ec._UploadURL(ctx, sel, v) -} - func (ec *executionContext) marshalNUser2tailly_back_v2ᚋinternalᚋdomainᚐUser(ctx context.Context, sel ast.SelectionSet, v domain.User) graphql.Marshaler { return ec._User(ctx, sel, &v) } diff --git a/internal/http/graph/models_gen.go b/internal/http/graph/models_gen.go index b3e174a..03e4f88 100644 --- a/internal/http/graph/models_gen.go +++ b/internal/http/graph/models_gen.go @@ -9,11 +9,6 @@ import ( "strconv" ) -type CreatePostInput struct { - Title string `json:"title"` - S3TempKey string `json:"s3TempKey"` -} - type FollowResult struct { Success bool `json:"success"` Message string `json:"message"` @@ -78,12 +73,6 @@ type UnfollowResult struct { Message string `json:"message"` } -type UploadURL struct { - URL string `json:"url"` - Key string `json:"key"` - ExpiresAt string `json:"expiresAt"` -} - type MessageStatus string const ( diff --git a/internal/http/graph/post_resolvers.go b/internal/http/graph/post_resolvers.go index e280966..79a2734 100644 --- a/internal/http/graph/post_resolvers.go +++ b/internal/http/graph/post_resolvers.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "tailly_back_v2/internal/domain" - "tailly_back_v2/pkg/S3" "time" ) @@ -125,38 +124,19 @@ func (r *mutationResolver) UnlikePost(ctx context.Context, postID int) (bool, er } // CreatePost is the resolver for the createPost field. -func (r *mutationResolver) CreatePost(ctx context.Context, input CreatePostInput) (*domain.Post, error) { +func (r *mutationResolver) CreatePost(ctx context.Context, title string, content string) (*domain.Post, error) { userID, err := getUserIDFromContext(ctx) if err != nil { return nil, fmt.Errorf("unauthorized: %w", err) } - post, err := r.Services.Post.CreateWithS3Upload(ctx, userID, input.Title, input.S3TempKey) + post, err := r.Services.Post.Create(ctx, userID, title, content) if err != nil { return nil, fmt.Errorf("failed to create post: %w", err) } return post, nil } -// GenerateUploadURL is the resolver for the generateUploadURL field. -func (r *queryResolver) GenerateUploadURL(ctx context.Context, filename string) (*UploadURL, error) { - userID, err := getUserIDFromContext(ctx) - if err != nil { - return nil, fmt.Errorf("unauthorized: %w", err) - } - - url, key, err := S3.GeneratePresignedUploadURL(userID, filename) - if err != nil { - return nil, fmt.Errorf("failed to generate upload URL: %w", err) - } - - return &UploadURL{ - URL: url, - Key: key, - ExpiresAt: time.Now().Add(15 * time.Minute).Format(time.RFC3339), - }, nil -} - // DeletePost is the resolver for the deletePost field. func (r *mutationResolver) DeletePost(ctx context.Context, id int) (bool, error) { userID, err := getUserIDFromContext(ctx) diff --git a/internal/http/graph/schema.graphql b/internal/http/graph/schema.graphql index 6cb7a6e..2828a13 100644 --- a/internal/http/graph/schema.graphql +++ b/internal/http/graph/schema.graphql @@ -170,26 +170,12 @@ type MarkNotificationReadResult { message: String! } -scalar Upload - -input CreatePostInput { - title: String! - s3TempKey: String! -} - -type UploadURL { - url: String! - key: String! - expiresAt: String! -} - # Запросы (получение данных) type Query { me: User! # Получить текущего пользователя post(id: Int!): Post! # Получить пост по ID posts: [Post!]! # Получить все посты postsPaginated(limit: Int!, offset: Int!): [Post!]! - generateUploadURL(filename: String!): UploadURL! getUserPosts(userId: Int!): [Post!]! user(id: Int!): User! # Получить пользователя по ID users: [User!]! @@ -200,14 +186,19 @@ type Query { comments(postID: Int!): [Comment!]! # Получить количество подписчиков getFollowersCount(userId: Int!): Int! + # Получить количество подписок getFollowingCount(userId: Int!): Int! + # Проверить подписку isFollowing(followingId: Int!): Boolean! + # Получить список подписчиков getFollowers(userId: Int!, limit: Int = 20, offset: Int = 0): FollowersResponse! + # Получить список подписок getFollowing(userId: Int!, limit: Int = 20, offset: Int = 0): FollowingResponse! + # Получить уведомления о подписках getSubscriptionNotifications( unreadOnly: Boolean = false @@ -228,7 +219,8 @@ type Mutation { refreshTokens(refreshToken: String!): Tokens! # Создание поста - createPost(input: CreatePostInput!): Post! + createPost(title: String!, content: String!): Post! + # Создание комментария createComment(postId: Int!, content: String!): Comment! diff --git a/internal/service/post_service.go b/internal/service/post_service.go index 960555b..d9f0e12 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -32,7 +32,6 @@ type PostService interface { Update(ctx context.Context, id int, title, content string) (*domain.Post, error) Delete(ctx context.Context, id int) error GetPaginated(ctx context.Context, limit, offset int) ([]*domain.Post, error) - CreateWithS3Upload(ctx context.Context, authorID int, title, s3TempKey string) (*domain.Post, error) } // Реализация сервиса постов @@ -45,66 +44,6 @@ func NewPostService(postRepo repository.PostRepository) PostService { return &postService{postRepo: postRepo} } -func (s *postService) CreateWithS3Upload(ctx context.Context, authorID int, title, s3Key string) (*domain.Post, error) { - const op = "service/postService.CreateWithS3Upload" - - // Валидация - if s3Key == "" { - return nil, errors.New("S3 key cannot be empty") - } - - // Проверяем что ключ принадлежит пользователю и находится в правильной папке - if !isValidUserPostKey(authorID, s3Key) { - return nil, errors.New("invalid S3 key for user") - } - - // Формируем URL для модерации - imageURL := fmt.Sprintf("https://s3.regru.cloud/tailly/%s", s3Key) - - // Модерация изображения по URL - modClient, err := moderation.NewModerationClient("tailly_censor:50051") - if err != nil { - return nil, fmt.Errorf("%s: failed to create moderation client: %w", op, err) - } - defer modClient.Close() - - allowed, err := modClient.CheckImageURL(ctx, imageURL) - if err != nil { - // Удаляем файл при ошибке модерации - S3.DeleteFromS3(imageURL) - return nil, fmt.Errorf("%s: image moderation failed: %w", op, err) - } - if !allowed { - // Удаляем файл если не прошло модерацию - S3.DeleteFromS3(imageURL) - return nil, errors.New("image rejected by moderation service") - } - - // Создаем пост с уже готовым URL - post := &domain.Post{ - Title: title, - Content: imageURL, - AuthorID: authorID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if err := s.postRepo.Create(ctx, post); err != nil { - // Если не удалось сохранить пост, удаляем файл из S3 - S3.DeleteFromS3(imageURL) - return nil, fmt.Errorf("%s: failed to save post: %w", op, err) - } - - return post, nil -} - -// Новая функция проверки ключа -func isValidUserPostKey(userID int, key string) bool { - // Проверяем что ключ принадлежит пользователю и находится в папке posts - // Формат: posts/{userID}/{timestamp}_{filename} - return strings.HasPrefix(key, fmt.Sprintf("posts/%d/", userID)) -} - // Создание нового поста func (s *postService) Create(ctx context.Context, authorID int, title, content string) (*domain.Post, error) { const op = "service/postService.Create" diff --git a/pkg/S3/s3.go b/pkg/S3/s3.go index baa93a2..930dd85 100644 --- a/pkg/S3/s3.go +++ b/pkg/S3/s3.go @@ -1,40 +1,15 @@ package S3 import ( - "context" "fmt" "strings" - "time" - "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" + "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" ) -// Получаем S3 клиент -func getS3Client() (*s3.Client, error) { - cfg, err := awsConfig.LoadDefaultConfig(context.TODO(), - awsConfig.WithRegion("ru-central1"), - awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( - "TJ946G2S1Z5FEI3I7DQQ", - "C2H2aITHRDpek8H921yhnrINZwDoADsjW3F6HURl", - "", - )), - ) - if err != nil { - return nil, err - } - - // Используем virtual-hosted-style endpoint - client := s3.NewFromConfig(cfg, func(o *s3.Options) { - o.BaseEndpoint = aws.String("https://s3.regru.cloud") - o.UsePathStyle = false // Отключаем path-style - }) - - return client, nil -} - func DeleteFromS3(imageURL string) error { const op = "s3.DeleteFromS3" @@ -45,12 +20,20 @@ func DeleteFromS3(imageURL string) error { bucket := parts[0] key := parts[1] - client, err := getS3Client() - if err != nil { - return fmt.Errorf("%s: failed to create S3 client: %w", op, err) - } + 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", + "", + ), + })) - _, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ + svc := s3.New(sess) + + _, err := svc.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }) @@ -58,48 +41,13 @@ func DeleteFromS3(imageURL string) error { return fmt.Errorf("%s: failed to delete object from S3: %w", op, err) } - // Ждем пока объект исчезнет - waiter := s3.NewObjectNotExistsWaiter(client) - err = waiter.Wait(context.TODO(), &s3.HeadObjectInput{ + err = svc.WaitUntilObjectNotExists(&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" - - client, err := getS3Client() - if err != nil { - return "", "", fmt.Errorf("%s: failed to create S3 client: %w", op, err) - } - - uniqueKey := fmt.Sprintf("posts/%d/%d_%s", userID, time.Now().UnixNano(), filename) - - presignClient := s3.NewPresignClient(client) - - result, err := presignClient.PresignPutObject(context.TODO(), - &s3.PutObjectInput{ - Bucket: aws.String("tailly"), - Key: aws.String(uniqueKey), - ContentLength: aws.Int64(10 * 1024 * 1024), - // Добавляем CORS headers через метаданные - Metadata: map[string]string{ - "Access-Control-Allow-Origin": "https://tailly.ru", - "Access-Control-Allow-Methods": "PUT, POST, DELETE", - "Access-Control-Allow-Headers": "*", - }, - }, - s3.WithPresignExpires(15*time.Minute), - ) - if err != nil { - return "", "", fmt.Errorf("%s: failed to generate presigned URL: %w", op, err) - } - - return result.URL, uniqueKey, nil -} diff --git a/pkg/moderation/moderation.go b/pkg/moderation/moderation.go index e5b3f6f..30c22f8 100644 --- a/pkg/moderation/moderation.go +++ b/pkg/moderation/moderation.go @@ -3,9 +3,8 @@ package moderation import ( "context" - pb "tailly_back_v2/pkg/moderation/proto" - "google.golang.org/grpc" + pb "tailly_back_v2/pkg/moderation/proto" ) type ModerationClient struct { @@ -36,16 +35,6 @@ func (c *ModerationClient) CheckImage(ctx context.Context, imageData []byte) (bo return resp.OverallDecision == "allowed", nil } -func (c *ModerationClient) CheckImageURL(ctx context.Context, imageURL string) (bool, error) { - resp, err := c.client.CheckImage(ctx, &pb.ImageRequest{ - ImageUrl: imageURL, // ← Передаем URL вместо данных! - }) - if err != nil { - return false, err - } - return resp.OverallDecision == "allowed", nil -} - func (c *ModerationClient) Close() { c.conn.Close() }