Add Wildberries product fetching and rate limiting functionality

This commit is contained in:
2025-07-04 13:30:50 +03:00
parent b48421e653
commit dc097c6fc8
67 changed files with 81355 additions and 110 deletions

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM alpine:latest
WORKDIR /app
COPY main .
COPY ".env" .
RUN apk add gcompat
CMD ["./main"]

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v6.31.0 // protoc v6.31.1
// source: marketplace/marketplace.proto // source: marketplace/marketplace.proto
package marketplace package marketplace

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v6.31.0 // - protoc v6.31.1
// source: marketplace/marketplace.proto // source: marketplace/marketplace.proto
package marketplace package marketplace

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v6.31.0 // protoc v6.31.1
// source: ozon/products.proto // source: ozon/products.proto
package products package products
@@ -110,6 +110,94 @@ func (x *GetListOfProductsResponse) GetProducts() []*Product {
return nil return nil
} }
type GetProductPriceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
MarketplaceId int64 `protobuf:"varint,1,opt,name=marketplace_id,json=marketplaceId,proto3" json:"marketplace_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetProductPriceRequest) Reset() {
*x = GetProductPriceRequest{}
mi := &file_ozon_products_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProductPriceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProductPriceRequest) ProtoMessage() {}
func (x *GetProductPriceRequest) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_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 GetProductPriceRequest.ProtoReflect.Descriptor instead.
func (*GetProductPriceRequest) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{2}
}
func (x *GetProductPriceRequest) GetMarketplaceId() int64 {
if x != nil {
return x.MarketplaceId
}
return 0
}
type GetProductPriceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
ProductPrices []*ProductPrice `protobuf:"bytes,1,rep,name=product_prices,json=productPrices,proto3" json:"product_prices,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetProductPriceResponse) Reset() {
*x = GetProductPriceResponse{}
mi := &file_ozon_products_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProductPriceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProductPriceResponse) ProtoMessage() {}
func (x *GetProductPriceResponse) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_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 GetProductPriceResponse.ProtoReflect.Descriptor instead.
func (*GetProductPriceResponse) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{3}
}
func (x *GetProductPriceResponse) GetProductPrices() []*ProductPrice {
if x != nil {
return x.ProductPrices
}
return nil
}
type Product struct { type Product struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
@@ -123,7 +211,7 @@ type Product struct {
func (x *Product) Reset() { func (x *Product) Reset() {
*x = Product{} *x = Product{}
mi := &file_ozon_products_proto_msgTypes[2] mi := &file_ozon_products_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -135,7 +223,7 @@ func (x *Product) String() string {
func (*Product) ProtoMessage() {} func (*Product) ProtoMessage() {}
func (x *Product) ProtoReflect() protoreflect.Message { func (x *Product) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[2] mi := &file_ozon_products_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -148,7 +236,7 @@ func (x *Product) ProtoReflect() protoreflect.Message {
// Deprecated: Use Product.ProtoReflect.Descriptor instead. // Deprecated: Use Product.ProtoReflect.Descriptor instead.
func (*Product) Descriptor() ([]byte, []int) { func (*Product) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{2} return file_ozon_products_proto_rawDescGZIP(), []int{4}
} }
func (x *Product) GetId() int64 { func (x *Product) GetId() int64 {
@@ -186,6 +274,74 @@ func (x *Product) GetStatuses() *Product_Status {
return nil return nil
} }
type ProductPrice struct {
state protoimpl.MessageState `protogen:"open.v1"`
Acquiring uint32 `protobuf:"varint,1,opt,name=acquiring,proto3" json:"acquiring,omitempty"`
Commissions *ProductPrice_Commissions `protobuf:"bytes,2,opt,name=commissions,proto3" json:"commissions,omitempty"`
OfferId string `protobuf:"bytes,4,opt,name=offer_id,json=offerId,proto3" json:"offer_id,omitempty"`
ProductId uint32 `protobuf:"varint,7,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProductPrice) Reset() {
*x = ProductPrice{}
mi := &file_ozon_products_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProductPrice) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProductPrice) ProtoMessage() {}
func (x *ProductPrice) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[5]
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 ProductPrice.ProtoReflect.Descriptor instead.
func (*ProductPrice) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{5}
}
func (x *ProductPrice) GetAcquiring() uint32 {
if x != nil {
return x.Acquiring
}
return 0
}
func (x *ProductPrice) GetCommissions() *ProductPrice_Commissions {
if x != nil {
return x.Commissions
}
return nil
}
func (x *ProductPrice) GetOfferId() string {
if x != nil {
return x.OfferId
}
return ""
}
func (x *ProductPrice) GetProductId() uint32 {
if x != nil {
return x.ProductId
}
return 0
}
type Product_Status struct { type Product_Status struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
StatusName string `protobuf:"bytes,1,opt,name=status_name,json=statusName,proto3" json:"status_name,omitempty"` StatusName string `protobuf:"bytes,1,opt,name=status_name,json=statusName,proto3" json:"status_name,omitempty"`
@@ -195,7 +351,7 @@ type Product_Status struct {
func (x *Product_Status) Reset() { func (x *Product_Status) Reset() {
*x = Product_Status{} *x = Product_Status{}
mi := &file_ozon_products_proto_msgTypes[3] mi := &file_ozon_products_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -207,7 +363,7 @@ func (x *Product_Status) String() string {
func (*Product_Status) ProtoMessage() {} func (*Product_Status) ProtoMessage() {}
func (x *Product_Status) ProtoReflect() protoreflect.Message { func (x *Product_Status) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[3] mi := &file_ozon_products_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -220,7 +376,7 @@ func (x *Product_Status) ProtoReflect() protoreflect.Message {
// Deprecated: Use Product_Status.ProtoReflect.Descriptor instead. // Deprecated: Use Product_Status.ProtoReflect.Descriptor instead.
func (*Product_Status) Descriptor() ([]byte, []int) { func (*Product_Status) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{2, 0} return file_ozon_products_proto_rawDescGZIP(), []int{4, 0}
} }
func (x *Product_Status) GetStatusName() string { func (x *Product_Status) GetStatusName() string {
@@ -240,7 +396,7 @@ type Product_Stocks struct {
func (x *Product_Stocks) Reset() { func (x *Product_Stocks) Reset() {
*x = Product_Stocks{} *x = Product_Stocks{}
mi := &file_ozon_products_proto_msgTypes[4] mi := &file_ozon_products_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -252,7 +408,7 @@ func (x *Product_Stocks) String() string {
func (*Product_Stocks) ProtoMessage() {} func (*Product_Stocks) ProtoMessage() {}
func (x *Product_Stocks) ProtoReflect() protoreflect.Message { func (x *Product_Stocks) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[4] mi := &file_ozon_products_proto_msgTypes[7]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -265,7 +421,7 @@ func (x *Product_Stocks) ProtoReflect() protoreflect.Message {
// Deprecated: Use Product_Stocks.ProtoReflect.Descriptor instead. // Deprecated: Use Product_Stocks.ProtoReflect.Descriptor instead.
func (*Product_Stocks) Descriptor() ([]byte, []int) { func (*Product_Stocks) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{2, 1} return file_ozon_products_proto_rawDescGZIP(), []int{4, 1}
} }
func (x *Product_Stocks) GetStocks() []*Product_Stock { func (x *Product_Stocks) GetStocks() []*Product_Stock {
@@ -294,7 +450,7 @@ type Product_Stock struct {
func (x *Product_Stock) Reset() { func (x *Product_Stock) Reset() {
*x = Product_Stock{} *x = Product_Stock{}
mi := &file_ozon_products_proto_msgTypes[5] mi := &file_ozon_products_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -306,7 +462,7 @@ func (x *Product_Stock) String() string {
func (*Product_Stock) ProtoMessage() {} func (*Product_Stock) ProtoMessage() {}
func (x *Product_Stock) ProtoReflect() protoreflect.Message { func (x *Product_Stock) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[5] mi := &file_ozon_products_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -319,7 +475,7 @@ func (x *Product_Stock) ProtoReflect() protoreflect.Message {
// Deprecated: Use Product_Stock.ProtoReflect.Descriptor instead. // Deprecated: Use Product_Stock.ProtoReflect.Descriptor instead.
func (*Product_Stock) Descriptor() ([]byte, []int) { func (*Product_Stock) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{2, 2} return file_ozon_products_proto_rawDescGZIP(), []int{4, 2}
} }
func (x *Product_Stock) GetPresent() int64 { func (x *Product_Stock) GetPresent() int64 {
@@ -350,6 +506,138 @@ func (x *Product_Stock) GetSource() string {
return "" return ""
} }
type ProductPrice_Commissions struct {
state protoimpl.MessageState `protogen:"open.v1"`
FboDelivToCustomerAmount float64 `protobuf:"fixed64,1,opt,name=fbo_deliv_to_customer_amount,json=fboDelivToCustomerAmount,proto3" json:"fbo_deliv_to_customer_amount,omitempty"`
FboDirectFlowTransMaxAmount float64 `protobuf:"fixed64,2,opt,name=fbo_direct_flow_trans_max_amount,json=fboDirectFlowTransMaxAmount,proto3" json:"fbo_direct_flow_trans_max_amount,omitempty"`
FboDirectFlowTransMinAmount float64 `protobuf:"fixed64,3,opt,name=fbo_direct_flow_trans_min_amount,json=fboDirectFlowTransMinAmount,proto3" json:"fbo_direct_flow_trans_min_amount,omitempty"`
FboReturnFlowAmount float64 `protobuf:"fixed64,4,opt,name=fbo_return_flow_amount,json=fboReturnFlowAmount,proto3" json:"fbo_return_flow_amount,omitempty"`
FbsDelivToCustomerAmount float64 `protobuf:"fixed64,5,opt,name=fbs_deliv_to_customer_amount,json=fbsDelivToCustomerAmount,proto3" json:"fbs_deliv_to_customer_amount,omitempty"`
FbsDirectFlowTransMaxAmount float64 `protobuf:"fixed64,6,opt,name=fbs_direct_flow_trans_max_amount,json=fbsDirectFlowTransMaxAmount,proto3" json:"fbs_direct_flow_trans_max_amount,omitempty"`
FbsDirectFlowTransMinAmount float64 `protobuf:"fixed64,7,opt,name=fbs_direct_flow_trans_min_amount,json=fbsDirectFlowTransMinAmount,proto3" json:"fbs_direct_flow_trans_min_amount,omitempty"`
FbsFirstMileMaxAmount float64 `protobuf:"fixed64,8,opt,name=fbs_first_mile_max_amount,json=fbsFirstMileMaxAmount,proto3" json:"fbs_first_mile_max_amount,omitempty"`
FbsFirstMileMinAmount float64 `protobuf:"fixed64,9,opt,name=fbs_first_mile_min_amount,json=fbsFirstMileMinAmount,proto3" json:"fbs_first_mile_min_amount,omitempty"`
FbsReturnFlowAmount float64 `protobuf:"fixed64,10,opt,name=fbs_return_flow_amount,json=fbsReturnFlowAmount,proto3" json:"fbs_return_flow_amount,omitempty"`
SalesPercentFbo float64 `protobuf:"fixed64,11,opt,name=sales_percent_fbo,json=salesPercentFbo,proto3" json:"sales_percent_fbo,omitempty"`
SalesPercentFbs float64 `protobuf:"fixed64,12,opt,name=sales_percent_fbs,json=salesPercentFbs,proto3" json:"sales_percent_fbs,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProductPrice_Commissions) Reset() {
*x = ProductPrice_Commissions{}
mi := &file_ozon_products_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProductPrice_Commissions) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProductPrice_Commissions) ProtoMessage() {}
func (x *ProductPrice_Commissions) ProtoReflect() protoreflect.Message {
mi := &file_ozon_products_proto_msgTypes[9]
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 ProductPrice_Commissions.ProtoReflect.Descriptor instead.
func (*ProductPrice_Commissions) Descriptor() ([]byte, []int) {
return file_ozon_products_proto_rawDescGZIP(), []int{5, 0}
}
func (x *ProductPrice_Commissions) GetFboDelivToCustomerAmount() float64 {
if x != nil {
return x.FboDelivToCustomerAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFboDirectFlowTransMaxAmount() float64 {
if x != nil {
return x.FboDirectFlowTransMaxAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFboDirectFlowTransMinAmount() float64 {
if x != nil {
return x.FboDirectFlowTransMinAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFboReturnFlowAmount() float64 {
if x != nil {
return x.FboReturnFlowAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsDelivToCustomerAmount() float64 {
if x != nil {
return x.FbsDelivToCustomerAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsDirectFlowTransMaxAmount() float64 {
if x != nil {
return x.FbsDirectFlowTransMaxAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsDirectFlowTransMinAmount() float64 {
if x != nil {
return x.FbsDirectFlowTransMinAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsFirstMileMaxAmount() float64 {
if x != nil {
return x.FbsFirstMileMaxAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsFirstMileMinAmount() float64 {
if x != nil {
return x.FbsFirstMileMinAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetFbsReturnFlowAmount() float64 {
if x != nil {
return x.FbsReturnFlowAmount
}
return 0
}
func (x *ProductPrice_Commissions) GetSalesPercentFbo() float64 {
if x != nil {
return x.SalesPercentFbo
}
return 0
}
func (x *ProductPrice_Commissions) GetSalesPercentFbs() float64 {
if x != nil {
return x.SalesPercentFbs
}
return 0
}
var File_ozon_products_proto protoreflect.FileDescriptor var File_ozon_products_proto protoreflect.FileDescriptor
const file_ozon_products_proto_rawDesc = "" + const file_ozon_products_proto_rawDesc = "" +
@@ -358,7 +646,11 @@ const file_ozon_products_proto_rawDesc = "" +
"\x18GetListOfProductsRequest\x12%\n" + "\x18GetListOfProductsRequest\x12%\n" +
"\x0emarketplace_id\x18\x01 \x01(\x03R\rmarketplaceId\"O\n" + "\x0emarketplace_id\x18\x01 \x01(\x03R\rmarketplaceId\"O\n" +
"\x19GetListOfProductsResponse\x122\n" + "\x19GetListOfProductsResponse\x122\n" +
"\bproducts\x18\x01 \x03(\v2\x16.ozon.products.ProductR\bproducts\"\xb3\x03\n" + "\bproducts\x18\x01 \x03(\v2\x16.ozon.products.ProductR\bproducts\"?\n" +
"\x16GetProductPriceRequest\x12%\n" +
"\x0emarketplace_id\x18\x01 \x01(\x03R\rmarketplaceId\"]\n" +
"\x17GetProductPriceResponse\x12B\n" +
"\x0eproduct_prices\x18\x01 \x03(\v2\x1b.ozon.products.ProductPriceR\rproductPrices\"\xb3\x03\n" +
"\aProduct\x12\x0e\n" + "\aProduct\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x19\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x19\n" +
"\boffer_id\x18\x02 \x01(\tR\aofferId\x125\n" + "\boffer_id\x18\x02 \x01(\tR\aofferId\x125\n" +
@@ -375,9 +667,30 @@ const file_ozon_products_proto_rawDesc = "" +
"\apresent\x18\x01 \x01(\x03R\apresent\x12\x1a\n" + "\apresent\x18\x01 \x01(\x03R\apresent\x12\x1a\n" +
"\breserved\x18\x02 \x01(\x03R\breserved\x12\x10\n" + "\breserved\x18\x02 \x01(\x03R\breserved\x12\x10\n" +
"\x03SKU\x18\x03 \x01(\x03R\x03SKU\x12\x16\n" + "\x03SKU\x18\x03 \x01(\x03R\x03SKU\x12\x16\n" +
"\x06source\x18\x04 \x01(\tR\x06source2{\n" + "\x06source\x18\x04 \x01(\tR\x06source\"\x93\a\n" +
"\fProductPrice\x12\x1c\n" +
"\tacquiring\x18\x01 \x01(\rR\tacquiring\x12I\n" +
"\vcommissions\x18\x02 \x01(\v2'.ozon.products.ProductPrice.CommissionsR\vcommissions\x12\x19\n" +
"\boffer_id\x18\x04 \x01(\tR\aofferId\x12\x1d\n" +
"\n" +
"product_id\x18\a \x01(\rR\tproductId\x1a\xdf\x05\n" +
"\vCommissions\x12>\n" +
"\x1cfbo_deliv_to_customer_amount\x18\x01 \x01(\x01R\x18fboDelivToCustomerAmount\x12E\n" +
" fbo_direct_flow_trans_max_amount\x18\x02 \x01(\x01R\x1bfboDirectFlowTransMaxAmount\x12E\n" +
" fbo_direct_flow_trans_min_amount\x18\x03 \x01(\x01R\x1bfboDirectFlowTransMinAmount\x123\n" +
"\x16fbo_return_flow_amount\x18\x04 \x01(\x01R\x13fboReturnFlowAmount\x12>\n" +
"\x1cfbs_deliv_to_customer_amount\x18\x05 \x01(\x01R\x18fbsDelivToCustomerAmount\x12E\n" +
" fbs_direct_flow_trans_max_amount\x18\x06 \x01(\x01R\x1bfbsDirectFlowTransMaxAmount\x12E\n" +
" fbs_direct_flow_trans_min_amount\x18\a \x01(\x01R\x1bfbsDirectFlowTransMinAmount\x128\n" +
"\x19fbs_first_mile_max_amount\x18\b \x01(\x01R\x15fbsFirstMileMaxAmount\x128\n" +
"\x19fbs_first_mile_min_amount\x18\t \x01(\x01R\x15fbsFirstMileMinAmount\x123\n" +
"\x16fbs_return_flow_amount\x18\n" +
" \x01(\x01R\x13fbsReturnFlowAmount\x12*\n" +
"\x11sales_percent_fbo\x18\v \x01(\x01R\x0fsalesPercentFbo\x12*\n" +
"\x11sales_percent_fbs\x18\f \x01(\x01R\x0fsalesPercentFbs2\xdf\x01\n" +
"\x0fProductsService\x12h\n" + "\x0fProductsService\x12h\n" +
"\x11GetListOfProducts\x12'.ozon.products.GetListOfProductsRequest\x1a(.ozon.products.GetListOfProductsResponse0\x01B\x11Z\x0f./ozon/productsb\x06proto3" "\x11GetListOfProducts\x12'.ozon.products.GetListOfProductsRequest\x1a(.ozon.products.GetListOfProductsResponse0\x01\x12b\n" +
"\x0fGetProductPrice\x12%.ozon.products.GetProductPriceRequest\x1a&.ozon.products.GetProductPriceResponse0\x01B\x11Z\x0f./ozon/productsb\x06proto3"
var ( var (
file_ozon_products_proto_rawDescOnce sync.Once file_ozon_products_proto_rawDescOnce sync.Once
@@ -391,27 +704,35 @@ func file_ozon_products_proto_rawDescGZIP() []byte {
return file_ozon_products_proto_rawDescData return file_ozon_products_proto_rawDescData
} }
var file_ozon_products_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_ozon_products_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_ozon_products_proto_goTypes = []any{ var file_ozon_products_proto_goTypes = []any{
(*GetListOfProductsRequest)(nil), // 0: ozon.products.GetListOfProductsRequest (*GetListOfProductsRequest)(nil), // 0: ozon.products.GetListOfProductsRequest
(*GetListOfProductsResponse)(nil), // 1: ozon.products.GetListOfProductsResponse (*GetListOfProductsResponse)(nil), // 1: ozon.products.GetListOfProductsResponse
(*Product)(nil), // 2: ozon.products.Product (*GetProductPriceRequest)(nil), // 2: ozon.products.GetProductPriceRequest
(*Product_Status)(nil), // 3: ozon.products.Product.Status (*GetProductPriceResponse)(nil), // 3: ozon.products.GetProductPriceResponse
(*Product_Stocks)(nil), // 4: ozon.products.Product.Stocks (*Product)(nil), // 4: ozon.products.Product
(*Product_Stock)(nil), // 5: ozon.products.Product.Stock (*ProductPrice)(nil), // 5: ozon.products.ProductPrice
(*Product_Status)(nil), // 6: ozon.products.Product.Status
(*Product_Stocks)(nil), // 7: ozon.products.Product.Stocks
(*Product_Stock)(nil), // 8: ozon.products.Product.Stock
(*ProductPrice_Commissions)(nil), // 9: ozon.products.ProductPrice.Commissions
} }
var file_ozon_products_proto_depIdxs = []int32{ var file_ozon_products_proto_depIdxs = []int32{
2, // 0: ozon.products.GetListOfProductsResponse.products:type_name -> ozon.products.Product 4, // 0: ozon.products.GetListOfProductsResponse.products:type_name -> ozon.products.Product
4, // 1: ozon.products.Product.stocks:type_name -> ozon.products.Product.Stocks 5, // 1: ozon.products.GetProductPriceResponse.product_prices:type_name -> ozon.products.ProductPrice
3, // 2: ozon.products.Product.statuses:type_name -> ozon.products.Product.Status 7, // 2: ozon.products.Product.stocks:type_name -> ozon.products.Product.Stocks
5, // 3: ozon.products.Product.Stocks.stocks:type_name -> ozon.products.Product.Stock 6, // 3: ozon.products.Product.statuses:type_name -> ozon.products.Product.Status
0, // 4: ozon.products.ProductsService.GetListOfProducts:input_type -> ozon.products.GetListOfProductsRequest 9, // 4: ozon.products.ProductPrice.commissions:type_name -> ozon.products.ProductPrice.Commissions
1, // 5: ozon.products.ProductsService.GetListOfProducts:output_type -> ozon.products.GetListOfProductsResponse 8, // 5: ozon.products.Product.Stocks.stocks:type_name -> ozon.products.Product.Stock
5, // [5:6] is the sub-list for method output_type 0, // 6: ozon.products.ProductsService.GetListOfProducts:input_type -> ozon.products.GetListOfProductsRequest
4, // [4:5] is the sub-list for method input_type 2, // 7: ozon.products.ProductsService.GetProductPrice:input_type -> ozon.products.GetProductPriceRequest
4, // [4:4] is the sub-list for extension type_name 1, // 8: ozon.products.ProductsService.GetListOfProducts:output_type -> ozon.products.GetListOfProductsResponse
4, // [4:4] is the sub-list for extension extendee 3, // 9: ozon.products.ProductsService.GetProductPrice:output_type -> ozon.products.GetProductPriceResponse
0, // [0:4] is the sub-list for field type_name 8, // [8:10] is the sub-list for method output_type
6, // [6:8] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
} }
func init() { file_ozon_products_proto_init() } func init() { file_ozon_products_proto_init() }
@@ -425,7 +746,7 @@ func file_ozon_products_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ozon_products_proto_rawDesc), len(file_ozon_products_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ozon_products_proto_rawDesc), len(file_ozon_products_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 6, NumMessages: 10,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v6.31.0 // - protoc v6.31.1
// source: ozon/products.proto // source: ozon/products.proto
package products package products
@@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9
const ( const (
ProductsService_GetListOfProducts_FullMethodName = "/ozon.products.ProductsService/GetListOfProducts" ProductsService_GetListOfProducts_FullMethodName = "/ozon.products.ProductsService/GetListOfProducts"
ProductsService_GetProductPrice_FullMethodName = "/ozon.products.ProductsService/GetProductPrice"
) )
// ProductsServiceClient is the client API for ProductsService service. // ProductsServiceClient is the client API for ProductsService service.
@@ -28,6 +29,7 @@ const (
type ProductsServiceClient interface { type ProductsServiceClient interface {
// Retrieves a list of products based on the provided request. // Retrieves a list of products based on the provided request.
GetListOfProducts(ctx context.Context, in *GetListOfProductsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetListOfProductsResponse], error) GetListOfProducts(ctx context.Context, in *GetListOfProductsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetListOfProductsResponse], error)
GetProductPrice(ctx context.Context, in *GetProductPriceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetProductPriceResponse], error)
} }
type productsServiceClient struct { type productsServiceClient struct {
@@ -57,12 +59,32 @@ func (c *productsServiceClient) GetListOfProducts(ctx context.Context, in *GetLi
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetListOfProductsClient = grpc.ServerStreamingClient[GetListOfProductsResponse] type ProductsService_GetListOfProductsClient = grpc.ServerStreamingClient[GetListOfProductsResponse]
func (c *productsServiceClient) GetProductPrice(ctx context.Context, in *GetProductPriceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetProductPriceResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ProductsService_ServiceDesc.Streams[1], ProductsService_GetProductPrice_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[GetProductPriceRequest, GetProductPriceResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetProductPriceClient = grpc.ServerStreamingClient[GetProductPriceResponse]
// ProductsServiceServer is the server API for ProductsService service. // ProductsServiceServer is the server API for ProductsService service.
// All implementations must embed UnimplementedProductsServiceServer // All implementations must embed UnimplementedProductsServiceServer
// for forward compatibility. // for forward compatibility.
type ProductsServiceServer interface { type ProductsServiceServer interface {
// Retrieves a list of products based on the provided request. // Retrieves a list of products based on the provided request.
GetListOfProducts(*GetListOfProductsRequest, grpc.ServerStreamingServer[GetListOfProductsResponse]) error GetListOfProducts(*GetListOfProductsRequest, grpc.ServerStreamingServer[GetListOfProductsResponse]) error
GetProductPrice(*GetProductPriceRequest, grpc.ServerStreamingServer[GetProductPriceResponse]) error
mustEmbedUnimplementedProductsServiceServer() mustEmbedUnimplementedProductsServiceServer()
} }
@@ -76,6 +98,9 @@ type UnimplementedProductsServiceServer struct{}
func (UnimplementedProductsServiceServer) GetListOfProducts(*GetListOfProductsRequest, grpc.ServerStreamingServer[GetListOfProductsResponse]) error { func (UnimplementedProductsServiceServer) GetListOfProducts(*GetListOfProductsRequest, grpc.ServerStreamingServer[GetListOfProductsResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetListOfProducts not implemented") return status.Errorf(codes.Unimplemented, "method GetListOfProducts not implemented")
} }
func (UnimplementedProductsServiceServer) GetProductPrice(*GetProductPriceRequest, grpc.ServerStreamingServer[GetProductPriceResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetProductPrice not implemented")
}
func (UnimplementedProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() {} func (UnimplementedProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() {}
func (UnimplementedProductsServiceServer) testEmbeddedByValue() {} func (UnimplementedProductsServiceServer) testEmbeddedByValue() {}
@@ -108,6 +133,17 @@ func _ProductsService_GetListOfProducts_Handler(srv interface{}, stream grpc.Ser
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetListOfProductsServer = grpc.ServerStreamingServer[GetListOfProductsResponse] type ProductsService_GetListOfProductsServer = grpc.ServerStreamingServer[GetListOfProductsResponse]
func _ProductsService_GetProductPrice_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetProductPriceRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ProductsServiceServer).GetProductPrice(m, &grpc.GenericServerStream[GetProductPriceRequest, GetProductPriceResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetProductPriceServer = grpc.ServerStreamingServer[GetProductPriceResponse]
// ProductsService_ServiceDesc is the grpc.ServiceDesc for ProductsService service. // ProductsService_ServiceDesc is the grpc.ServiceDesc for ProductsService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@@ -121,6 +157,11 @@ var ProductsService_ServiceDesc = grpc.ServiceDesc{
Handler: _ProductsService_GetListOfProducts_Handler, Handler: _ProductsService_GetListOfProducts_Handler,
ServerStreams: true, ServerStreams: true,
}, },
{
StreamName: "GetProductPrice",
Handler: _ProductsService_GetProductPrice_Handler,
ServerStreams: true,
},
}, },
Metadata: "ozon/products.proto", Metadata: "ozon/products.proto",
} }

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v6.31.0 // protoc v6.31.1
// source: wb/products.proto // source: wb/products.proto
package products package products
@@ -10,6 +10,7 @@ import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync"
unsafe "unsafe" unsafe "unsafe"
) )
@@ -20,20 +21,255 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type GetProductsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
MarketplaceId int64 `protobuf:"varint,1,opt,name=marketplace_id,json=marketplaceId,proto3" json:"marketplace_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetProductsRequest) Reset() {
*x = GetProductsRequest{}
mi := &file_wb_products_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProductsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProductsRequest) ProtoMessage() {}
func (x *GetProductsRequest) ProtoReflect() protoreflect.Message {
mi := &file_wb_products_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 GetProductsRequest.ProtoReflect.Descriptor instead.
func (*GetProductsRequest) Descriptor() ([]byte, []int) {
return file_wb_products_proto_rawDescGZIP(), []int{0}
}
func (x *GetProductsRequest) GetMarketplaceId() int64 {
if x != nil {
return x.MarketplaceId
}
return 0
}
type Product struct {
state protoimpl.MessageState `protogen:"open.v1"`
NmID int64 `protobuf:"varint,1,opt,name=nmID,proto3" json:"nmID,omitempty"`
SubjectID int64 `protobuf:"varint,2,opt,name=subjectID,proto3" json:"subjectID,omitempty"`
VendorCode string `protobuf:"bytes,3,opt,name=vendor_code,json=vendorCode,proto3" json:"vendor_code,omitempty"`
Sizes []*Product_Size `protobuf:"bytes,4,rep,name=sizes,proto3" json:"sizes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Product) Reset() {
*x = Product{}
mi := &file_wb_products_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Product) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Product) ProtoMessage() {}
func (x *Product) ProtoReflect() protoreflect.Message {
mi := &file_wb_products_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 Product.ProtoReflect.Descriptor instead.
func (*Product) Descriptor() ([]byte, []int) {
return file_wb_products_proto_rawDescGZIP(), []int{1}
}
func (x *Product) GetNmID() int64 {
if x != nil {
return x.NmID
}
return 0
}
func (x *Product) GetSubjectID() int64 {
if x != nil {
return x.SubjectID
}
return 0
}
func (x *Product) GetVendorCode() string {
if x != nil {
return x.VendorCode
}
return ""
}
func (x *Product) GetSizes() []*Product_Size {
if x != nil {
return x.Sizes
}
return nil
}
type GetProductsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetProductsResponse) Reset() {
*x = GetProductsResponse{}
mi := &file_wb_products_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetProductsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetProductsResponse) ProtoMessage() {}
func (x *GetProductsResponse) ProtoReflect() protoreflect.Message {
mi := &file_wb_products_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 GetProductsResponse.ProtoReflect.Descriptor instead.
func (*GetProductsResponse) Descriptor() ([]byte, []int) {
return file_wb_products_proto_rawDescGZIP(), []int{2}
}
func (x *GetProductsResponse) GetProducts() []*Product {
if x != nil {
return x.Products
}
return nil
}
type Product_Size struct {
state protoimpl.MessageState `protogen:"open.v1"`
Skus []string `protobuf:"bytes,1,rep,name=skus,proto3" json:"skus,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Product_Size) Reset() {
*x = Product_Size{}
mi := &file_wb_products_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Product_Size) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Product_Size) ProtoMessage() {}
func (x *Product_Size) ProtoReflect() protoreflect.Message {
mi := &file_wb_products_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 Product_Size.ProtoReflect.Descriptor instead.
func (*Product_Size) Descriptor() ([]byte, []int) {
return file_wb_products_proto_rawDescGZIP(), []int{1, 0}
}
func (x *Product_Size) GetSkus() []string {
if x != nil {
return x.Skus
}
return nil
}
var File_wb_products_proto protoreflect.FileDescriptor var File_wb_products_proto protoreflect.FileDescriptor
const file_wb_products_proto_rawDesc = "" + const file_wb_products_proto_rawDesc = "" +
"\n" + "\n" +
"\x11wb/products.proto\x12\vwb.products2\x11\n" + "\x11wb/products.proto\x12\vwb.products\";\n" +
"\x0fProductsServiceB\x0fZ\r./wb/productsb\x06proto3" "\x12GetProductsRequest\x12%\n" +
"\x0emarketplace_id\x18\x01 \x01(\x03R\rmarketplaceId\"\xa9\x01\n" +
"\aProduct\x12\x12\n" +
"\x04nmID\x18\x01 \x01(\x03R\x04nmID\x12\x1c\n" +
"\tsubjectID\x18\x02 \x01(\x03R\tsubjectID\x12\x1f\n" +
"\vvendor_code\x18\x03 \x01(\tR\n" +
"vendorCode\x12/\n" +
"\x05sizes\x18\x04 \x03(\v2\x19.wb.products.Product.SizeR\x05sizes\x1a\x1a\n" +
"\x04Size\x12\x12\n" +
"\x04skus\x18\x01 \x03(\tR\x04skus\"G\n" +
"\x13GetProductsResponse\x120\n" +
"\bproducts\x18\x01 \x03(\v2\x14.wb.products.ProductR\bproducts2e\n" +
"\x0fProductsService\x12R\n" +
"\vGetProducts\x12\x1f.wb.products.GetProductsRequest\x1a .wb.products.GetProductsResponse0\x01B\x0fZ\r./wb/productsb\x06proto3"
var file_wb_products_proto_goTypes = []any{} var (
file_wb_products_proto_rawDescOnce sync.Once
file_wb_products_proto_rawDescData []byte
)
func file_wb_products_proto_rawDescGZIP() []byte {
file_wb_products_proto_rawDescOnce.Do(func() {
file_wb_products_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_wb_products_proto_rawDesc), len(file_wb_products_proto_rawDesc)))
})
return file_wb_products_proto_rawDescData
}
var file_wb_products_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_wb_products_proto_goTypes = []any{
(*GetProductsRequest)(nil), // 0: wb.products.GetProductsRequest
(*Product)(nil), // 1: wb.products.Product
(*GetProductsResponse)(nil), // 2: wb.products.GetProductsResponse
(*Product_Size)(nil), // 3: wb.products.Product.Size
}
var file_wb_products_proto_depIdxs = []int32{ var file_wb_products_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type 3, // 0: wb.products.Product.sizes:type_name -> wb.products.Product.Size
0, // [0:0] is the sub-list for method input_type 1, // 1: wb.products.GetProductsResponse.products:type_name -> wb.products.Product
0, // [0:0] is the sub-list for extension type_name 0, // 2: wb.products.ProductsService.GetProducts:input_type -> wb.products.GetProductsRequest
0, // [0:0] is the sub-list for extension extendee 2, // 3: wb.products.ProductsService.GetProducts:output_type -> wb.products.GetProductsResponse
0, // [0:0] is the sub-list for field type_name 3, // [3:4] is the sub-list for method output_type
2, // [2:3] 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_wb_products_proto_init() } func init() { file_wb_products_proto_init() }
@@ -47,12 +283,13 @@ func file_wb_products_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_wb_products_proto_rawDesc), len(file_wb_products_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_wb_products_proto_rawDesc), len(file_wb_products_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 0, NumMessages: 4,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
GoTypes: file_wb_products_proto_goTypes, GoTypes: file_wb_products_proto_goTypes,
DependencyIndexes: file_wb_products_proto_depIdxs, DependencyIndexes: file_wb_products_proto_depIdxs,
MessageInfos: file_wb_products_proto_msgTypes,
}.Build() }.Build()
File_wb_products_proto = out.File File_wb_products_proto = out.File
file_wb_products_proto_goTypes = nil file_wb_products_proto_goTypes = nil

View File

@@ -1,13 +1,16 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v6.31.0 // - protoc v6.31.1
// source: wb/products.proto // source: wb/products.proto
package products package products
import ( import (
context "context"
grpc "google.golang.org/grpc" 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 // This is a compile-time assertion to ensure that this generated file
@@ -15,10 +18,15 @@ import (
// Requires gRPC-Go v1.64.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const (
ProductsService_GetProducts_FullMethodName = "/wb.products.ProductsService/GetProducts"
)
// ProductsServiceClient is the client API for ProductsService service. // ProductsServiceClient is the client API for ProductsService 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. // 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 ProductsServiceClient interface { type ProductsServiceClient interface {
GetProducts(ctx context.Context, in *GetProductsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetProductsResponse], error)
} }
type productsServiceClient struct { type productsServiceClient struct {
@@ -29,10 +37,30 @@ func NewProductsServiceClient(cc grpc.ClientConnInterface) ProductsServiceClient
return &productsServiceClient{cc} return &productsServiceClient{cc}
} }
func (c *productsServiceClient) GetProducts(ctx context.Context, in *GetProductsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetProductsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ProductsService_ServiceDesc.Streams[0], ProductsService_GetProducts_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[GetProductsRequest, GetProductsResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetProductsClient = grpc.ServerStreamingClient[GetProductsResponse]
// ProductsServiceServer is the server API for ProductsService service. // ProductsServiceServer is the server API for ProductsService service.
// All implementations must embed UnimplementedProductsServiceServer // All implementations must embed UnimplementedProductsServiceServer
// for forward compatibility. // for forward compatibility.
type ProductsServiceServer interface { type ProductsServiceServer interface {
GetProducts(*GetProductsRequest, grpc.ServerStreamingServer[GetProductsResponse]) error
mustEmbedUnimplementedProductsServiceServer() mustEmbedUnimplementedProductsServiceServer()
} }
@@ -43,6 +71,9 @@ type ProductsServiceServer interface {
// pointer dereference when methods are called. // pointer dereference when methods are called.
type UnimplementedProductsServiceServer struct{} type UnimplementedProductsServiceServer struct{}
func (UnimplementedProductsServiceServer) GetProducts(*GetProductsRequest, grpc.ServerStreamingServer[GetProductsResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetProducts not implemented")
}
func (UnimplementedProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() {} func (UnimplementedProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() {}
func (UnimplementedProductsServiceServer) testEmbeddedByValue() {} func (UnimplementedProductsServiceServer) testEmbeddedByValue() {}
@@ -64,6 +95,17 @@ func RegisterProductsServiceServer(s grpc.ServiceRegistrar, srv ProductsServiceS
s.RegisterService(&ProductsService_ServiceDesc, srv) s.RegisterService(&ProductsService_ServiceDesc, srv)
} }
func _ProductsService_GetProducts_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetProductsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ProductsServiceServer).GetProducts(m, &grpc.GenericServerStream[GetProductsRequest, GetProductsResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProductsService_GetProductsServer = grpc.ServerStreamingServer[GetProductsResponse]
// ProductsService_ServiceDesc is the grpc.ServiceDesc for ProductsService service. // ProductsService_ServiceDesc is the grpc.ServiceDesc for ProductsService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@@ -71,6 +113,12 @@ var ProductsService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "wb.products.ProductsService", ServiceName: "wb.products.ProductsService",
HandlerType: (*ProductsServiceServer)(nil), HandlerType: (*ProductsServiceServer)(nil),
Methods: []grpc.MethodDesc{}, Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{
Metadata: "wb/products.proto", {
StreamName: "GetProducts",
Handler: _ProductsService_GetProducts_Handler,
ServerStreams: true,
},
},
Metadata: "wb/products.proto",
} }

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v6.31.0 // protoc v6.31.1
// source: yandexmarket/products.proto // source: yandexmarket/products.proto
package products package products

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v6.31.0 // - protoc v6.31.1
// source: yandexmarket/products.proto // source: yandexmarket/products.proto
package products package products

View File

@@ -8,9 +8,12 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"net" "net"
"os" "os"
"sipro-mps/internal/config"
"sipro-mps/internal/marketplace" "sipro-mps/internal/marketplace"
ozon "sipro-mps/internal/ozon/products" ozon "sipro-mps/internal/ozon/products"
"sipro-mps/internal/redis" "sipro-mps/internal/redis"
"sipro-mps/internal/tasks/client"
wb "sipro-mps/internal/wb/products"
) )
func logMessage(level string, format string, a ...interface{}) { func logMessage(level string, format string, a ...interface{}) {
@@ -50,7 +53,11 @@ func createGrpcServer(pool *pgxpool.Pool) {
fmt.Printf("failed to register Ozon Products gRPC server: %v\n", err) fmt.Printf("failed to register Ozon Products gRPC server: %v\n", err)
return return
} }
_, err = wb.RegisterAdapterGRPC(grpcServer, *repo)
if err != nil {
fmt.Printf("failed to register Wildberries Products gRPC server: %v\n", err)
return
}
fmt.Println("gRPC server registered successfully.") fmt.Println("gRPC server registered successfully.")
// Start serving gRPC requests // Start serving gRPC requests
fmt.Println("gRPC server is starting on port 8080...") fmt.Println("gRPC server is starting on port 8080...")
@@ -61,26 +68,64 @@ func createGrpcServer(pool *pgxpool.Pool) {
fmt.Println("gRPC server created.") fmt.Println("gRPC server created.")
} }
func main() { func initDotenv() error {
// Initializing the dotenv file
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
logMessage("error", "Error loading .env file: %v", err) return fmt.Errorf("error loading .env file: %w", err)
return
} }
logMessage("info", "Dotenv file loaded successfully. 🌱") logMessage("info", "Dotenv file loaded successfully. 🌱")
return nil
}
func initRedisClient(ctx context.Context) error {
err := redis.InitClient(ctx)
if err != nil {
return fmt.Errorf("error initializing Redis client: %w", err)
}
//defer redis.CloseClient()
logMessage("info", "Redis client initialized successfully. 🟥")
return nil
}
func initRedisLocker() error {
err := redis.InitLocker()
if err != nil {
return fmt.Errorf("error initializing Redis locker: %w", err)
}
logMessage("info", "Redis locker initialized successfully. 🟥")
return nil
}
func main() {
err := initDotenv()
if err != nil {
logMessage("error", "Failed to load .env file: %v", err)
return
}
logMessage("info", "Starting the SIPRO Marketplace Server... 🚀1")
ctx := context.Background() ctx := context.Background()
// Initializing the Redis client // Initializing the Redis client
err = redis.InitClient(ctx) err = initRedisClient(ctx)
if err != nil { if err != nil {
logMessage("error", "Failed to initialize Redis client: %v", err) logMessage("error", "Failed to initialize Redis client: %v", err)
return return
} }
defer redis.CloseClient() defer redis.CloseClient()
logMessage("info", "Redis client initialized successfully. 🟥")
// Initializing the Redis locker
err = initRedisLocker()
if err != nil {
logMessage("error", "Failed to initialize Redis locker: %v", err)
return
}
defer redis.CloseLocker()
cfg, err := config.LoadConfig()
if err != nil {
logMessage("error", "Failed to load configuration: %v", err)
return
}
client.InitClient(*cfg.Redis)
// Initializing pgx connection // Initializing pgx connection
dbpool, err := pgxpool.New(ctx, os.Getenv("POSTGRES_URL")) dbpool, err := pgxpool.New(ctx, os.Getenv("POSTGRES_URL"))
if err != nil { if err != nil {
@@ -88,11 +133,6 @@ func main() {
return return
} }
defer dbpool.Close() defer dbpool.Close()
logMessage("info", "Connected to PostgreSQL successfully. 🐘")
createGrpcServer(dbpool) createGrpcServer(dbpool)
} }
//for _, item := range items {
// //logMessage("info", "Product ID: %s, Name: %s, Price: %d", item.Price, item.Name, item.Price)
//}

34
cmd/tasks_server/main.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
"sipro-mps/internal/config"
"sipro-mps/internal/redis"
"sipro-mps/internal/tasks/server"
)
func main() {
cfg, err := config.LoadConfig()
if err != nil {
panic(err)
}
ctx := context.Background()
err = redis.InitClient(ctx)
if err != nil {
panic(err)
}
defer redis.CloseClient()
err = redis.InitLocker()
if err != nil {
panic(err)
}
defer redis.CloseLocker()
dbpool, err := pgxpool.New(ctx, cfg.Database.URL)
if err != nil {
panic(err)
}
srv := server.NewAsynqServer(cfg.Redis, dbpool)
srv.Run()
}

51
cmd/test/main.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"context"
"fmt"
"github.com/go-faster/errors"
"github.com/hibiken/asynq"
"github.com/joho/godotenv"
"github.com/redis/rueidis"
"sipro-mps/internal/config"
"sipro-mps/internal/redis"
"sipro-mps/internal/tasks"
)
func main() {
godotenv.Load()
ctx := context.Background()
redis.InitClient(ctx)
c := *redis.Client
key := fmt.Sprintf("wb:products:%d", "test")
v, err := c.Do(ctx, c.B().Get().Key(key).Build()).ToString()
if err != nil {
if errors.As(err, &rueidis.Nil) {
fmt.Println("Key does not exist in Redis:", key)
return
}
}
fmt.Println("Value from Redis:", v)
return
cfg, err := config.LoadConfig()
if err != nil {
panic(err)
}
client := asynq.NewClient(asynq.RedisClientOpt{Addr: cfg.Redis.Host + ":" + cfg.Redis.Port, Password: cfg.Redis.Password})
defer func(client *asynq.Client) {
err := client.Close()
if err != nil {
panic(err)
}
}(client)
task, err := tasks.NewFetchProductsTask(1130)
if err != nil {
panic(err)
}
info, err := client.Enqueue(task)
if err != nil {
panic(err)
}
println("Task enqueued successfully:", info.ID, "with queue name:", info.Queue, "and payload size:", len(info.Payload), "bytes")
}

15
docker-compose.yml Normal file
View File

@@ -0,0 +1,15 @@
version: '3.8'
services:
app:
image: git.denco.store/fakz9/sipro-marketplaces:latest
depends_on:
- redis
networks:
- appnet
redis:
image: redis
networks:
- appnet
networks:
appnet:
driver: bridge

39
go.mod
View File

@@ -4,24 +4,55 @@ go 1.24
require ( require (
git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505 git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505
github.com/go-faster/errors v0.7.1
github.com/go-faster/jx v1.1.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/hibiken/asynq v0.25.1
github.com/jackc/pgx/v5 v5.7.5 github.com/jackc/pgx/v5 v5.7.5
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/ogen-go/ogen v1.14.0
github.com/redis/rueidis v1.0.60 github.com/redis/rueidis v1.0.60
github.com/samber/lo v1.50.0 github.com/samber/lo v1.50.0
github.com/tidwall/gjson v1.18.0 github.com/tidwall/gjson v1.18.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/metric v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
google.golang.org/grpc v1.72.2 google.golang.org/grpc v1.72.2
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
) )
require ( require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-faster/yaml v0.4.6 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/crypto v0.37.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
golang.org/x/net v0.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.32.0 // indirect go.uber.org/zap v1.27.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )

104
go.sum
View File

@@ -1,18 +1,45 @@
git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505 h1:5mviYMLXLIvsFEXLR0IlGuqMNzkB8X/yrmxZHYk0n84= git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505 h1:5mviYMLXLIvsFEXLR0IlGuqMNzkB8X/yrmxZHYk0n84=
git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505/go.mod h1:1uPm278HN7mDkP507KHsLpnW+R9vWGEzp9BSMycjVbQ= git.denco.store/fakz9/ozon-api-client v1.18.1-0.20250526003754-c6c303092505/go.mod h1:1uPm278HN7mDkP507KHsLpnW+R9vWGEzp9BSMycjVbQ=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg=
github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg=
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -23,16 +50,37 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ogen-go/ogen v1.14.0 h1:TU1Nj4z9UBsAfTkf+IhuNNp7igdFQKqkk9+6/y4XuWg=
github.com/ogen-go/ogen v1.14.0/go.mod h1:Iw1vkqkx6SU7I9th5ceP+fVPJ6Wge4e3kAVzAxJEpPE=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/rueidis v1.0.60 h1:MGZX8uNdw7iyWz22JhjA/9iXzddfCUE/EMK4VxKoKpA= github.com/redis/rueidis v1.0.60 h1:MGZX8uNdw7iyWz22JhjA/9iXzddfCUE/EMK4VxKoKpA=
github.com/redis/rueidis v1.0.60/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= github.com/redis/rueidis v1.0.60/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -47,26 +95,38 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 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/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
@@ -74,6 +134,10 @@ google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,17 +1,18 @@
package config package config
import "github.com/joho/godotenv"
type Config struct { type Config struct {
DB string Redis *RedisConfig
HTTP string Database *DatabaseConfig
GRPC string
Kafka string
} }
func Load() Config { func LoadConfig() (*Config, error) {
return Config{ err := godotenv.Load()
DB: "dbname=test password=GjitkYf[eq user=postgres sslmode=disable", if err != nil {
HTTP: ":8080", return nil, err
GRPC: ":50051",
Kafka: "localhost:9092",
} }
redisConfig := LoadRedisConfig()
databaseConfig := LoadDatabaseConfig()
return &Config{Redis: redisConfig, Database: databaseConfig}, nil
} }

View File

@@ -0,0 +1,23 @@
package config
import "os"
type DatabaseConfig struct {
Host string
Port string
Login string
Password string
Database string
URL string
}
func LoadDatabaseConfig() *DatabaseConfig {
return &DatabaseConfig{
Host: os.Getenv("POSTGRES_HOST"),
Port: os.Getenv("POSTGRES_PORT"),
Login: os.Getenv("POSTGRES_LOGIN"),
Password: os.Getenv("POSTGRES_PASSWORD"),
Database: os.Getenv("POSTGRES_DATABASE"),
URL: os.Getenv("POSTGRES_URL"),
}
}

23
internal/config/redis.go Normal file
View File

@@ -0,0 +1,23 @@
package config
import "os"
type RedisConfig struct {
Host string
Port string
Password string
Addr string
}
func LoadRedisConfig() *RedisConfig {
host := os.Getenv("REDIS_HOST")
port := os.Getenv("REDIS_PORT")
password := os.Getenv("REDIS_PASSWORD")
addr := os.Getenv("REDIS_ADDR")
return &RedisConfig{
Host: host,
Port: port,
Password: password,
Addr: addr,
}
}

View File

@@ -14,4 +14,5 @@ type Marketplace struct {
Name string Name string
AuthData pgtype.Text AuthData pgtype.Text
WarehouseID pgtype.Text WarehouseID pgtype.Text
AuthDataJson []byte
} }

View File

@@ -10,7 +10,7 @@ import (
) )
const getMarketplaceByID = `-- name: GetMarketplaceByID :one const getMarketplaceByID = `-- name: GetMarketplaceByID :one
SELECT id, base_marketplace, name, auth_data, warehouse_id FROM marketplaces SELECT id, base_marketplace, name, auth_data, warehouse_id, auth_data_json FROM marketplaces
WHERE id = $1 LIMIT 1 WHERE id = $1 LIMIT 1
` `
@@ -23,6 +23,7 @@ func (q *Queries) GetMarketplaceByID(ctx context.Context, id int32) (Marketplace
&i.Name, &i.Name,
&i.AuthData, &i.AuthData,
&i.WarehouseID, &i.WarehouseID,
&i.AuthDataJson,
) )
return i, err return i, err
} }

View File

@@ -1,9 +1,14 @@
create table marketplaces create table marketplaces
( (
id serial id serial
primary key, primary key,
base_marketplace integer not null, base_marketplace integer not null,
name varchar not null, name varchar not null,
auth_data varchar, auth_data varchar,
warehouse_id varchar warehouse_id varchar,
auth_data_json jsonb generated always as (
CASE
WHEN ((auth_data)::text IS JSON) THEN (auth_data)::jsonb
ELSE NULL::jsonb
END) stored
); );

View File

@@ -5,4 +5,5 @@ type Marketplace struct {
BaseMarketplace int `json:"base_marketplace"` BaseMarketplace int `json:"base_marketplace"`
AuthData string `json:"auth_data"` AuthData string `json:"auth_data"`
WarehouseID string `json:"warehouse_id"` WarehouseID string `json:"warehouse_id"`
AuthDataJson []byte `json:"auth_data_json,omitempty"`
} }

View File

@@ -24,5 +24,6 @@ func (r *dbRepository) GetMarketplaceByID(ctx context.Context, id int) (*Marketp
BaseMarketplace: int(marketplace.BaseMarketplace), BaseMarketplace: int(marketplace.BaseMarketplace),
AuthData: marketplace.AuthData.String, AuthData: marketplace.AuthData.String,
WarehouseID: marketplace.WarehouseID.String, WarehouseID: marketplace.WarehouseID.String,
AuthDataJson: marketplace.AuthDataJson,
}, nil }, nil
} }

View File

@@ -5,7 +5,7 @@ import (
proto "sipro-mps/api/generated/v1/ozon/products" proto "sipro-mps/api/generated/v1/ozon/products"
) )
//go:generate go run github.com/jmattheis/goverter/cmd/goverter gen -g 'ignoreUnexported yes' . //go:generate go run github.com/jmattheis/goverter/cmd/goverter gen -global "ignoreUnexported yes" .
// goverter:converter // goverter:converter
// goverter:extend Int632ToInt64 // goverter:extend Int632ToInt64

View File

@@ -5,9 +5,8 @@ package generated
import ( import (
ozon "git.denco.store/fakz9/ozon-api-client/ozon" ozon "git.denco.store/fakz9/ozon-api-client/ozon"
products "sipro-mps/api/generated/v1/ozon/products" products "sipro-mps/api/generated/v1/ozon/products"
ozon "git.denco.store/fakz9/ozon-api-client/ozon" mapping "sipro-mps/internal/ozon/products/mapping"
) )
type ConverterImpl struct{} type ConverterImpl struct{}

View File

@@ -85,7 +85,7 @@ func (a *apiRepository) GetAllProducts(ctx context.Context, marketplaceId int) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := []OzonProduct{} var items []OzonProduct
productIdsChan := make(chan []int64) productIdsChan := make(chan []int64)
producsChan := make(chan []OzonProduct) producsChan := make(chan []OzonProduct)
errChan := make(chan error) errChan := make(chan error)

View File

@@ -50,7 +50,7 @@ func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error
waitTime, err := rateLimiterScript.Exec(ctx, *redis.Client, []string{clientId}, []string{ waitTime, err := rateLimiterScript.Exec(ctx, *redis.Client, []string{clientId}, []string{
fmt.Sprintf("%d", now), fmt.Sprintf("%d", now),
fmt.Sprintf("%d", int64(windowSize)), fmt.Sprintf("%d", int64(windowSize)),
fmt.Sprintf("%d", 50), fmt.Sprintf("%d", rps),
}).ToInt64() }).ToInt64()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute rate limit script: %w", err) return nil, fmt.Errorf("failed to execute rate limit script: %w", err)

View File

@@ -11,6 +11,7 @@ var Client *rueidis.Client
func InitClient(ctx context.Context) error { func InitClient(ctx context.Context) error {
var err error var err error
host := os.Getenv("REDIS_HOST") host := os.Getenv("REDIS_HOST")
//host := "redis"
port := os.Getenv("REDIS_PORT") port := os.Getenv("REDIS_PORT")
password := os.Getenv("REDIS_PASSWORD") password := os.Getenv("REDIS_PASSWORD")

29
internal/redis/lock.go Normal file
View File

@@ -0,0 +1,29 @@
package redis
import (
"github.com/redis/rueidis"
"github.com/redis/rueidis/rueidislock"
"os"
)
var Locker *rueidislock.Locker
func InitLocker() error {
redisAddr := os.Getenv("REDIS_ADDR")
password := os.Getenv("REDIS_PASSWORD")
locker, err := rueidislock.NewLocker(rueidislock.LockerOption{
ClientOption: rueidis.ClientOption{InitAddress: []string{redisAddr}, Password: password},
})
if err != nil {
return err
}
Locker = &locker
return nil
}
func CloseLocker() {
if Locker != nil {
(*Locker).Close()
}
Locker = nil
}

View File

@@ -0,0 +1,25 @@
package client
import (
"github.com/hibiken/asynq"
"sipro-mps/internal/config"
)
var Client *asynq.Client
// InitClient initializes the Asynq client with the provided Redis configuration.
func InitClient(redisConfig config.RedisConfig) {
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: redisConfig.Addr,
Password: redisConfig.Password,
})
Client = client
}
func CloseClient() {
if Client != nil {
if err := Client.Close(); err != nil {
panic(err)
}
}
}

View File

@@ -0,0 +1,39 @@
package server
import (
"github.com/hibiken/asynq"
"github.com/jackc/pgx/v5/pgxpool"
"sipro-mps/internal/config"
"sipro-mps/internal/tasks/types"
"sipro-mps/internal/tasks/wb"
)
type AsynqServer struct {
redisConfig *config.RedisConfig
dbpool *pgxpool.Pool
}
func NewAsynqServer(redisConfig *config.RedisConfig, dbpool *pgxpool.Pool) *AsynqServer {
return &AsynqServer{
redisConfig: redisConfig,
dbpool: dbpool,
}
}
func (s *AsynqServer) createMux() *asynq.ServeMux {
mux := asynq.NewServeMux()
// Register task handlers here
mux.Handle(types.TypeWbFetchProducts, &wb.FetchProductsProcessor{Dbpool: s.dbpool})
return mux
}
func (s *AsynqServer) Run() {
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: s.redisConfig.Addr, Password: s.redisConfig.Password},
asynq.Config{Concurrency: 10},
)
mux := s.createMux()
if err := srv.Run(mux); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,5 @@
package types
const (
TypeWbFetchProducts = "wb:fetch_products"
)

View File

@@ -0,0 +1,19 @@
package types
import (
"encoding/json"
"github.com/hibiken/asynq"
"time"
)
type FetchProductsTask struct {
MarketplaceId int
}
func NewFetchProductsTask(marketplaceId int) (*asynq.Task, error) {
payload, err := json.Marshal(&FetchProductsTask{MarketplaceId: marketplaceId})
if err != nil {
return nil, err
}
return asynq.NewTask(TypeWbFetchProducts, payload, asynq.MaxRetry(2), asynq.Timeout(20*time.Minute)), nil
}

65
internal/tasks/wb/wb.go Normal file
View File

@@ -0,0 +1,65 @@
package wb
import (
"context"
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/samber/lo"
pb "sipro-mps/api/generated/v1/wb/products"
mp_repo "sipro-mps/internal/marketplace"
"sipro-mps/internal/redis"
"sipro-mps/internal/tasks/types"
wb_products_repo "sipro-mps/internal/wb/products"
conv "sipro-mps/internal/wb/products/mapping/generated"
)
type FetchProductsProcessor struct {
Dbpool *pgxpool.Pool
wbRepo wb_products_repo.Repository
}
func (p *FetchProductsProcessor) ProcessTask(ctx context.Context, task *asynq.Task) error {
var payload types.FetchProductsTask
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
return asynq.SkipRetry
}
marketplaceRepo := mp_repo.NewDBRepository(p.Dbpool)
repo := wb_products_repo.NewAPIRepository(marketplaceRepo)
_, sellerId, err := repo.ParseMarketplace(ctx, payload.MarketplaceId)
if err != nil {
return fmt.Errorf("failed to parse marketplace %d: %w", payload.MarketplaceId, err)
}
locker := *redis.Locker
_, cancel, err := locker.TryWithContext(ctx, fmt.Sprintf("wb:products:marketplace:%s:lock", sellerId))
if err != nil {
fmt.Printf("Failed to acquire lock for marketplace %s: %v\n", sellerId, err)
return asynq.SkipRetry
}
fmt.Println("Working on marketplace", payload.MarketplaceId, "with seller ID", sellerId)
defer cancel()
redisKey := fmt.Sprintf("wb:products:%s", sellerId)
productsRaw, err := repo.GetAllProducts(ctx, payload.MarketplaceId)
if err != nil {
return fmt.Errorf("failed to fetch products for marketplace %d: %w", payload.MarketplaceId, err)
}
converter := conv.ConverterImpl{}
products := lo.Map(productsRaw, func(item wb_products_repo.WbProduct, _ int) *pb.Product {
return converter.ToProto(&item)
})
redisClient := *redis.Client
productsJson, err := json.Marshal(products)
if err != nil {
return fmt.Errorf("failed to marshal products: %w", err)
}
err = redisClient.Do(ctx, redisClient.B().Set().Key(redisKey).Value(string(productsJson)).Build()).Error()
if err != nil {
return err
}
return nil
}

59
internal/wb/common.go Normal file
View File

@@ -0,0 +1,59 @@
package wb
import (
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt/v5"
"net/http"
"sipro-mps/internal/marketplace"
wbclient "sipro-mps/pkg/api/wb/client"
"time"
)
type WbAuthData struct {
Token string `json:"token"`
}
func NewWbAuthData(token string) WbAuthData {
return WbAuthData{
Token: token,
}
}
func DecodeWildberriesJwt(token []byte) (WbAuthData, jwt.MapClaims, error) {
var authData WbAuthData
err := json.Unmarshal(token, &authData)
if err != nil {
return authData, nil, fmt.Errorf("failed to unmarshal JWT: %w", err)
}
claims := jwt.MapClaims{}
_, _, err = jwt.NewParser().ParseUnverified(authData.Token, claims)
if err != nil {
return authData, nil, fmt.Errorf("invalid JWT: %w", err)
}
return authData, claims, nil
}
func GetClientFromMarketplace(mp *marketplace.Marketplace) (*wbclient.Client, error) {
authData, claims, err := DecodeWildberriesJwt(mp.AuthDataJson)
if err != nil {
return nil, fmt.Errorf("failed to decode Wildberries JWT")
}
exp := claims["exp"].(float64)
// chec if token is expired, for now unix date
now := float64(time.Now().Unix())
if exp < now {
return nil, fmt.Errorf("token is expired")
}
securityHandler := NewWildberriesSecurityHandler(authData.Token)
httpClient := &http.Client{
Transport: NewRateLimitTransport(),
}
client, err := wbclient.NewClient("https://content-api.wildberries.ru", securityHandler, wbclient.WithClient(httpClient))
if err != nil {
return nil, fmt.Errorf("failed to create Wildberries client: %w", err)
}
return client, nil
}

View File

@@ -0,0 +1,63 @@
package products
import (
"fmt"
"github.com/samber/lo"
"google.golang.org/grpc"
pb "sipro-mps/api/generated/v1/wb/products"
"sipro-mps/internal/marketplace"
)
type AdapterGRPC struct {
pb.UnimplementedProductsServiceServer
repo Repository
}
func NewAdapterGRPC(repo Repository) *AdapterGRPC {
return &AdapterGRPC{
repo: repo,
}
}
func RegisterAdapterGRPC(server *grpc.Server, marketplacesRepository marketplace.Repository) (*Repository, error) {
repo := NewAPIRepository(marketplacesRepository)
adapter := NewAdapterGRPC(repo)
pb.RegisterProductsServiceServer(server, adapter)
return &repo, nil
}
func (a *AdapterGRPC) GetProducts(req *pb.GetProductsRequest, stream pb.ProductsService_GetProductsServer) error {
ctx := stream.Context()
resultChan := make(chan []pb.Product)
errChan := make(chan error)
go a.repo.StreamAllProductsCache(ctx, int(req.MarketplaceId), resultChan, errChan)
for {
select {
case <-ctx.Done():
fmt.Println("context done")
return ctx.Err()
case products, ok := <-resultChan:
if !ok {
fmt.Println("result channel closed")
return nil
}
resp := &pb.GetProductsResponse{
Products: lo.Map(products, func(p pb.Product, _ int) *pb.Product {
return &p
}),
}
if err := stream.Send(resp); err != nil {
fmt.Println("error sending response", err)
return err
}
case err, ok := <-errChan:
if !ok {
return nil
}
if ok && err != nil {
fmt.Println("error in channel", err)
return err
}
}
}
}

View File

@@ -0,0 +1,9 @@
package products
import (
pb "sipro-mps/api/generated/v1/wb/products"
"sipro-mps/pkg/api/wb/client"
)
type WbProduct = api.ContentV2GetCardsListPostOKCardsItem
type PbProduct = pb.Product

View File

@@ -0,0 +1,28 @@
package mapping
import wbclient "sipro-mps/pkg/api/wb/client"
// import (
//
// proto "sipro-mps/api/generated/v1/wb/products"
// internal "sipro-mps/internal/wb/products"
// wbclient "sipro-mps/pkg/api/wb/client"
//
// )
//
// //go:generate go run github.com/jmattheis/goverter/cmd/goverter gen -global "ignoreUnexported yes" .
//
// // goverter:converter
// // goverter:extend OptIntToInt64 OptStringToString
//
// type Converter interface {
// // goverter:ignore state sizeCache unknownFields
//
// ToProto(details *internal.WbProduct) *proto.Product
// }
func OptIntToInt64(i wbclient.OptInt) int64 {
return int64(i.Value)
}
func OptStringToString(s wbclient.OptString) string {
return s.Value
}

View File

@@ -0,0 +1,40 @@
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
//go:build !goverter
package generated
import (
products "sipro-mps/api/generated/v1/wb/products"
mapping "sipro-mps/internal/wb/products/mapping"
client "sipro-mps/pkg/api/wb/client"
)
type ConverterImpl struct{}
func (c *ConverterImpl) ToProto(source *client.ContentV2GetCardsListPostOKCardsItem) *products.Product {
var pProductsProduct *products.Product
if source != nil {
var productsProduct products.Product
productsProduct.NmID = mapping.OptIntToInt64((*source).NmID)
productsProduct.SubjectID = mapping.OptIntToInt64((*source).SubjectID)
productsProduct.VendorCode = mapping.OptStringToString((*source).VendorCode)
if (*source).Sizes != nil {
productsProduct.Sizes = make([]*products.Product_Size, len((*source).Sizes))
for i := 0; i < len((*source).Sizes); i++ {
productsProduct.Sizes[i] = c.apiContentV2GetCardsListPostOKCardsItemSizesItemToPProductsProduct_Size((*source).Sizes[i])
}
}
pProductsProduct = &productsProduct
}
return pProductsProduct
}
func (c *ConverterImpl) apiContentV2GetCardsListPostOKCardsItemSizesItemToPProductsProduct_Size(source client.ContentV2GetCardsListPostOKCardsItemSizesItem) *products.Product_Size {
var productsProduct_Size products.Product_Size
if source.Skus != nil {
productsProduct_Size.Skus = make([]string, len(source.Skus))
for i := 0; i < len(source.Skus); i++ {
productsProduct_Size.Skus[i] = source.Skus[i]
}
}
return &productsProduct_Size
}

View File

@@ -0,0 +1,13 @@
package products
import (
"context"
"sipro-mps/internal/marketplace"
)
type Repository interface {
GetAllProducts(ctx context.Context, marketplaceId int) ([]WbProduct, error)
StreamAllProducts(ctx context.Context, marketplaceId int, resultChan chan<- []WbProduct, errChan chan<- error)
StreamAllProductsCache(ctx context.Context, marketplaceId int, resultChan chan<- []PbProduct, errChan chan<- error)
ParseMarketplace(ctx context.Context, marketplaceId int) (*marketplace.Marketplace, string, error)
}

View File

@@ -0,0 +1,224 @@
package products
import (
"context"
"encoding/json"
"fmt"
"github.com/go-faster/errors"
"github.com/redis/rueidis"
"github.com/samber/lo"
pb "sipro-mps/api/generated/v1/wb/products"
"sipro-mps/internal/marketplace"
"sipro-mps/internal/redis"
"sipro-mps/internal/tasks/client"
"sipro-mps/internal/tasks/types"
"sipro-mps/internal/wb"
"sipro-mps/internal/wb/products/mapping/generated"
wbapi "sipro-mps/pkg/api/wb/client"
)
const (
maxRetries = 5
maxProductsPerRequest = 100
)
type apiRepository struct {
marketplaceRepository marketplace.Repository
}
func (a apiRepository) ParseMarketplace(ctx context.Context, marketplaceId int) (*marketplace.Marketplace, string, error) {
marketplaceByID, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
if err != nil {
return nil, "", err
}
_, claims, err := wb.DecodeWildberriesJwt(marketplaceByID.AuthDataJson)
if err != nil {
return nil, "", err
}
sellerId := claims["sid"].(string)
return marketplaceByID, sellerId, nil
}
func fetchProducts(
ctx context.Context,
client *wbapi.Client,
sellerId string,
resultChan chan<- []WbProduct,
errChan chan<- error,
) {
defer close(resultChan)
defer close(errChan)
request := wbapi.ContentV2GetCardsListPostReq{}
request.Settings.SetTo(wbapi.ContentV2GetCardsListPostReqSettings{})
request.Settings.Value.Cursor.SetTo(wbapi.ContentV2GetCardsListPostReqSettingsCursor{})
request.Settings.Value.Cursor.Value.Limit.SetTo(maxProductsPerRequest)
request.Settings.Value.Filter.SetTo(wbapi.ContentV2GetCardsListPostReqSettingsFilter{})
request.Settings.Value.Filter.Value.WithPhoto.SetTo(-1)
currentRetry := 0
for {
response, err := client.ContentV2GetCardsListPost(ctx, &request, wbapi.ContentV2GetCardsListPostParams{Locale: wbapi.NewOptString("ru")})
if err != nil {
currentRetry++
if currentRetry >= maxRetries {
errChan <- fmt.Errorf("fetching product IDs: %w", err)
return
}
continue
}
currentRetry = 0
switch r := response.(type) {
case *wbapi.ContentV2GetCardsListPostOKHeaders:
err = wb.SyncRateLimitRemaining(ctx, sellerId, r.XRatelimitRemaining.Value)
if err != nil {
errChan <- fmt.Errorf("syncing rate limit: %w", err)
return
}
resultChan <- r.Response.Cards
if r.Response.Cursor.Value.Total.Value < maxProductsPerRequest {
return
}
request.Settings.Value.Cursor.Value.UpdatedAt.SetTo(r.Response.Cursor.Value.UpdatedAt.Value)
request.Settings.Value.Cursor.Value.NmID.SetTo(r.Response.Cursor.Value.NmID.Value)
case *wbapi.R429Headers:
err = wb.SetRateLimitRetry(ctx, sellerId, r.XRatelimitRetry.Value, r.XRatelimitLimit.Value, r.XRatelimitReset.Value)
if err != nil {
errChan <- fmt.Errorf("setting rate limit retry: %w", err)
return
}
default:
errChan <- fmt.Errorf("unexpected response type: %T", r)
return
}
}
}
func (a apiRepository) StreamAllProductsCache(ctx context.Context, marketplaceId int, resultChan chan<- []pb.Product, errChan chan<- error) {
defer close(resultChan)
defer close(errChan)
_, sellerId, err := a.ParseMarketplace(ctx, marketplaceId)
if err != nil {
errChan <- err
return
}
c := *redis.Client
key := fmt.Sprintf("wb:products:%s", sellerId)
jsonString, err := c.Do(ctx, c.B().Get().Key(key).Build()).ToString()
if err == nil && jsonString != "null" {
var result []pb.Product
err = json.Unmarshal([]byte(jsonString), &result)
if err != nil {
errChan <- fmt.Errorf("unmarshalling products from cache: %w", err)
return
}
task, err := types.NewFetchProductsTask(marketplaceId)
if err != nil {
errChan <- fmt.Errorf("creating fetch products task: %w", err)
return
}
_, err = client.Client.Enqueue(task)
if err != nil {
errChan <- fmt.Errorf("enqueueing fetch products task: %w", err)
return
}
resultChan <- result
return
}
if !errors.As(err, &rueidis.Nil) && err != nil {
errChan <- fmt.Errorf("fetching products from cache: %w", err)
return
}
converter := generated.ConverterImpl{}
innerResultChan := make(chan []WbProduct)
innerErrChan := make(chan error)
go a.StreamAllProducts(ctx, marketplaceId, innerResultChan, innerErrChan)
var allProducts []pb.Product
defer func() {
jsonData, err := json.Marshal(allProducts)
if err != nil {
errChan <- fmt.Errorf("marshalling products to cache: %w", err)
return
}
err = c.Do(ctx, c.B().Set().Key(key).Value(string(jsonData)).Build()).Error()
if err != nil {
errChan <- fmt.Errorf("setting products to cache: %w", err)
return
}
}()
for {
select {
case err, ok := <-innerErrChan:
if !ok {
return
}
errChan <- fmt.Errorf("streaming products: %w", err)
return
case products, ok := <-innerResultChan:
if !ok {
return
}
pbProducts := lo.Map(products, func(p WbProduct, _ int) pb.Product {
return *converter.ToProto(&p)
})
allProducts = append(allProducts, pbProducts...)
resultChan <- pbProducts
}
}
}
func (a apiRepository) GetAllProducts(ctx context.Context, marketplaceId int) ([]WbProduct, error) {
marketplaceByID, sellerId, err := a.ParseMarketplace(ctx, marketplaceId)
fromMarketplace, err := wb.GetClientFromMarketplace(marketplaceByID)
if err != nil {
return nil, err
}
resultChan := make(chan []WbProduct)
errChan := make(chan error)
go fetchProducts(ctx, fromMarketplace, sellerId, resultChan, errChan)
var products []WbProduct
isWaiting := true
for {
if !isWaiting {
break
}
select {
case err, ok := <-errChan:
if !ok {
isWaiting = false
continue
}
return nil, err
case newProducts, ok := <-resultChan:
if !ok {
isWaiting = false
}
products = append(products, newProducts...)
}
}
return products, nil
}
func (a apiRepository) StreamAllProducts(ctx context.Context, marketplaceId int, resultChan chan<- []WbProduct, errChan chan<- error) {
marketplaceByID, sellerId, err := a.ParseMarketplace(ctx, marketplaceId)
if err != nil {
errChan <- err
return
}
fromMarketplace, err := wb.GetClientFromMarketplace(marketplaceByID)
if err != nil {
errChan <- err
return
}
go fetchProducts(ctx, fromMarketplace, sellerId, resultChan, errChan)
}
func NewAPIRepository(marketplaceRepository marketplace.Repository) Repository {
return &apiRepository{
marketplaceRepository: marketplaceRepository,
}
}

170
internal/wb/rate_limiter.go Normal file
View File

@@ -0,0 +1,170 @@
package wb
import (
"context"
"encoding/json"
"fmt"
"github.com/redis/rueidis"
"net/http"
"sipro-mps/internal/redis"
"time"
)
const (
defaultBucketCapacity = 10 // max burst size
refillRate = 100.0 / 60000 // 300 requests per minute → 1 token per 200ms
tokenTTLMillis = 60000 // Redis key TTL: 60s
)
var tokenBucketScript = rueidis.NewLuaScript(`
local key = KEYS[1]
local now = tonumber(ARGV[1])
local default_capacity = tonumber(ARGV[2])
local refill_rate = tonumber(ARGV[3])
local ttl = tonumber(ARGV[4])
-- Retry lock
local retry_key = key .. ":retry_until"
local retry_until = tonumber(redis.call("GET", retry_key))
if retry_until and now < retry_until then
return retry_until - now
end
-- Token Bucket
local capacity_key = key .. ":capacity"
local token_key = key .. ":tokens"
local time_key = key .. ":last_refill"
local capacity = tonumber(redis.call("GET", capacity_key)) or default_capacity
local tokens = tonumber(redis.call("GET", token_key))
local last_refill = tonumber(redis.call("GET", time_key))
if tokens == nil then tokens = capacity end
if last_refill == nil then last_refill = now end
local elapsed = now - last_refill
local refill = elapsed * refill_rate
tokens = math.min(capacity, tokens + refill)
last_refill = now
if tokens >= 1 then
tokens = tokens - 1
redis.call("SET", token_key, tokens)
redis.call("SET", time_key, last_refill)
redis.call("PEXPIRE", token_key, ttl)
redis.call("PEXPIRE", time_key, ttl)
return 0
else
local wait_time = math.ceil((1 - tokens) / refill_rate)
return wait_time
end
`)
type RateLimitTransport struct {
http.RoundTripper
}
func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := req.Context()
tokenString := req.Header.Get("Authorization")
authData := NewWbAuthData(tokenString)
authDataBytes, err := json.Marshal(authData)
if err != nil {
return nil, fmt.Errorf("failed to marshal Wildberries auth data: %w", err)
}
_, claims, err := DecodeWildberriesJwt(authDataBytes)
if err != nil {
return nil, fmt.Errorf("failed to decode Wildberries JWT: %w", err)
}
sellerId := claims["sid"].(string)
if sellerId == "" {
return nil, fmt.Errorf("sellerId is required in JWT claims")
}
now := time.Now().UnixMilli()
client := *redis.Client
waitTime, err := tokenBucketScript.Exec(ctx, client, []string{sellerId}, []string{
fmt.Sprintf("%d", now),
fmt.Sprintf("%d", defaultBucketCapacity),
fmt.Sprintf("%f", refillRate),
fmt.Sprintf("%d", tokenTTLMillis),
}).ToInt64()
if err != nil {
return nil, fmt.Errorf("rate limit script error: %w", err)
}
if waitTime > 0 {
select {
case <-time.After(time.Duration(waitTime) * time.Millisecond):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return t.RoundTripper.RoundTrip(req)
}
func SyncRateLimitRemaining(ctx context.Context, sellerId string, remaining int) error {
if sellerId == "" || remaining < 0 {
return fmt.Errorf("invalid sellerId or remaining")
}
now := time.Now().UnixMilli()
client := *redis.Client
cmds := []rueidis.Completed{
client.B().Set().Key(sellerId + ":capacity").Value(fmt.Sprintf("%d", defaultBucketCapacity)).Ex(time.Minute).Build(),
client.B().Set().Key(sellerId + ":tokens").Value(fmt.Sprintf("%d", remaining)).Ex(time.Minute).Build(),
client.B().Set().Key(sellerId + ":last_refill").Value(fmt.Sprintf("%d", now)).Ex(time.Minute).Build(),
}
results := client.DoMulti(ctx, cmds...)
for _, res := range results {
if res.Error() != nil {
return fmt.Errorf("failed to sync rate limit: %w", res.Error())
}
}
return nil
}
func SetRateLimitRetry(ctx context.Context, sellerId string, retrySeconds int, limit int, resetSeconds int) error {
if sellerId == "" {
return fmt.Errorf("sellerId is required")
}
now := time.Now()
retryUntil := now.Add(time.Duration(retrySeconds) * time.Second).UnixMilli()
client := *redis.Client
cmds := []rueidis.Completed{
client.B().Set().
Key(sellerId + ":retry_until").
Value(fmt.Sprintf("%d", retryUntil)).
Px(time.Duration(retrySeconds+5) * time.Second).Build(),
}
if limit > 0 {
cmds = append(cmds, client.B().Set().
Key(sellerId+":capacity").
Value(fmt.Sprintf("%d", limit)).
Ex(time.Hour).Build())
}
if resetSeconds > 0 {
resetAt := now.Add(time.Duration(resetSeconds) * time.Second)
fmt.Printf("Seller %s rate limit resets at %v (limit: %d)\n", sellerId, resetAt, limit)
}
results := client.DoMulti(ctx, cmds...)
for _, res := range results {
if res.Error() != nil {
return fmt.Errorf("failed to set retry info: %w", res.Error())
}
}
return nil
}
func NewRateLimitTransport() *RateLimitTransport {
return &RateLimitTransport{RoundTripper: http.DefaultTransport}
}

View File

@@ -0,0 +1,22 @@
package wb
import (
"context"
wbclient "sipro-mps/pkg/api/wb/client"
)
type WildberriesSecurityHandler struct {
ApiKey string
}
func (sh WildberriesSecurityHandler) HeaderApiKey(ctx context.Context, operationName wbclient.OperationName, client *wbclient.Client) (wbclient.HeaderApiKey, error) {
return wbclient.HeaderApiKey{
APIKey: sh.ApiKey,
Roles: nil,
}, nil
}
func NewWildberriesSecurityHandler(apiKey string) WildberriesSecurityHandler {
return WildberriesSecurityHandler{
ApiKey: apiKey,
}
}

1
internal/wb/types.go Normal file
View File

@@ -0,0 +1 @@
package wb

5572
pkg/api/wb/02-products.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/otelogen"
)
var (
// Allocate option closure once.
clientSpanKind = trace.WithSpanKind(trace.SpanKindClient)
// Allocate option closure once.
serverSpanKind = trace.WithSpanKind(trace.SpanKindServer)
)
type (
optionFunc[C any] func(*C)
otelOptionFunc func(*otelConfig)
)
type otelConfig struct {
TracerProvider trace.TracerProvider
Tracer trace.Tracer
MeterProvider metric.MeterProvider
Meter metric.Meter
}
func (cfg *otelConfig) initOTEL() {
if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}
if cfg.MeterProvider == nil {
cfg.MeterProvider = otel.GetMeterProvider()
}
cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name,
trace.WithInstrumentationVersion(otelogen.SemVersion()),
)
cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name,
metric.WithInstrumentationVersion(otelogen.SemVersion()),
)
}
// ErrorHandler is error handler.
type ErrorHandler = ogenerrors.ErrorHandler
type serverConfig struct {
otelConfig
NotFound http.HandlerFunc
MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)
ErrorHandler ErrorHandler
Prefix string
Middleware Middleware
MaxMultipartMemory int64
}
// ServerOption is server config option.
type ServerOption interface {
applyServer(*serverConfig)
}
var _ ServerOption = (optionFunc[serverConfig])(nil)
func (o optionFunc[C]) applyServer(c *C) {
o(c)
}
var _ ServerOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyServer(c *serverConfig) {
o(&c.otelConfig)
}
func newServerConfig(opts ...ServerOption) serverConfig {
cfg := serverConfig{
NotFound: http.NotFound,
MethodNotAllowed: func(w http.ResponseWriter, r *http.Request, allowed string) {
status := http.StatusMethodNotAllowed
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Methods", allowed)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
status = http.StatusNoContent
} else {
w.Header().Set("Allow", allowed)
}
w.WriteHeader(status)
},
ErrorHandler: ogenerrors.DefaultErrorHandler,
Middleware: nil,
MaxMultipartMemory: 32 << 20, // 32 MB
}
for _, opt := range opts {
opt.applyServer(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseServer struct {
cfg serverConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) {
s.cfg.NotFound(w, r)
}
func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, allowed string) {
s.cfg.MethodNotAllowed(w, r, allowed)
}
func (cfg serverConfig) baseServer() (s baseServer, err error) {
s = baseServer{cfg: cfg}
if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil {
return s, err
}
return s, nil
}
type clientConfig struct {
otelConfig
Client ht.Client
}
// ClientOption is client config option.
type ClientOption interface {
applyClient(*clientConfig)
}
var _ ClientOption = (optionFunc[clientConfig])(nil)
func (o optionFunc[C]) applyClient(c *C) {
o(c)
}
var _ ClientOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyClient(c *clientConfig) {
o(&c.otelConfig)
}
func newClientConfig(opts ...ClientOption) clientConfig {
cfg := clientConfig{
Client: http.DefaultClient,
}
for _, opt := range opts {
opt.applyClient(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseClient struct {
cfg clientConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (cfg clientConfig) baseClient() (c baseClient, err error) {
c = baseClient{cfg: cfg}
if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil {
return c, err
}
return c, nil
}
// Option is config option.
type Option interface {
ServerOption
ClientOption
}
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
//
// If none is specified, the global provider is used.
func WithTracerProvider(provider trace.TracerProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.TracerProvider = provider
}
})
}
// WithMeterProvider specifies a meter provider to use for creating a meter.
//
// If none is specified, the otel.GetMeterProvider() is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.MeterProvider = provider
}
})
}
// WithClient specifies http client to use.
func WithClient(client ht.Client) ClientOption {
return optionFunc[clientConfig](func(cfg *clientConfig) {
if client != nil {
cfg.Client = client
}
})
}
// WithNotFound specifies Not Found handler to use.
func WithNotFound(notFound http.HandlerFunc) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if notFound != nil {
cfg.NotFound = notFound
}
})
}
// WithMethodNotAllowed specifies Method Not Allowed handler to use.
func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if methodNotAllowed != nil {
cfg.MethodNotAllowed = methodNotAllowed
}
})
}
// WithErrorHandler specifies error handler to use.
func WithErrorHandler(h ErrorHandler) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if h != nil {
cfg.ErrorHandler = h
}
})
}
// WithPathPrefix specifies server path prefix.
func WithPathPrefix(prefix string) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
cfg.Prefix = prefix
})
}
// WithMiddleware specifies middlewares to use.
func WithMiddleware(m ...Middleware) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
switch len(m) {
case 0:
cfg.Middleware = nil
case 1:
cfg.Middleware = m[0]
default:
cfg.Middleware = middleware.ChainMiddlewares(m...)
}
})
}
// WithMaxMultipartMemory specifies limit of memory for storing file parts.
// File parts which can't be stored in memory will be stored on disk in temporary files.
func WithMaxMultipartMemory(max int64) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if max > 0 {
cfg.MaxMultipartMemory = max
}
})
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,166 @@
// Code generated by ogen, DO NOT EDIT.
package api
type APIV2BufferGoodsTaskGetRes interface {
aPIV2BufferGoodsTaskGetRes()
}
type APIV2BufferTasksGetRes interface {
aPIV2BufferTasksGetRes()
}
type APIV2HistoryGoodsTaskGetRes interface {
aPIV2HistoryGoodsTaskGetRes()
}
type APIV2HistoryTasksGetRes interface {
aPIV2HistoryTasksGetRes()
}
type APIV2ListGoodsFilterGetRes interface {
aPIV2ListGoodsFilterGetRes()
}
type APIV2ListGoodsSizeNmGetRes interface {
aPIV2ListGoodsSizeNmGetRes()
}
type APIV2QuarantineGoodsGetRes interface {
aPIV2QuarantineGoodsGetRes()
}
type APIV2UploadTaskClubDiscountPostRes interface {
aPIV2UploadTaskClubDiscountPostRes()
}
type APIV2UploadTaskPostRes interface {
aPIV2UploadTaskPostRes()
}
type APIV2UploadTaskSizePostRes interface {
aPIV2UploadTaskSizePostRes()
}
type APIV3OfficesGetRes interface {
aPIV3OfficesGetRes()
}
type APIV3StocksWarehouseIdDeleteRes interface {
aPIV3StocksWarehouseIdDeleteRes()
}
type APIV3StocksWarehouseIdPostRes interface {
aPIV3StocksWarehouseIdPostRes()
}
type APIV3StocksWarehouseIdPutRes interface {
aPIV3StocksWarehouseIdPutRes()
}
type APIV3WarehousesGetRes interface {
aPIV3WarehousesGetRes()
}
type APIV3WarehousesPostRes interface {
aPIV3WarehousesPostRes()
}
type APIV3WarehousesWarehouseIdDeleteRes interface {
aPIV3WarehousesWarehouseIdDeleteRes()
}
type APIV3WarehousesWarehouseIdPutRes interface {
aPIV3WarehousesWarehouseIdPutRes()
}
type ContentV2BarcodesPostRes interface {
contentV2BarcodesPostRes()
}
type ContentV2CardsDeleteTrashPostRes interface {
contentV2CardsDeleteTrashPostRes()
}
type ContentV2CardsErrorListGetRes interface {
contentV2CardsErrorListGetRes()
}
type ContentV2CardsLimitsGetRes interface {
contentV2CardsLimitsGetRes()
}
type ContentV2CardsRecoverPostRes interface {
contentV2CardsRecoverPostRes()
}
type ContentV2CardsUpdatePostRes interface {
contentV2CardsUpdatePostRes()
}
type ContentV2CardsUploadAddPostRes interface {
contentV2CardsUploadAddPostRes()
}
type ContentV2CardsUploadPostRes interface {
contentV2CardsUploadPostRes()
}
type ContentV2DirectoryColorsGetRes interface {
contentV2DirectoryColorsGetRes()
}
type ContentV2DirectoryCountriesGetRes interface {
contentV2DirectoryCountriesGetRes()
}
type ContentV2DirectoryKindsGetRes interface {
contentV2DirectoryKindsGetRes()
}
type ContentV2DirectorySeasonsGetRes interface {
contentV2DirectorySeasonsGetRes()
}
type ContentV2DirectoryTnvedGetRes interface {
contentV2DirectoryTnvedGetRes()
}
type ContentV2DirectoryVatGetRes interface {
contentV2DirectoryVatGetRes()
}
type ContentV2GetCardsListPostRes interface {
contentV2GetCardsListPostRes()
}
type ContentV2GetCardsTrashPostRes interface {
contentV2GetCardsTrashPostRes()
}
type ContentV2ObjectAllGetRes interface {
contentV2ObjectAllGetRes()
}
type ContentV2ObjectCharcsSubjectIdGetRes interface {
contentV2ObjectCharcsSubjectIdGetRes()
}
type ContentV2ObjectParentAllGetRes interface {
contentV2ObjectParentAllGetRes()
}
type ContentV2TagNomenclatureLinkPostRes interface {
contentV2TagNomenclatureLinkPostRes()
}
type ContentV2TagsGetRes interface {
contentV2TagsGetRes()
}
type ContentV3MediaFilePostRes interface {
contentV3MediaFilePostRes()
}
type ContentV3MediaSavePostRes interface {
contentV3MediaSavePostRes()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"go.opentelemetry.io/otel/attribute"
)
// Labeler is used to allow adding custom attributes to the server request metrics.
type Labeler struct {
attrs []attribute.KeyValue
}
// Add attributes to the Labeler.
func (l *Labeler) Add(attrs ...attribute.KeyValue) {
l.attrs = append(l.attrs, attrs...)
}
// AttributeSet returns the attributes added to the Labeler as an attribute.Set.
func (l *Labeler) AttributeSet() attribute.Set {
return attribute.NewSet(l.attrs...)
}
type labelerContextKey struct{}
// LabelerFromContext retrieves the Labeler from the provided context, if present.
//
// If no Labeler was found in the provided context a new, empty Labeler is returned and the second
// return value is false. In this case it is safe to use the Labeler but any attributes added to
// it will not be used.
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok {
return l, true
}
return &Labeler{}, false
}
func contextWithLabeler(ctx context.Context, l *Labeler) context.Context {
return context.WithValue(ctx, labelerContextKey{}, l)
}

View File

@@ -0,0 +1,10 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"github.com/ogen-go/ogen/middleware"
)
// Middleware is middleware type.
type Middleware = middleware.Middleware

View File

@@ -0,0 +1,50 @@
// Code generated by ogen, DO NOT EDIT.
package api
// OperationName is the ogen operation name
type OperationName = string
const (
APIV2BufferGoodsTaskGetOperation OperationName = "APIV2BufferGoodsTaskGet"
APIV2BufferTasksGetOperation OperationName = "APIV2BufferTasksGet"
APIV2HistoryGoodsTaskGetOperation OperationName = "APIV2HistoryGoodsTaskGet"
APIV2HistoryTasksGetOperation OperationName = "APIV2HistoryTasksGet"
APIV2ListGoodsFilterGetOperation OperationName = "APIV2ListGoodsFilterGet"
APIV2ListGoodsSizeNmGetOperation OperationName = "APIV2ListGoodsSizeNmGet"
APIV2QuarantineGoodsGetOperation OperationName = "APIV2QuarantineGoodsGet"
APIV2UploadTaskClubDiscountPostOperation OperationName = "APIV2UploadTaskClubDiscountPost"
APIV2UploadTaskPostOperation OperationName = "APIV2UploadTaskPost"
APIV2UploadTaskSizePostOperation OperationName = "APIV2UploadTaskSizePost"
APIV3OfficesGetOperation OperationName = "APIV3OfficesGet"
APIV3StocksWarehouseIdDeleteOperation OperationName = "APIV3StocksWarehouseIdDelete"
APIV3StocksWarehouseIdPostOperation OperationName = "APIV3StocksWarehouseIdPost"
APIV3StocksWarehouseIdPutOperation OperationName = "APIV3StocksWarehouseIdPut"
APIV3WarehousesGetOperation OperationName = "APIV3WarehousesGet"
APIV3WarehousesPostOperation OperationName = "APIV3WarehousesPost"
APIV3WarehousesWarehouseIdDeleteOperation OperationName = "APIV3WarehousesWarehouseIdDelete"
APIV3WarehousesWarehouseIdPutOperation OperationName = "APIV3WarehousesWarehouseIdPut"
ContentV2BarcodesPostOperation OperationName = "ContentV2BarcodesPost"
ContentV2CardsDeleteTrashPostOperation OperationName = "ContentV2CardsDeleteTrashPost"
ContentV2CardsErrorListGetOperation OperationName = "ContentV2CardsErrorListGet"
ContentV2CardsLimitsGetOperation OperationName = "ContentV2CardsLimitsGet"
ContentV2CardsRecoverPostOperation OperationName = "ContentV2CardsRecoverPost"
ContentV2CardsUpdatePostOperation OperationName = "ContentV2CardsUpdatePost"
ContentV2CardsUploadAddPostOperation OperationName = "ContentV2CardsUploadAddPost"
ContentV2CardsUploadPostOperation OperationName = "ContentV2CardsUploadPost"
ContentV2DirectoryColorsGetOperation OperationName = "ContentV2DirectoryColorsGet"
ContentV2DirectoryCountriesGetOperation OperationName = "ContentV2DirectoryCountriesGet"
ContentV2DirectoryKindsGetOperation OperationName = "ContentV2DirectoryKindsGet"
ContentV2DirectorySeasonsGetOperation OperationName = "ContentV2DirectorySeasonsGet"
ContentV2DirectoryTnvedGetOperation OperationName = "ContentV2DirectoryTnvedGet"
ContentV2DirectoryVatGetOperation OperationName = "ContentV2DirectoryVatGet"
ContentV2GetCardsListPostOperation OperationName = "ContentV2GetCardsListPost"
ContentV2GetCardsTrashPostOperation OperationName = "ContentV2GetCardsTrashPost"
ContentV2ObjectAllGetOperation OperationName = "ContentV2ObjectAllGet"
ContentV2ObjectCharcsSubjectIdGetOperation OperationName = "ContentV2ObjectCharcsSubjectIdGet"
ContentV2ObjectParentAllGetOperation OperationName = "ContentV2ObjectParentAllGet"
ContentV2TagNomenclatureLinkPostOperation OperationName = "ContentV2TagNomenclatureLinkPost"
ContentV2TagsGetOperation OperationName = "ContentV2TagsGet"
ContentV3MediaFilePostOperation OperationName = "ContentV3MediaFilePost"
ContentV3MediaSavePostOperation OperationName = "ContentV3MediaSavePost"
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,315 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"bytes"
"mime"
"mime/multipart"
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/uri"
)
func encodeAPIV2UploadTaskClubDiscountPostRequest(
req *APIV2UploadTaskClubDiscountPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV2UploadTaskPostRequest(
req *APIV2UploadTaskPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV2UploadTaskSizePostRequest(
req *APIV2UploadTaskSizePostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV3StocksWarehouseIdDeleteRequest(
req *APIV3StocksWarehouseIdDeleteReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV3StocksWarehouseIdPostRequest(
req *APIV3StocksWarehouseIdPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV3StocksWarehouseIdPutRequest(
req OptAPIV3StocksWarehouseIdPutReq,
r *http.Request,
) error {
const contentType = "application/json"
if !req.Set {
// Keep request with empty body if value is not set.
return nil
}
e := new(jx.Encoder)
{
if req.Set {
req.Encode(e)
}
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV3WarehousesPostRequest(
req *APIV3WarehousesPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeAPIV3WarehousesWarehouseIdPutRequest(
req *APIV3WarehousesWarehouseIdPutReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2BarcodesPostRequest(
req *ContentV2BarcodesPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2CardsDeleteTrashPostRequest(
req *ContentV2CardsDeleteTrashPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2CardsRecoverPostRequest(
req *ContentV2CardsRecoverPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2CardsUpdatePostRequest(
req []ContentV2CardsUpdatePostReqItem,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
if req != nil {
e.ArrStart()
for _, elem := range req {
elem.Encode(e)
}
e.ArrEnd()
}
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2CardsUploadAddPostRequest(
req OptContentV2CardsUploadAddPostReq,
r *http.Request,
) error {
const contentType = "application/json"
if !req.Set {
// Keep request with empty body if value is not set.
return nil
}
e := new(jx.Encoder)
{
if req.Set {
req.Encode(e)
}
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2CardsUploadPostRequest(
req []ContentV2CardsUploadPostReqItem,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
if req != nil {
e.ArrStart()
for _, elem := range req {
elem.Encode(e)
}
e.ArrEnd()
}
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2GetCardsListPostRequest(
req *ContentV2GetCardsListPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2GetCardsTrashPostRequest(
req *ContentV2GetCardsTrashPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV2TagNomenclatureLinkPostRequest(
req *ContentV2TagNomenclatureLinkPostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeContentV3MediaFilePostRequest(
req *ContentV3MediaFilePostReq,
r *http.Request,
) error {
const contentType = "multipart/form-data"
request := req
q := uri.NewFormEncoder(map[string]string{})
body, boundary := ht.CreateMultipartBody(func(w *multipart.Writer) error {
if val, ok := request.Uploadfile.Get(); ok {
if err := val.WriteMultipart("uploadfile", w); err != nil {
return errors.Wrap(err, "write \"uploadfile\"")
}
}
if err := q.WriteMultipart(w); err != nil {
return errors.Wrap(err, "write multipart")
}
return nil
})
ht.SetCloserBody(r, body, mime.FormatMediaType(contentType, map[string]string{"boundary": boundary}))
return nil
}
func encodeContentV3MediaSavePostRequest(
req *ContentV3MediaSavePostReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"net/http"
"strings"
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/ogenerrors"
)
// SecurityHandler is handler for security parameters.
type SecurityHandler interface {
// HandleHeaderApiKey handles HeaderApiKey security.
HandleHeaderApiKey(ctx context.Context, operationName OperationName, t HeaderApiKey) (context.Context, error)
}
func findAuthorization(h http.Header, prefix string) (string, bool) {
v, ok := h["Authorization"]
if !ok {
return "", false
}
for _, vv := range v {
scheme, value, ok := strings.Cut(vv, " ")
if !ok || !strings.EqualFold(scheme, prefix) {
continue
}
return value, true
}
return "", false
}
var operationRolesHeaderApiKey = map[string][]string{
APIV2BufferGoodsTaskGetOperation: []string{},
APIV2BufferTasksGetOperation: []string{},
APIV2HistoryGoodsTaskGetOperation: []string{},
APIV2HistoryTasksGetOperation: []string{},
APIV2ListGoodsFilterGetOperation: []string{},
APIV2ListGoodsSizeNmGetOperation: []string{},
APIV2QuarantineGoodsGetOperation: []string{},
APIV2UploadTaskClubDiscountPostOperation: []string{},
APIV2UploadTaskPostOperation: []string{},
APIV2UploadTaskSizePostOperation: []string{},
APIV3OfficesGetOperation: []string{},
APIV3StocksWarehouseIdDeleteOperation: []string{},
APIV3StocksWarehouseIdPostOperation: []string{},
APIV3StocksWarehouseIdPutOperation: []string{},
APIV3WarehousesGetOperation: []string{},
APIV3WarehousesPostOperation: []string{},
APIV3WarehousesWarehouseIdDeleteOperation: []string{},
APIV3WarehousesWarehouseIdPutOperation: []string{},
ContentV2BarcodesPostOperation: []string{},
ContentV2CardsDeleteTrashPostOperation: []string{},
ContentV2CardsErrorListGetOperation: []string{},
ContentV2CardsLimitsGetOperation: []string{},
ContentV2CardsRecoverPostOperation: []string{},
ContentV2CardsUpdatePostOperation: []string{},
ContentV2CardsUploadAddPostOperation: []string{},
ContentV2CardsUploadPostOperation: []string{},
ContentV2DirectoryColorsGetOperation: []string{},
ContentV2DirectoryCountriesGetOperation: []string{},
ContentV2DirectoryKindsGetOperation: []string{},
ContentV2DirectorySeasonsGetOperation: []string{},
ContentV2DirectoryTnvedGetOperation: []string{},
ContentV2DirectoryVatGetOperation: []string{},
ContentV2GetCardsListPostOperation: []string{},
ContentV2GetCardsTrashPostOperation: []string{},
ContentV2ObjectAllGetOperation: []string{},
ContentV2ObjectCharcsSubjectIdGetOperation: []string{},
ContentV2ObjectParentAllGetOperation: []string{},
ContentV2TagNomenclatureLinkPostOperation: []string{},
ContentV2TagsGetOperation: []string{},
ContentV3MediaFilePostOperation: []string{},
ContentV3MediaSavePostOperation: []string{},
}
func (s *Server) securityHeaderApiKey(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
var t HeaderApiKey
const parameterName = "Authorization"
value := req.Header.Get(parameterName)
if value == "" {
return ctx, false, nil
}
t.APIKey = value
t.Roles = operationRolesHeaderApiKey[operationName]
rctx, err := s.sec.HandleHeaderApiKey(ctx, operationName, t)
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
return nil, false, nil
} else if err != nil {
return nil, false, err
}
return rctx, true, err
}
// SecuritySource is provider of security values (tokens, passwords, etc.).
type SecuritySource interface {
// HeaderApiKey provides HeaderApiKey security value.
HeaderApiKey(ctx context.Context, operationName OperationName, client *Client) (HeaderApiKey, error)
}
func (s *Client) securityHeaderApiKey(ctx context.Context, operationName OperationName, req *http.Request) error {
t, err := s.sec.HeaderApiKey(ctx, operationName, s)
if err != nil {
return errors.Wrap(err, "security source \"HeaderApiKey\"")
}
req.Header.Set("Authorization", t.APIKey)
return nil
}

View File

@@ -0,0 +1,951 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
)
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// APIV2BufferGoodsTaskGet implements GET /api/v2/buffer/goods/task operation.
//
// Метод предоставляет информацию о товарах и ошибках в
// товарах из загрузки в обработке.
// <div class="description_important">
// Необработанная загрузка — это загрузка скидок для <a
// href="/openapi/promotion#tag/Kalendar-akcij">календаря акций</a>. Такие
// скидки применятся к товарам только в момент начала
// акции.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/buffer/goods/task
APIV2BufferGoodsTaskGet(ctx context.Context, params APIV2BufferGoodsTaskGetParams) (APIV2BufferGoodsTaskGetRes, error)
// APIV2BufferTasksGet implements GET /api/v2/buffer/tasks operation.
//
// Метод предоставляет информацию про загрузку скидок в
// обработке.
// <div class="description_important">
// Необработанная загрузка — это загрузка скидок для <a
// href="/openapi/promotion#tag/Kalendar-akcij">календаря акций</a>. Такие
// скидки применятся к товарам только в момент начала
// акции.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/buffer/tasks
APIV2BufferTasksGet(ctx context.Context, params APIV2BufferTasksGetParams) (APIV2BufferTasksGetRes, error)
// APIV2HistoryGoodsTaskGet implements GET /api/v2/history/goods/task operation.
//
// Метод предоставляет информацию о товарах и об
// ошибках в товарах в обработанной загрузке.
// <div class="description_important">
// Обработанная загрузка — это загрузка цен и скидок
// для <a
// href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task/post">товаров</a> и <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1size/post">размеров товаров</a>, а также скидок <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1club-discount/post">WB Клуба</a>.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/history/goods/task
APIV2HistoryGoodsTaskGet(ctx context.Context, params APIV2HistoryGoodsTaskGetParams) (APIV2HistoryGoodsTaskGetRes, error)
// APIV2HistoryTasksGet implements GET /api/v2/history/tasks operation.
//
// Метод предоставляет информацию об обработанной
// загрузке цен и скидок.
// <div class="description_important">
// Обработанная загрузка — это загрузка цен и скидок
// для <a
// href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task/post">товаров</a> и <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1size/post">размеров товаров</a>, а также скидок <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1club-discount/post">WB Клуба</a>.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/history/tasks
APIV2HistoryTasksGet(ctx context.Context, params APIV2HistoryTasksGetParams) (APIV2HistoryTasksGetRes, error)
// APIV2ListGoodsFilterGet implements GET /api/v2/list/goods/filter operation.
//
// Метод предоставляет информацию о товарах по их
// артикулам: цены, валюту, общие скидки и скидки для [WB
// Клуба](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1club-discount/post).
// <br><br>
// Чтобы получить информацию обо всех товарах продавца,
// оставьте артикул пустым, установите `limit=1000`, в
// параметре `offset` установите смещение по количеству
// записей. Количество нужно рассчитать по формуле: `offset`
// плюс `limit` из предыдущего запроса. Повторяйте запрос,
// пока вы не получите ответ с пустым массивом.<br> Чтобы
// получить информацию о размерах товара, используйте
// [отдельный
// метод](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1list~1goods~1size~1nm/get).
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/list/goods/filter
APIV2ListGoodsFilterGet(ctx context.Context, params APIV2ListGoodsFilterGetParams) (APIV2ListGoodsFilterGetRes, error)
// APIV2ListGoodsSizeNmGet implements GET /api/v2/list/goods/size/nm operation.
//
// Метод предоставляет информацию обо всех размерах
// одного товарам: цены, валюту, общие скидки и скидки
// для [WB
// Клуба](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1club-discount/post).
// <br><br>
// Работает только для товаров из категорий, где можно
// устанавливать цены отдельно для разных размеров. Для
// таких товаров `editableSizePrice: true`.
// <br><br>
// Чтобы получить информацию о самом товаре,
// используйте [отдельный
// метод](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1list~1goods~1filter/get).
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/list/goods/size/nm
APIV2ListGoodsSizeNmGet(ctx context.Context, params APIV2ListGoodsSizeNmGetParams) (APIV2ListGoodsSizeNmGetRes, error)
// APIV2QuarantineGoodsGet implements GET /api/v2/quarantine/goods operation.
//
// Метод предоставляет информацию о товарах в карантине.
// <br><br>
// Если новая цена товара со скидкой будет минимум в 3
// раза меньше старой, товар попадёт [в
// карантин](https://seller.wildberries.ru/discount-and-prices/quarantine) и будет
// продаваться по старой цене. Ошибка об этом будет в
// ответах методов [состояний
// загрузок](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1tasks/get).
// <br><br>
// Вы можете изменить цену или скидку с помощью API либо
// вывести товар из карантина [в личном
// кабинете](https://seller.wildberries.ru/discount-and-prices/quarantine).
// <br><br>
// Для товаров с [поразмерной установкой
// цен](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1size/post)
// карантин не применяется.
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// GET /api/v2/quarantine/goods
APIV2QuarantineGoodsGet(ctx context.Context, params APIV2QuarantineGoodsGetParams) (APIV2QuarantineGoodsGetRes, error)
// APIV2UploadTaskClubDiscountPost implements POST /api/v2/upload/task/club-discount operation.
//
// Устанавливает скидки для товаров в рамках подписки [WB
// Клуб](https://seller.wildberries.ru/help-center/article/A-337).
// <div class="description_important">
// Получить информацию о процессе установки цен и
// скидок можно с помощью методов <a
// href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1tasks/get">состояния</a> и <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1goods~1task/get">детализации</a> обработанной загрузки.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// POST /api/v2/upload/task/club-discount
APIV2UploadTaskClubDiscountPost(ctx context.Context, req *APIV2UploadTaskClubDiscountPostReq) (APIV2UploadTaskClubDiscountPostRes, error)
// APIV2UploadTaskPost implements POST /api/v2/upload/task operation.
//
// Метод устанавливает цены и скидки для товаров.
// <br><br>
// Чтобы установить цены и скидки для размеров товара,
// используйте [отдельный
// метод](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task~1size/post).
// <div class="description_important">
// Получить информацию о процессе установки цен и
// скидок можно с помощью методов <a
// href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1tasks/get">состояния</a> и <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1goods~1task/get">детализации</a> обработанной загрузки.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// POST /api/v2/upload/task
APIV2UploadTaskPost(ctx context.Context, req *APIV2UploadTaskPostReq) (APIV2UploadTaskPostRes, error)
// APIV2UploadTaskSizePost implements POST /api/v2/upload/task/size operation.
//
// Метод устанавливает цены отдельно для размеров
// товаров.
// Работает только для товаров из категорий, где можно
// устанавливать цены отдельно для разных размеров. Для
// [таких
// товаров](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1list~1goods~1size~1nm/get) `editableSizePrice: true`.
// Чтобы установить цены и скидки для самих товаров,
// используйте [отдельный
// метод](/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1upload~1task/post).
// <div class="description_important">
// Получить информацию о процессе установки цен и
// скидок можно с помощью методов <a
// href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1tasks/get">состояния</a> и <a href="/openapi/work-with-products#tag/Ceny-i-skidki/paths/~1api~1v2~1history~1goods~1task/get">детализации</a> обработанной загрузки.
// </div>
// <div class="description_limit">
// Максимум 10 запросов за 6 <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">секунд</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Цены и скидки</a>
// на один аккаунт продавца
// </div>.
//
// POST /api/v2/upload/task/size
APIV2UploadTaskSizePost(ctx context.Context, req *APIV2UploadTaskSizePostReq) (APIV2UploadTaskSizePostRes, error)
// APIV3OfficesGet implements GET /api/v3/offices operation.
//
// Метод предоставляет список всех складов WB для
// привязки к складам продавца. Предназначен для
// определения складов WB, чтобы сдавать готовые заказы
// по схеме [FBS](/openapi/orders-fbs#tag/Zakazy-FBS) (Fulfillment by Seller).
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// GET /api/v3/offices
APIV3OfficesGet(ctx context.Context) (APIV3OfficesGetRes, error)
// APIV3StocksWarehouseIdDelete implements DELETE /api/v3/stocks/{warehouseId} operation.
//
// Метод удаляет запись об остатках товаров продавца из
// [списка
// остатков](/openapi/work-with-products#tag/Ostatki-na-skladah-prodavca/paths/~1api~1v3~1stocks~1%7BwarehouseId%7D/post).
// <div class="description_important">
// <strong>Действие необратимо</strong>. Удаленный остаток
// будет необходимо загрузить повторно для
// возобновления продаж.
// </div>
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// DELETE /api/v3/stocks/{warehouseId}
APIV3StocksWarehouseIdDelete(ctx context.Context, req *APIV3StocksWarehouseIdDeleteReq, params APIV3StocksWarehouseIdDeleteParams) (APIV3StocksWarehouseIdDeleteRes, error)
// APIV3StocksWarehouseIdPost implements POST /api/v3/stocks/{warehouseId} operation.
//
// Метод предоставляет данные об остатках товаров на
// [складах продавца](/openapi/work-with-products#tag/Sklady-prodavca).
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// POST /api/v3/stocks/{warehouseId}
APIV3StocksWarehouseIdPost(ctx context.Context, req *APIV3StocksWarehouseIdPostReq, params APIV3StocksWarehouseIdPostParams) (APIV3StocksWarehouseIdPostRes, error)
// APIV3StocksWarehouseIdPut implements PUT /api/v3/stocks/{warehouseId} operation.
//
// Метод обновляет количество остатков товаров
// продавца [в
// списке](/openapi/work-with-products#tag/Ostatki-na-skladah-prodavca/paths/~1api~1v3~1stocks~1%7BwarehouseId%7D/post).
// <div class="description_important">
// Названия параметров запроса не валидируются. При
// отправке некорректных названий вы получите успешный
// ответ (<code>204</code>), но остатки не обновятся.
// </div>
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// PUT /api/v3/stocks/{warehouseId}
APIV3StocksWarehouseIdPut(ctx context.Context, req OptAPIV3StocksWarehouseIdPutReq, params APIV3StocksWarehouseIdPutParams) (APIV3StocksWarehouseIdPutRes, error)
// APIV3WarehousesGet implements GET /api/v3/warehouses operation.
//
// Метод предоставляет список всех складов продавца.
// Может использоваться для получения [остатков
// товаров](/openapi/work-with-products#tag/Ostatki-na-skladah-prodavca/paths/~1api~1v3~1stocks~1%7BwarehouseId%7D/post).
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// GET /api/v3/warehouses
APIV3WarehousesGet(ctx context.Context) (APIV3WarehousesGetRes, error)
// APIV3WarehousesPost implements POST /api/v3/warehouses operation.
//
// Метод создаёт склад продавца для работы с [остатками
// товаров](/openapi/work-with-products#tag/Ostatki-na-skladah-prodavca/paths/~1api~1v3~1stocks~1%7BwarehouseId%7D/post). Нужно привязать к складу продавца [склад WB](/openapi/work-with-products#tag/Sklady-prodavca/paths/~1api~1v3~1offices/get) для работы по схеме [FBS](/openapi/orders-fbs#tag/Zakazy-FBS) (Fulfillment by Seller).
// <div class="description_important">
// Нельзя привязывать склад WB, который уже используется
// </div>
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// POST /api/v3/warehouses
APIV3WarehousesPost(ctx context.Context, req *APIV3WarehousesPostReq) (APIV3WarehousesPostRes, error)
// APIV3WarehousesWarehouseIdDelete implements DELETE /api/v3/warehouses/{warehouseId} operation.
//
// Метод удаляет склад продавца из [списка
// складов](/openapi/work-with-products#tag/Sklady-prodavca/paths/~1api~1v3~1warehouses/get).
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// DELETE /api/v3/warehouses/{warehouseId}
APIV3WarehousesWarehouseIdDelete(ctx context.Context, params APIV3WarehousesWarehouseIdDeleteParams) (APIV3WarehousesWarehouseIdDeleteRes, error)
// APIV3WarehousesWarehouseIdPut implements PUT /api/v3/warehouses/{warehouseId} operation.
//
// Метод обновляет данные склада продавца в [списке
// складов](/openapi/work-with-products#tag/Sklady-prodavca/paths/~1api~1v3~1warehouses/get).
// Данные о привязанном [складе
// WB](/openapi/work-with-products#tag/Sklady-prodavca/paths/~1api~1v3~1offices/get) можно
// изменить один раз в сутки.
// <div class="description_important">
// Нельзя привязывать склад WB, который уже используется
// </div>
// <div class="description_limit">
// Максимум 300 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Маркетплейс</a> на
// один аккаунт продавца.
// <br><br>
// Один запрос с кодом ответа <code>409</code> учитывается как 5
// запросов
// </div>.
//
// PUT /api/v3/warehouses/{warehouseId}
APIV3WarehousesWarehouseIdPut(ctx context.Context, req *APIV3WarehousesWarehouseIdPutReq, params APIV3WarehousesWarehouseIdPutParams) (APIV3WarehousesWarehouseIdPutRes, error)
// ContentV2BarcodesPost implements POST /content/v2/barcodes operation.
//
// Метод генерирует массив уникальных баркодов для
// создания размера в [карточке
// товара](/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post). Можно использовать, если у вас нет собственных баркодов.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/barcodes
ContentV2BarcodesPost(ctx context.Context, req *ContentV2BarcodesPostReq) (ContentV2BarcodesPostRes, error)
// ContentV2CardsDeleteTrashPost implements POST /content/v2/cards/delete/trash operation.
//
// Метод переносит [карточки товаров в
// корзину](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1trash/post). При этом карточки товаров не удаляются, их можно [восстановить](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1recover/post).
// <div class="description_important">
// После переноса в корзину карточке товара
// присваивается новый <code>imtID</code>.
// </div>
// Карточки товаров удаляются автоматически, если лежат
// в корзине больше 30 дней. Очистка корзины происходит
// каждую ночь по московскому времени.<br>
// Карточки товаров можно удалить в любое время в
// [личном кабинете](https://seller.wildberries.ru/new-goods/basket-cards).
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/cards/delete/trash
ContentV2CardsDeleteTrashPost(ctx context.Context, req *ContentV2CardsDeleteTrashPostReq) (ContentV2CardsDeleteTrashPostRes, error)
// ContentV2CardsErrorListGet implements GET /content/v2/cards/error/list operation.
//
// Метод предоставляет список карточек товаров, при
// создании или редактировании которых произошли
// ошибки, с описанием этих ошибок.
// <div class="description_important">
// Чтобы убрать карточку товара из списка, нужно
// повторно сделать запрос на <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создание</a> или редактирование карточки товара с исправленными ошибками.
// </div>
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/cards/error/list
ContentV2CardsErrorListGet(ctx context.Context, params ContentV2CardsErrorListGetParams) (ContentV2CardsErrorListGetRes, error)
// ContentV2CardsLimitsGet implements GET /content/v2/cards/limits operation.
//
// Возвращает бесплатные и платные лимиты продавца на
// [создание карточек
// товаров](/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post).<br><br>
// Формула для получения количества карточек, которые
// можно создать:
// > (`freeLimits` + `paidLimits`) - количество созданных карточек
// Созданными считаются карточки, которые можно
// получить через методы [список карточек
// товаров](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1list/post) и [список карточек товаров в корзине](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1trash/post).
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/cards/limits
ContentV2CardsLimitsGet(ctx context.Context) (ContentV2CardsLimitsGetRes, error)
// ContentV2CardsRecoverPost implements POST /content/v2/cards/recover operation.
//
// Метод восстанавливает [карточки товаров из
// корзины](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1trash/post).
// <div class="description_important">
// Карточка товара сохраняет тот же <code>imtID</code>, что был
// присвоен ей при <a
// href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1delete~1trash/post">перемещении в корзину</a>.
// </div>
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/cards/recover
ContentV2CardsRecoverPost(ctx context.Context, req *ContentV2CardsRecoverPostReq) (ContentV2CardsRecoverPostRes, error)
// ContentV2CardsUpdatePost implements POST /content/v2/cards/update operation.
//
// Метод обновляет карточки товаров. Данные для
// обновления можно получить через [список карточек
// товаров](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1list/post) и [список карточек товаров в корзине](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1trash/post).
// <div class="description_important">
// Карточка товара перезаписывается при обновлении.
// Поэтому в запросе нужно передать <strong>все</strong>
// параметры карточки, в том числе те, которые вы не
// собираетесь обновлять.
// </div>
// Нельзя редактировать или удалять баркоды, но можно
// добавить дополнительный баркод к карточке товара.
// Параметры `photos`, `video` и `tags` редактировать или удалять
// через данный метод нельзя.<br>
// Габариты товаров можно указать только в `сантиметрах`,
// вес товара с упаковкой — в `килограммах`.
// <br><br>
// В одном запросе можно отредактировать максимум 3000
// карточек товаров (`nmID`). Максимальный размер запроса 10
// Мб.<br>
// Если ответ `Успешно` (`200`), но какие-то карточки не
// обновились, получите [список несозданных карточек
// товаров](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1error~1list/get).
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня для метода будет отдельный
// лимит — 10 запросов в минуту на один аккаунт продавца
// </div>.
//
// POST /content/v2/cards/update
ContentV2CardsUpdatePost(ctx context.Context, req []ContentV2CardsUpdatePostReqItem) (ContentV2CardsUpdatePostRes, error)
// ContentV2CardsUploadAddPost implements POST /content/v2/cards/upload/add operation.
//
// Метод создаёт новые карточки товаров, присоединяя их
// к существующим карточкам.
// Габариты товаров можно указать только в `сантиметрах`,
// вес товара с упаковкой — в `килограммах`.
// <br><br>
// Создание карточки товара происходит асинхронно.
// После отправки запрос становится в очередь на
// обработку.<br>Максимальный размер запроса 10 Мб.<br>
// Если ответ `Успешно` (`200`), но какие-то карточки не
// обновились, получите [список несозданных карточек
// товаров](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1error~1list/get).
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня для метода будет отдельный
// лимит — 10 запросов в минуту на один аккаунт продавца
// </div>.
//
// POST /content/v2/cards/upload/add
ContentV2CardsUploadAddPost(ctx context.Context, req OptContentV2CardsUploadAddPostReq) (ContentV2CardsUploadAddPostRes, error)
// ContentV2CardsUploadPost implements POST /content/v2/cards/upload operation.
//
// Метод создаёт карточки товаров c указанием описаний и
// характеристик товаров.<br>
// <div class="description_important">
// Есть две формы запроса: для создания отдельных и
// объединённых карточек товаров.
// </div>
// Габариты товаров можно указать только в `сантиметрах`,
// вес товара с упаковкой — в `килограммах`.
// <br><br>
// Создание карточки товара происходит асинхронно.
// После отправки запрос становится в очередь на
// обработку.<br>
// В одном запросе можно создать максимум 100
// объединённых карточек товаров (`imtID`), по 30 карточек
// товаров в каждой. Максимальный размер запроса 10 Мб.<br>
// Если ответ `Успешно` (`200`), но какие-то карточки не
// обновились, получите [список несозданных карточек
// товаров](/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1error~1list/get).
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня для метода будет отдельный
// лимит — 10 запросов в минуту на один аккаунт продавца
// </div>.
//
// POST /content/v2/cards/upload
ContentV2CardsUploadPost(ctx context.Context, req []ContentV2CardsUploadPostReqItem) (ContentV2CardsUploadPostRes, error)
// ContentV2DirectoryColorsGet implements GET /content/v2/directory/colors operation.
//
// Метод предоставляет возможные значения
// [характеристики](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1charcs~1%7BsubjectId%7D/get) предмета `Цвет`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/colors
ContentV2DirectoryColorsGet(ctx context.Context, params ContentV2DirectoryColorsGetParams) (ContentV2DirectoryColorsGetRes, error)
// ContentV2DirectoryCountriesGet implements GET /content/v2/directory/countries operation.
//
// Метод предоставляет возможные значения
// [характеристики](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1charcs~1%7BsubjectId%7D/get) предмета `Страна производства`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/countries
ContentV2DirectoryCountriesGet(ctx context.Context, params ContentV2DirectoryCountriesGetParams) (ContentV2DirectoryCountriesGetRes, error)
// ContentV2DirectoryKindsGet implements GET /content/v2/directory/kinds operation.
//
// Метод предоставляет возможные значения
// [характеристики](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1charcs~1%7BsubjectId%7D/get) предмета `Пол`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/kinds
ContentV2DirectoryKindsGet(ctx context.Context, params ContentV2DirectoryKindsGetParams) (ContentV2DirectoryKindsGetRes, error)
// ContentV2DirectorySeasonsGet implements GET /content/v2/directory/seasons operation.
//
// Метод предоставляет возможные значения
// [характеристики](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1charcs~1%7BsubjectId%7D/get) предмета `Сезон`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/seasons
ContentV2DirectorySeasonsGet(ctx context.Context, params ContentV2DirectorySeasonsGetParams) (ContentV2DirectorySeasonsGetRes, error)
// ContentV2DirectoryTnvedGet implements GET /content/v2/directory/tnved operation.
//
// Метод предоставляет список ТНВЭД-кодов по ID
// [предмета](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1all/get) и фрагменту ТНВЭД-кода.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/tnved
ContentV2DirectoryTnvedGet(ctx context.Context, params ContentV2DirectoryTnvedGetParams) (ContentV2DirectoryTnvedGetRes, error)
// ContentV2DirectoryVatGet implements GET /content/v2/directory/vat operation.
//
// Метод предоставляет возможные значения
// [характеристики](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1charcs~1%7BsubjectId%7D/get) предмета `Ставка НДС`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/directory/vat
ContentV2DirectoryVatGet(ctx context.Context, params ContentV2DirectoryVatGetParams) (ContentV2DirectoryVatGetRes, error)
// ContentV2GetCardsListPost implements POST /content/v2/get/cards/list operation.
//
// <div class="description_auth">
// Метод доступен по <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">токену</a> с
// категорией <strong>Контент</strong> или <strong>Продвижение</strong>
// </div>
// Метод предоставляет список созданных карточек
// товаров.
// <div class="description_important">
// В ответе метода не будет карточек, находящихся в
// корзине. Получить такие карточки можно через <a
// href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1get~1cards~1trash/post">отдельный метод</a>.
// </div>
// Чтобы получить **больше 100** карточек товаров,
// воспользуйтесь пагинацией:
// <ol>
// <li>Сделайте первый запрос: <br>
// <pre style="background-color: rgb(38 50 56 / 5%); color: #e53935">
// {
// "settings": {
// "cursor": {
// "limit": 100
// },
// "filter": {
// "withPhoto": -1
// }
// }
// }</pre>
// </li>
// <li>Пройдите в конец полученного списка карточек
// товаров.</li>
// <li>Скопируйте из <code>cursor</code> две строки:
// <ul>
// <li><code>"updatedAt": "***"</code></li>
// <li><code>"nmID": ***</code></li>
// </ul></li>
// <li>Вставьте скопированные строки в параметр запроса
// <code>cursor</code>.</li>
// <li>Повторите запрос. </li>
// <li>Повторяйте пункты со <b>2</b> по <b>5</b>, пока поле
// <code>total</code> в ответе не станет меньше чем параметр
// <code>limit</code> в запросе. Это будет означать, что вы
// получили все карточки.
// </ol>
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/get/cards/list
ContentV2GetCardsListPost(ctx context.Context, req *ContentV2GetCardsListPostReq, params ContentV2GetCardsListPostParams) (ContentV2GetCardsListPostRes, error)
// ContentV2GetCardsTrashPost implements POST /content/v2/get/cards/trash operation.
//
// <div class="description_auth">
// Метод доступен по <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">токену</a> с
// категорией <strong>Контент</strong> или <strong>Продвижение</strong>
// </div>
// Метод предоставляет список карточек товаров в
// корзине.<br><br>
// Чтобы получить **больше 100** карточек товаров,
// воспользуйтесь пагинацией:
// <ol>
// <li>Сделайте первый запрос: <br>
// <pre style="background-color: rgb(38 50 56 / 5%); color: #e53935">
// {
// "settings": {
// "cursor": {
// "limit": 100
// },
// "filter": {
// "withPhoto": -1
// }
// }
// }</pre>
// </li>
// <li>Пройдите в конец полученного списка карточек
// товаров.</li>
// <li>Скопируйте из <code>cursor</code> две строки:
// <ul>
// <li><code>"trashedAt": "***"</code></li>
// <li><code>"nmID": ***</code></li>
// </ul></li>
// <li>Вставьте скопированные строки в параметр запроса
// <code>cursor</code>.</li>
// <li>Повторите запрос. </li>
// <li>Повторяйте пункты со <b>2</b> по <b>5</b>, пока поле
// <code>total</code> в ответе не станет меньше чем параметр
// <code>limit</code> в запросе. Это будет означать, что вы
// получили все карточки.
// </ol>
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/get/cards/trash
ContentV2GetCardsTrashPost(ctx context.Context, req *ContentV2GetCardsTrashPostReq, params ContentV2GetCardsTrashPostParams) (ContentV2GetCardsTrashPostRes, error)
// ContentV2ObjectAllGet implements GET /content/v2/object/all operation.
//
// Метод предоставляет список названий [родительских
// категорий
// предметов](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1parent~1all/get) и их предметов с ID. Например, у категории `Игрушки` будут предметы `Калейдоскопы`, `Куклы`, `Мячики`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/object/all
ContentV2ObjectAllGet(ctx context.Context, params ContentV2ObjectAllGetParams) (ContentV2ObjectAllGetRes, error)
// ContentV2ObjectCharcsSubjectIdGet implements GET /content/v2/object/charcs/{subjectId} operation.
//
// Метод предоставляет параметры характеристик
// предмета: названия, типы данных, единицы измерения и
// так далее. В запросе необходимо указать ID
// [предмета](/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1object~1all/get).
// <div class="description_important">
// Для получения характеристик <a
// href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1colors/get">Цвет</a>, <a href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1kinds/get">Пол</a>, <a href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1countries/get">Страна производства</a>, <a href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1seasons/get">Сезон</a>, <a href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1vat/get">Ставка НДС</a> и <a href="/openapi/work-with-products#tag/Kategorii-predmety-i-harakteristiki/paths/~1content~1v2~1directory~1tnved/get">ТНВЭД-код</a> используйте отдельные методы
// </div>
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/object/charcs/{subjectId}
ContentV2ObjectCharcsSubjectIdGet(ctx context.Context, params ContentV2ObjectCharcsSubjectIdGetParams) (ContentV2ObjectCharcsSubjectIdGetRes, error)
// ContentV2ObjectParentAllGet implements GET /content/v2/object/parent/all operation.
//
// Метод предоставляет названия и ID всех родительских
// категорий для [создания карточек
// товаров](/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov): например,
// `Электроника`, `Бытовая химия`, `Рукоделие`.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/object/parent/all
ContentV2ObjectParentAllGet(ctx context.Context, params ContentV2ObjectParentAllGetParams) (ContentV2ObjectParentAllGetRes, error)
// ContentV2TagNomenclatureLinkPost implements POST /content/v2/tag/nomenclature/link operation.
//
// Метод добавляет или снимает ярлык с карточки товара.
// К карточке можно добавить максимум 15 ярлыков.<br>
// При удалении ярлыка из карточки товара он не
// удаляется из [списка
// ярлыков](/openapi/work-with-products#tag/Yarlyki/paths/~1content~1v2~1tags/get)
// продавца.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v2/tag/nomenclature/link
ContentV2TagNomenclatureLinkPost(ctx context.Context, req *ContentV2TagNomenclatureLinkPostReq) (ContentV2TagNomenclatureLinkPostRes, error)
// ContentV2TagsGet implements GET /content/v2/tags operation.
//
// Метод предоставляет список и характеристики всех
// ярлыков продавца для группировки и фильтрации
// товаров.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// GET /content/v2/tags
ContentV2TagsGet(ctx context.Context) (ContentV2TagsGetRes, error)
// ContentV3MediaFilePost implements POST /content/v3/media/file operation.
//
// Метод загружает и добавляет один медиафайл к
// карточке товара.
// Требования к изображениям:
// * максимум изображений для одной карточки товара — 30
// * минимальное разрешение — 700x900 px
// * максимальный размер — 32 Мб
// * минимальное качество — 65%
// * форматы — JPG, PNG, BMP, GIF (статичные), WebP
// Требования к видео:
// * максимум одно видео для одной карточки товара
// * максимальный размер — 50 Мб
// * форматы — MOV, MP4
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v3/media/file
ContentV3MediaFilePost(ctx context.Context, req *ContentV3MediaFilePostReq, params ContentV3MediaFilePostParams) (ContentV3MediaFilePostRes, error)
// ContentV3MediaSavePost implements POST /content/v3/media/save operation.
//
// Метод загружает набор медиафайлов в карточку товара
// через указание ссылок в запросе.
// <div class="description_important">
// Новые медиафайлы полностью заменяют старые. Чтобы
// добавить новые медиафайлы, укажите в запросе ссылки
// одновременно на новые и старые медиафайлы.
// </div>
// Требования к изображениям:
// * максимум изображений для одной карточки товара — 30
// * минимальное разрешение — 700×900 px
// * максимальный размер — 32 Мб
// * минимальное качество — 65%
// * форматы — JPG, PNG, BMP, GIF (статичные), WebP
// Требования к видео:
// * максимум одно видео для одной карточки товара
// * максимальный размер — 50 Мб
// * форматы — MOV, MP4
// Если видео или хотя бы одно изображение в запросе не
// соответствует требованиям, то даже при успешном
// ответе ни одно изображение/видео не загрузится.
// <div class="description_limit">
// Максимум 100 запросов в <a
// href="/openapi/api-information#tag/Vvedenie/Limity-zaprosov">минуту</a> для всех
// методов категории <a
// href="/openapi/api-information#tag/Avtorizaciya/Kak-sozdat-token">Контент</a> на один
// аккаунт продавца. С 5 июня — за исключением методов <a
// href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload/post">создания</a>, <a href="/openapi/work-with-products#tag/Sozdanie-kartochek-tovarov/paths/~1content~1v2~1cards~1upload~1add/post">создания с присоединением</a> и <a href="/openapi/work-with-products#tag/Kartochki-tovarov/paths/~1content~1v2~1cards~1update/post">редактирования</a> карточек товаров
// </div>.
//
// POST /content/v3/media/save
ContentV3MediaSavePost(ctx context.Context, req *ContentV3MediaSavePostReq) (ContentV3MediaSavePostRes, error)
}
// Server implements http server based on OpenAPI v3 specification and
// calls Handler to handle requests.
type Server struct {
h Handler
sec SecurityHandler
baseServer
}
// NewServer creates new Server.
func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) {
s, err := newServerConfig(opts...).baseServer()
if err != nil {
return nil, err
}
return &Server{
h: h,
sec: sec,
baseServer: s,
}, nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3
pkg/api/wb/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package wb
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target client --clean 02-products.yaml

7
pkg/api/wb/ogen.yml Normal file
View File

@@ -0,0 +1,7 @@
generator:
ignore_not_implemented: [ "all" ]
content_type_aliases:
"application/problem+json": "application/json"
features:
enable:
- 'client/security/reentrant'