This commit is contained in:
parent
36b559d486
commit
470b0b5342
1
go.mod
1
go.mod
@ -36,6 +36,7 @@ require (
|
|||||||
github.com/prometheus/common v0.65.0 // indirect
|
github.com/prometheus/common v0.65.0 // indirect
|
||||||
github.com/prometheus/procfs v0.17.0 // indirect
|
github.com/prometheus/procfs v0.17.0 // indirect
|
||||||
github.com/sosodev/duration v1.3.1 // indirect
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
|||||||
1
go.sum
1
go.sum
@ -79,6 +79,7 @@ github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NF
|
|||||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
|
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
|
||||||
|
|||||||
@ -106,7 +106,7 @@ type ComplexityRoot struct {
|
|||||||
ConfirmEmail func(childComplexity int, token string) int
|
ConfirmEmail func(childComplexity int, token string) int
|
||||||
CreateChat func(childComplexity int, user1Id int, user2Id int) int
|
CreateChat func(childComplexity int, user1Id int, user2Id int) int
|
||||||
CreateComment func(childComplexity int, postID int, content string) 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
|
DeletePost func(childComplexity int, id int) int
|
||||||
LikePost func(childComplexity int, postID int) int
|
LikePost func(childComplexity int, postID int) int
|
||||||
Login func(childComplexity int, input domain.LoginInput) int
|
Login func(childComplexity int, input domain.LoginInput) int
|
||||||
@ -206,7 +206,7 @@ type MutationResolver interface {
|
|||||||
Register(ctx context.Context, input domain.RegisterInput) (*domain.User, error)
|
Register(ctx context.Context, input domain.RegisterInput) (*domain.User, error)
|
||||||
Login(ctx context.Context, input domain.LoginInput) (*domain.Tokens, error)
|
Login(ctx context.Context, input domain.LoginInput) (*domain.Tokens, error)
|
||||||
RefreshTokens(ctx context.Context, refreshToken string) (*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)
|
CreateComment(ctx context.Context, postID int, content string) (*domain.Comment, error)
|
||||||
LikePost(ctx context.Context, postID int) (*domain.Like, error)
|
LikePost(ctx context.Context, postID int) (*domain.Like, error)
|
||||||
UnlikePost(ctx context.Context, postID int) (bool, error)
|
UnlikePost(ctx context.Context, postID int) (bool, error)
|
||||||
@ -534,7 +534,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
return 0, false
|
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":
|
case "Mutation.deletePost":
|
||||||
if e.complexity.Mutation.DeletePost == nil {
|
if e.complexity.Mutation.DeletePost == nil {
|
||||||
@ -1003,7 +1003,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
|||||||
opCtx := graphql.GetOperationContext(ctx)
|
opCtx := graphql.GetOperationContext(ctx)
|
||||||
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
|
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
|
||||||
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
|
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
|
||||||
ec.unmarshalInputCreatePostInput,
|
|
||||||
ec.unmarshalInputLoginInput,
|
ec.unmarshalInputLoginInput,
|
||||||
ec.unmarshalInputRegisterInput,
|
ec.unmarshalInputRegisterInput,
|
||||||
)
|
)
|
||||||
@ -1221,11 +1220,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) {
|
func (ec *executionContext) field_Mutation_createPost_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]any{}
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3081,7 +3085,7 @@ func (ec *executionContext) _Mutation_createPost(ctx context.Context, field grap
|
|||||||
}()
|
}()
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
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 {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
@ -8125,40 +8129,6 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field
|
|||||||
|
|
||||||
// region **************************** input.gotpl *****************************
|
// 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", "image"}
|
|
||||||
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 "image":
|
|
||||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("image"))
|
|
||||||
data, err := ec.unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, v)
|
|
||||||
if err != nil {
|
|
||||||
return it, err
|
|
||||||
}
|
|
||||||
it.Image = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return it, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj any) (domain.LoginInput, error) {
|
func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj any) (domain.LoginInput, error) {
|
||||||
var it domain.LoginInput
|
var it domain.LoginInput
|
||||||
asMap := map[string]any{}
|
asMap := map[string]any{}
|
||||||
@ -10570,11 +10540,6 @@ func (ec *executionContext) marshalNComment2ᚖtailly_back_v2ᚋinternalᚋdomai
|
|||||||
return ec._Comment(ctx, sel, v)
|
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 {
|
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)
|
return ec._Device(ctx, sel, &v)
|
||||||
}
|
}
|
||||||
@ -10883,22 +10848,6 @@ func (ec *executionContext) marshalNTokens2ᚖtailly_back_v2ᚋinternalᚋdomain
|
|||||||
return ec._Tokens(ctx, sel, v)
|
return ec._Tokens(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, v any) (graphql.Upload, error) {
|
|
||||||
res, err := graphql.UnmarshalUpload(v)
|
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) marshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, sel ast.SelectionSet, v graphql.Upload) graphql.Marshaler {
|
|
||||||
_ = sel
|
|
||||||
res := graphql.MarshalUpload(v)
|
|
||||||
if res == graphql.Null {
|
|
||||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
|
||||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) marshalNUser2tailly_back_v2ᚋinternalᚋdomainᚐUser(ctx context.Context, sel ast.SelectionSet, v domain.User) graphql.Marshaler {
|
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)
|
return ec._User(ctx, sel, &v)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,15 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreatePostInput struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Image graphql.Upload `json:"image"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation struct {
|
type Mutation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package graph
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"tailly_back_v2/internal/domain"
|
"tailly_back_v2/internal/domain"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -125,35 +124,13 @@ func (r *mutationResolver) UnlikePost(ctx context.Context, postID int) (bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePost is the resolver for the createPost field.
|
// 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)
|
userID, err := getUserIDFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unauthorized: %w", err)
|
return nil, fmt.Errorf("unauthorized: %w", err)
|
||||||
}
|
}
|
||||||
contentType := input.Image.ContentType
|
|
||||||
size := input.Image.Size
|
|
||||||
|
|
||||||
if size > 10_000_000 {
|
post, err := r.Services.Post.Create(ctx, userID, title, content)
|
||||||
return nil, fmt.Errorf("image too large (max 10MB), got %d bytes", size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем поддерживаемые форматы
|
|
||||||
supportedTypes := map[string]bool{
|
|
||||||
"image/jpeg": true,
|
|
||||||
"image/png": true,
|
|
||||||
"image/webp": true,
|
|
||||||
}
|
|
||||||
if !supportedTypes[contentType] {
|
|
||||||
return nil, fmt.Errorf("unsupported image format: %s", contentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Читаем бинарные данные
|
|
||||||
imageData, err := io.ReadAll(input.Image.File)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
post, err := r.Services.Post.Create(ctx, userID, input.Title, imageData, contentType, size)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create post: %w", err)
|
return nil, fmt.Errorf("failed to create post: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,14 +119,6 @@ type Query {
|
|||||||
comments(postID: Int!): [Comment!]!
|
comments(postID: Int!): [Comment!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
scalar Upload
|
|
||||||
|
|
||||||
input CreatePostInput {
|
|
||||||
title: String!
|
|
||||||
image: Upload!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Мутации (изменение данных)
|
# Мутации (изменение данных)
|
||||||
type Mutation {
|
type Mutation {
|
||||||
# Регистрация нового пользователя
|
# Регистрация нового пользователя
|
||||||
@ -139,7 +131,7 @@ type Mutation {
|
|||||||
refreshTokens(refreshToken: String!): Tokens!
|
refreshTokens(refreshToken: String!): Tokens!
|
||||||
|
|
||||||
# Создание поста
|
# Создание поста
|
||||||
createPost(input: CreatePostInput!): Post!
|
createPost(title: String!, content: String!): Post!
|
||||||
|
|
||||||
# Создание комментария
|
# Создание комментария
|
||||||
createComment(postId: Int!, content: String!): Comment!
|
createComment(postId: Int!, content: String!): Comment!
|
||||||
|
|||||||
@ -3,9 +3,12 @@ package service
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
"strings"
|
"strings"
|
||||||
"tailly_back_v2/internal/domain"
|
"tailly_back_v2/internal/domain"
|
||||||
"tailly_back_v2/internal/repository"
|
"tailly_back_v2/internal/repository"
|
||||||
@ -21,7 +24,7 @@ import (
|
|||||||
|
|
||||||
// Интерфейс сервиса постов
|
// Интерфейс сервиса постов
|
||||||
type PostService interface {
|
type PostService interface {
|
||||||
Create(ctx context.Context, authorID int, title string, imageData []byte, contentType string, size int64) (*domain.Post, error)
|
Create(ctx context.Context, authorID int, title, content string) (*domain.Post, error)
|
||||||
GetByID(ctx context.Context, id int) (*domain.Post, error)
|
GetByID(ctx context.Context, id int) (*domain.Post, error)
|
||||||
GetAll(ctx context.Context) ([]*domain.Post, error)
|
GetAll(ctx context.Context) ([]*domain.Post, error)
|
||||||
GetByAuthorID(ctx context.Context, authorID int) ([]*domain.Post, error)
|
GetByAuthorID(ctx context.Context, authorID int) ([]*domain.Post, error)
|
||||||
@ -40,25 +43,63 @@ func NewPostService(postRepo repository.PostRepository) PostService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Создание нового поста
|
// Создание нового поста
|
||||||
func (s *postService) Create(ctx context.Context, authorID int, title string, imageData []byte, contentType string, size int64) (*domain.Post, error) {
|
func (s *postService) Create(ctx context.Context, authorID int, title, content string) (*domain.Post, error) {
|
||||||
const op = "service/postService.Create"
|
const op = "service/postService.Create"
|
||||||
|
|
||||||
// Валидация
|
// Валидация
|
||||||
if len(imageData) == 0 {
|
if content == "" {
|
||||||
return nil, errors.New("image data cannot be empty")
|
return nil, errors.New("post content cannot be empty")
|
||||||
}
|
}
|
||||||
if len(imageData) > 10_000_000 {
|
|
||||||
|
// Определяем формат и извлекаем данные
|
||||||
|
var base64Data, ext, contentType string
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(content, "data:image/jpeg;base64,"):
|
||||||
|
base64Data = strings.TrimPrefix(content, "data:image/jpeg;base64,")
|
||||||
|
ext = "jpg"
|
||||||
|
contentType = "image/jpeg"
|
||||||
|
case strings.HasPrefix(content, "data:image/png;base64,"):
|
||||||
|
base64Data = strings.TrimPrefix(content, "data:image/png;base64,")
|
||||||
|
ext = "png"
|
||||||
|
contentType = "image/png"
|
||||||
|
case strings.HasPrefix(content, "data:image/webp;base64,"):
|
||||||
|
base64Data = strings.TrimPrefix(content, "data:image/webp;base64,")
|
||||||
|
ext = "webp"
|
||||||
|
contentType = "image/webp"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid image format, expected JPEG, PNG or WebP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Декодируем base64
|
||||||
|
imgData, err := base64.StdEncoding.DecodeString(base64Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to decode base64: %w", op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем размер
|
||||||
|
if len(imgData) > 10_000_000 {
|
||||||
return nil, errors.New("image too large (max 10MB)")
|
return nil, errors.New("image too large (max 10MB)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Модерация изображения - передаем БИНАРНЫЕ ДАННЫЕ
|
// Проверяем валидность изображения
|
||||||
|
switch ext {
|
||||||
|
case "jpg":
|
||||||
|
if _, err := jpeg.Decode(bytes.NewReader(imgData)); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: invalid JPEG image: %w", op, err)
|
||||||
|
}
|
||||||
|
case "png":
|
||||||
|
if _, err := png.Decode(bytes.NewReader(imgData)); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: invalid PNG image: %w", op, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Модерация изображения
|
||||||
modClient, err := moderation.NewModerationClient("tailly_censor:50051")
|
modClient, err := moderation.NewModerationClient("tailly_censor:50051")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: failed to create moderation client: %w", op, err)
|
return nil, fmt.Errorf("%s: failed to create moderation client: %w", op, err)
|
||||||
}
|
}
|
||||||
defer modClient.Close()
|
defer modClient.Close()
|
||||||
|
|
||||||
allowed, err := modClient.CheckImage(ctx, imageData) // ← Прямая передача bytes
|
allowed, err := modClient.CheckImage(ctx, imgData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: image moderation failed: %w", op, err)
|
return nil, fmt.Errorf("%s: image moderation failed: %w", op, err)
|
||||||
}
|
}
|
||||||
@ -85,7 +126,7 @@ func (s *postService) Create(ctx context.Context, authorID int, title string, im
|
|||||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||||
Bucket: aws.String("tailly"),
|
Bucket: aws.String("tailly"),
|
||||||
Key: aws.String(s3Key),
|
Key: aws.String(s3Key),
|
||||||
Body: bytes.NewReader(imageData), // ← Бинарные данные
|
Body: bytes.NewReader(imgData),
|
||||||
ContentType: aws.String(contentType),
|
ContentType: aws.String(contentType),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -109,46 +150,6 @@ func (s *postService) Create(ctx context.Context, authorID int, title string, im
|
|||||||
return post, nil
|
return post, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContentTypeAndExtension(filename string) (string, string) {
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(strings.ToLower(filename), ".jpg"), strings.HasSuffix(strings.ToLower(filename), ".jpeg"):
|
|
||||||
return "image/jpeg", "jpg"
|
|
||||||
case strings.HasSuffix(strings.ToLower(filename), ".png"):
|
|
||||||
return "image/png", "png"
|
|
||||||
case strings.HasSuffix(strings.ToLower(filename), ".webp"):
|
|
||||||
return "image/webp", "webp"
|
|
||||||
default:
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidImage(data []byte, ext string) bool {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ext {
|
|
||||||
case "jpg", "jpeg":
|
|
||||||
// JPEG: FF D8 FF
|
|
||||||
return data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF
|
|
||||||
case "png":
|
|
||||||
// PNG: 89 50 4E 47
|
|
||||||
return data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47
|
|
||||||
case "webp":
|
|
||||||
// WebP: RIFF....WEBP
|
|
||||||
return len(data) > 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WEBP"
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileExtension(filename string) string {
|
|
||||||
if idx := strings.LastIndex(filename, "."); idx != -1 {
|
|
||||||
return filename[idx:]
|
|
||||||
}
|
|
||||||
return ".jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получение поста по ID
|
// Получение поста по ID
|
||||||
func (s *postService) GetByID(ctx context.Context, id int) (*domain.Post, error) {
|
func (s *postService) GetByID(ctx context.Context, id int) (*domain.Post, error) {
|
||||||
post, err := s.postRepo.GetByID(ctx, id)
|
post, err := s.postRepo.GetByID(ctx, id)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user