diff --git a/go.mod b/go.mod index 566e396..36d73a7 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/prometheus/client_golang v1.22.0 github.com/vektah/gqlparser/v2 v2.5.25 golang.org/x/crypto v0.37.0 + google.golang.org/grpc v1.73.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) @@ -30,7 +31,10 @@ require ( github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/sosodev/duration v1.3.1 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect ) diff --git a/go.sum b/go.sum index 5366519..53ad240 100644 --- a/go.sum +++ b/go.sum @@ -25,10 +25,16 @@ github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7c github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -70,12 +76,30 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vektah/gqlparser/v2 v2.5.25 h1:FmWtFEa+invTIzWlWK6Vk7BVEZU/97QBzeI8Z1JjGt8= github.com/vektah/gqlparser/v2 v2.5.25/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= diff --git a/internal/service/post_service.go b/internal/service/post_service.go index 4be8b91..673cf26 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -14,6 +14,7 @@ import ( "tailly_back_v2/internal/domain" "tailly_back_v2/internal/repository" "tailly_back_v2/internal/utils" + "tailly_back_v2/pkg/moderation" "time" "github.com/aws/aws-sdk-go/aws" @@ -80,6 +81,22 @@ func (s *postService) Create(ctx context.Context, authorID int, title, content s return nil, errors.New("image too large (max 10MB)") } + // Создаем клиент модерации + modClient, err := moderation.NewModerationClient("localhost:50051") + if err != nil { + return nil, fmt.Errorf("failed to create moderation client: %v", err) + } + defer modClient.Close() + + // Проверяем изображение + allowed, err := modClient.CheckImage(ctx, imgBase64) + if err != nil { + return nil, fmt.Errorf("image moderation failed: %v", err) + } + if !allowed { + return nil, errors.New("image rejected by moderation service") + } + randName := utils.GenerateId() + ".jpg" buf := new(bytes.Buffer) diff --git a/pkg/moderation/moderation.go b/pkg/moderation/moderation.go new file mode 100644 index 0000000..30c22f8 --- /dev/null +++ b/pkg/moderation/moderation.go @@ -0,0 +1,40 @@ +package moderation + +import ( + "context" + + "google.golang.org/grpc" + pb "tailly_back_v2/pkg/moderation/proto" +) + +type ModerationClient struct { + conn *grpc.ClientConn + client pb.ModerationServiceClient +} + +func NewModerationClient(addr string) (*ModerationClient, error) { + conn, err := grpc.Dial(addr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + + return &ModerationClient{ + conn: conn, + client: pb.NewModerationServiceClient(conn), + }, nil +} + +func (c *ModerationClient) CheckImage(ctx context.Context, imageData []byte) (bool, error) { + resp, err := c.client.CheckImage(ctx, &pb.ImageRequest{ + ImageData: imageData, + }) + if err != nil { + return false, err + } + + return resp.OverallDecision == "allowed", nil +} + +func (c *ModerationClient) Close() { + c.conn.Close() +} diff --git a/pkg/moderation/proto/moderation.pb.go b/pkg/moderation/proto/moderation.pb.go new file mode 100644 index 0000000..f91adb6 --- /dev/null +++ b/pkg/moderation/proto/moderation.pb.go @@ -0,0 +1,400 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v3.21.12 +// source: moderation.proto + +package __ + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ImageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ImageData []byte `protobuf:"bytes,1,opt,name=image_data,json=imageData,proto3" json:"image_data,omitempty"` + ImageUrl string `protobuf:"bytes,2,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImageRequest) Reset() { + *x = ImageRequest{} + mi := &file_moderation_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageRequest) ProtoMessage() {} + +func (x *ImageRequest) ProtoReflect() protoreflect.Message { + mi := &file_moderation_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageRequest.ProtoReflect.Descriptor instead. +func (*ImageRequest) Descriptor() ([]byte, []int) { + return file_moderation_proto_rawDescGZIP(), []int{0} +} + +func (x *ImageRequest) GetImageData() []byte { + if x != nil { + return x.ImageData + } + return nil +} + +func (x *ImageRequest) GetImageUrl() string { + if x != nil { + return x.ImageUrl + } + return "" +} + +type VideoRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + VideoUrl string `protobuf:"bytes,1,opt,name=video_url,json=videoUrl,proto3" json:"video_url,omitempty"` + FrameSampleRate int32 `protobuf:"varint,2,opt,name=frame_sample_rate,json=frameSampleRate,proto3" json:"frame_sample_rate,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoRequest) Reset() { + *x = VideoRequest{} + mi := &file_moderation_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoRequest) ProtoMessage() {} + +func (x *VideoRequest) ProtoReflect() protoreflect.Message { + mi := &file_moderation_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoRequest.ProtoReflect.Descriptor instead. +func (*VideoRequest) Descriptor() ([]byte, []int) { + return file_moderation_proto_rawDescGZIP(), []int{1} +} + +func (x *VideoRequest) GetVideoUrl() string { + if x != nil { + return x.VideoUrl + } + return "" +} + +func (x *VideoRequest) GetFrameSampleRate() int32 { + if x != nil { + return x.FrameSampleRate + } + return 0 +} + +type ModerationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + OverallDecision string `protobuf:"bytes,1,opt,name=overall_decision,json=overallDecision,proto3" json:"overall_decision,omitempty"` + Predictions []*Prediction `protobuf:"bytes,2,rep,name=predictions,proto3" json:"predictions,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModerationResponse) Reset() { + *x = ModerationResponse{} + mi := &file_moderation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModerationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModerationResponse) ProtoMessage() {} + +func (x *ModerationResponse) ProtoReflect() protoreflect.Message { + mi := &file_moderation_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModerationResponse.ProtoReflect.Descriptor instead. +func (*ModerationResponse) Descriptor() ([]byte, []int) { + return file_moderation_proto_rawDescGZIP(), []int{2} +} + +func (x *ModerationResponse) GetOverallDecision() string { + if x != nil { + return x.OverallDecision + } + return "" +} + +func (x *ModerationResponse) GetPredictions() []*Prediction { + if x != nil { + return x.Predictions + } + return nil +} + +func (x *ModerationResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type Prediction struct { + state protoimpl.MessageState `protogen:"open.v1"` + Category string `protobuf:"bytes,1,opt,name=category,proto3" json:"category,omitempty"` + Score float32 `protobuf:"fixed32,2,opt,name=score,proto3" json:"score,omitempty"` + Decision string `protobuf:"bytes,3,opt,name=decision,proto3" json:"decision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Prediction) Reset() { + *x = Prediction{} + mi := &file_moderation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Prediction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Prediction) ProtoMessage() {} + +func (x *Prediction) ProtoReflect() protoreflect.Message { + mi := &file_moderation_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Prediction.ProtoReflect.Descriptor instead. +func (*Prediction) Descriptor() ([]byte, []int) { + return file_moderation_proto_rawDescGZIP(), []int{3} +} + +func (x *Prediction) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *Prediction) GetScore() float32 { + if x != nil { + return x.Score + } + return 0 +} + +func (x *Prediction) GetDecision() string { + if x != nil { + return x.Decision + } + return "" +} + +type ModerationSettings struct { + state protoimpl.MessageState `protogen:"open.v1"` + CategoryThresholds map[string]float32 `protobuf:"bytes,1,rep,name=category_thresholds,json=categoryThresholds,proto3" json:"category_thresholds,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + AlwaysBlockCategories []string `protobuf:"bytes,2,rep,name=always_block_categories,json=alwaysBlockCategories,proto3" json:"always_block_categories,omitempty"` + AlwaysAllowCategories []string `protobuf:"bytes,3,rep,name=always_allow_categories,json=alwaysAllowCategories,proto3" json:"always_allow_categories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModerationSettings) Reset() { + *x = ModerationSettings{} + mi := &file_moderation_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModerationSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModerationSettings) ProtoMessage() {} + +func (x *ModerationSettings) ProtoReflect() protoreflect.Message { + mi := &file_moderation_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModerationSettings.ProtoReflect.Descriptor instead. +func (*ModerationSettings) Descriptor() ([]byte, []int) { + return file_moderation_proto_rawDescGZIP(), []int{4} +} + +func (x *ModerationSettings) GetCategoryThresholds() map[string]float32 { + if x != nil { + return x.CategoryThresholds + } + return nil +} + +func (x *ModerationSettings) GetAlwaysBlockCategories() []string { + if x != nil { + return x.AlwaysBlockCategories + } + return nil +} + +func (x *ModerationSettings) GetAlwaysAllowCategories() []string { + if x != nil { + return x.AlwaysAllowCategories + } + return nil +} + +var File_moderation_proto protoreflect.FileDescriptor + +const file_moderation_proto_rawDesc = "" + + "\n" + + "\x10moderation.proto\x12\n" + + "moderation\"J\n" + + "\fImageRequest\x12\x1d\n" + + "\n" + + "image_data\x18\x01 \x01(\fR\timageData\x12\x1b\n" + + "\timage_url\x18\x02 \x01(\tR\bimageUrl\"W\n" + + "\fVideoRequest\x12\x1b\n" + + "\tvideo_url\x18\x01 \x01(\tR\bvideoUrl\x12*\n" + + "\x11frame_sample_rate\x18\x02 \x01(\x05R\x0fframeSampleRate\"\x93\x01\n" + + "\x12ModerationResponse\x12)\n" + + "\x10overall_decision\x18\x01 \x01(\tR\x0foverallDecision\x128\n" + + "\vpredictions\x18\x02 \x03(\v2\x16.moderation.PredictionR\vpredictions\x12\x18\n" + + "\amessage\x18\x03 \x01(\tR\amessage\"Z\n" + + "\n" + + "Prediction\x12\x1a\n" + + "\bcategory\x18\x01 \x01(\tR\bcategory\x12\x14\n" + + "\x05score\x18\x02 \x01(\x02R\x05score\x12\x1a\n" + + "\bdecision\x18\x03 \x01(\tR\bdecision\"\xb4\x02\n" + + "\x12ModerationSettings\x12g\n" + + "\x13category_thresholds\x18\x01 \x03(\v26.moderation.ModerationSettings.CategoryThresholdsEntryR\x12categoryThresholds\x126\n" + + "\x17always_block_categories\x18\x02 \x03(\tR\x15alwaysBlockCategories\x126\n" + + "\x17always_allow_categories\x18\x03 \x03(\tR\x15alwaysAllowCategories\x1aE\n" + + "\x17CategoryThresholdsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x02R\x05value:\x028\x012\xa7\x01\n" + + "\x11ModerationService\x12H\n" + + "\n" + + "CheckImage\x12\x18.moderation.ImageRequest\x1a\x1e.moderation.ModerationResponse\"\x00\x12H\n" + + "\n" + + "CheckVideo\x12\x18.moderation.VideoRequest\x1a\x1e.moderation.ModerationResponse\"\x00B\x03Z\x01.b\x06proto3" + +var ( + file_moderation_proto_rawDescOnce sync.Once + file_moderation_proto_rawDescData []byte +) + +func file_moderation_proto_rawDescGZIP() []byte { + file_moderation_proto_rawDescOnce.Do(func() { + file_moderation_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_moderation_proto_rawDesc), len(file_moderation_proto_rawDesc))) + }) + return file_moderation_proto_rawDescData +} + +var file_moderation_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_moderation_proto_goTypes = []any{ + (*ImageRequest)(nil), // 0: moderation.ImageRequest + (*VideoRequest)(nil), // 1: moderation.VideoRequest + (*ModerationResponse)(nil), // 2: moderation.ModerationResponse + (*Prediction)(nil), // 3: moderation.Prediction + (*ModerationSettings)(nil), // 4: moderation.ModerationSettings + nil, // 5: moderation.ModerationSettings.CategoryThresholdsEntry +} +var file_moderation_proto_depIdxs = []int32{ + 3, // 0: moderation.ModerationResponse.predictions:type_name -> moderation.Prediction + 5, // 1: moderation.ModerationSettings.category_thresholds:type_name -> moderation.ModerationSettings.CategoryThresholdsEntry + 0, // 2: moderation.ModerationService.CheckImage:input_type -> moderation.ImageRequest + 1, // 3: moderation.ModerationService.CheckVideo:input_type -> moderation.VideoRequest + 2, // 4: moderation.ModerationService.CheckImage:output_type -> moderation.ModerationResponse + 2, // 5: moderation.ModerationService.CheckVideo:output_type -> moderation.ModerationResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_moderation_proto_init() } +func file_moderation_proto_init() { + if File_moderation_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_moderation_proto_rawDesc), len(file_moderation_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_moderation_proto_goTypes, + DependencyIndexes: file_moderation_proto_depIdxs, + MessageInfos: file_moderation_proto_msgTypes, + }.Build() + File_moderation_proto = out.File + file_moderation_proto_goTypes = nil + file_moderation_proto_depIdxs = nil +} diff --git a/pkg/moderation/proto/moderation.proto b/pkg/moderation/proto/moderation.proto new file mode 100644 index 0000000..0d31e75 --- /dev/null +++ b/pkg/moderation/proto/moderation.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package moderation; +option go_package = "."; + +service ModerationService { + rpc CheckImage (ImageRequest) returns (ModerationResponse) {} + rpc CheckVideo (VideoRequest) returns (ModerationResponse) {} +} + +message ImageRequest { + bytes image_data = 1; + string image_url = 2; +} + +message VideoRequest { + string video_url = 1; + int32 frame_sample_rate = 2; +} + +message ModerationResponse { + string overall_decision = 1; + repeated Prediction predictions = 2; + string message = 3; +} + +message Prediction { + string category = 1; + float score = 2; + string decision = 3; +} + +message ModerationSettings { + map category_thresholds = 1; + repeated string always_block_categories = 2; + repeated string always_allow_categories = 3; +} \ No newline at end of file diff --git a/pkg/moderation/proto/moderation_grpc.pb.go b/pkg/moderation/proto/moderation_grpc.pb.go new file mode 100644 index 0000000..484086e --- /dev/null +++ b/pkg/moderation/proto/moderation_grpc.pb.go @@ -0,0 +1,159 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.21.12 +// source: moderation.proto + +package __ + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + ModerationService_CheckImage_FullMethodName = "/moderation.ModerationService/CheckImage" + ModerationService_CheckVideo_FullMethodName = "/moderation.ModerationService/CheckVideo" +) + +// ModerationServiceClient is the client API for ModerationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ModerationServiceClient interface { + CheckImage(ctx context.Context, in *ImageRequest, opts ...grpc.CallOption) (*ModerationResponse, error) + CheckVideo(ctx context.Context, in *VideoRequest, opts ...grpc.CallOption) (*ModerationResponse, error) +} + +type moderationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewModerationServiceClient(cc grpc.ClientConnInterface) ModerationServiceClient { + return &moderationServiceClient{cc} +} + +func (c *moderationServiceClient) CheckImage(ctx context.Context, in *ImageRequest, opts ...grpc.CallOption) (*ModerationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ModerationResponse) + err := c.cc.Invoke(ctx, ModerationService_CheckImage_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *moderationServiceClient) CheckVideo(ctx context.Context, in *VideoRequest, opts ...grpc.CallOption) (*ModerationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ModerationResponse) + err := c.cc.Invoke(ctx, ModerationService_CheckVideo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ModerationServiceServer is the server API for ModerationService service. +// All implementations must embed UnimplementedModerationServiceServer +// for forward compatibility. +type ModerationServiceServer interface { + CheckImage(context.Context, *ImageRequest) (*ModerationResponse, error) + CheckVideo(context.Context, *VideoRequest) (*ModerationResponse, error) + mustEmbedUnimplementedModerationServiceServer() +} + +// UnimplementedModerationServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedModerationServiceServer struct{} + +func (UnimplementedModerationServiceServer) CheckImage(context.Context, *ImageRequest) (*ModerationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckImage not implemented") +} +func (UnimplementedModerationServiceServer) CheckVideo(context.Context, *VideoRequest) (*ModerationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckVideo not implemented") +} +func (UnimplementedModerationServiceServer) mustEmbedUnimplementedModerationServiceServer() {} +func (UnimplementedModerationServiceServer) testEmbeddedByValue() {} + +// UnsafeModerationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ModerationServiceServer will +// result in compilation errors. +type UnsafeModerationServiceServer interface { + mustEmbedUnimplementedModerationServiceServer() +} + +func RegisterModerationServiceServer(s grpc.ServiceRegistrar, srv ModerationServiceServer) { + // If the following call pancis, it indicates UnimplementedModerationServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ModerationService_ServiceDesc, srv) +} + +func _ModerationService_CheckImage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModerationServiceServer).CheckImage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModerationService_CheckImage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModerationServiceServer).CheckImage(ctx, req.(*ImageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModerationService_CheckVideo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VideoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModerationServiceServer).CheckVideo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModerationService_CheckVideo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModerationServiceServer).CheckVideo(ctx, req.(*VideoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ModerationService_ServiceDesc is the grpc.ServiceDesc for ModerationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ModerationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "moderation.ModerationService", + HandlerType: (*ModerationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CheckImage", + Handler: _ModerationService_CheckImage_Handler, + }, + { + MethodName: "CheckVideo", + Handler: _ModerationService_CheckVideo_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "moderation.proto", +}