- Mock Ozon Seller API endpoints - Products, Postings, Finance, Returns, Analytics - JSON fixtures for testing - Docker support - Gitea Actions CI/CD
283 lines
6.6 KiB
Go
283 lines
6.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Common request/response types
|
|
type ErrorResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// ProductStocksRequest - POST /v3/product/info/stocks
|
|
type ProductStocksRequest struct {
|
|
Filter struct {
|
|
OfferIDs []string `json:"offer_id"`
|
|
ProductIDs []int64 `json:"product_id"`
|
|
Visibility string `json:"visibility"`
|
|
} `json:"filter"`
|
|
LastID string `json:"last_id"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type StockItem struct {
|
|
ProductID int64 `json:"product_id"`
|
|
OfferID string `json:"offer_id"`
|
|
Stock int `json:"stock"`
|
|
WarehouseID int64 `json:"warehouse_id"`
|
|
WarehouseName string `json:"warehouse_name"`
|
|
}
|
|
|
|
func ProductStocks(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req ProductStocksRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json: "+err.Error())
|
|
return
|
|
}
|
|
|
|
items := GetStocksFixtures()
|
|
|
|
// Filter by offer_ids if provided
|
|
if len(req.Filter.OfferIDs) > 0 {
|
|
filtered := make([]StockItem, 0)
|
|
for _, item := range items {
|
|
for _, oid := range req.Filter.OfferIDs {
|
|
if item.OfferID == oid {
|
|
filtered = append(filtered, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
items = filtered
|
|
}
|
|
|
|
// Filter by product_ids if provided
|
|
if len(req.Filter.ProductIDs) > 0 {
|
|
filtered := make([]StockItem, 0)
|
|
for _, item := range items {
|
|
for _, pid := range req.Filter.ProductIDs {
|
|
if item.ProductID == pid {
|
|
filtered = append(filtered, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
items = filtered
|
|
}
|
|
|
|
// Apply limit
|
|
if req.Limit > 0 && len(items) > req.Limit {
|
|
items = items[:req.Limit]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"items": items,
|
|
"total": len(items),
|
|
})
|
|
}
|
|
|
|
// ProductInfoListRequest - POST /v3/product/info/list
|
|
type ProductInfoListRequest struct {
|
|
Filter struct {
|
|
OfferIDs []string `json:"offer_id"`
|
|
ProductIDs []int64 `json:"product_id"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type ProductInfoItem struct {
|
|
ProductID int64 `json:"product_id"`
|
|
OfferID string `json:"offer_id"`
|
|
Name string `json:"name"`
|
|
Barcode string `json:"barcode,omitempty"`
|
|
BuyPrice float64 `json:"buy_price"`
|
|
Price float64 `json:"price"`
|
|
PremiumPrice float64 `json:"premium_price"`
|
|
CategoryID int64 `json:"category_id"`
|
|
}
|
|
|
|
func ProductInfoList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req ProductInfoListRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
items := GetProductsFixtures()
|
|
|
|
if len(req.Filter.OfferIDs) > 0 {
|
|
filtered := make([]ProductInfoItem, 0)
|
|
for _, item := range items {
|
|
for _, oid := range req.Filter.OfferIDs {
|
|
if item.OfferID == oid {
|
|
filtered = append(filtered, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
items = filtered
|
|
}
|
|
|
|
if req.Limit > 0 && len(items) > req.Limit {
|
|
items = items[:req.Limit]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"items": items,
|
|
"total": len(items),
|
|
})
|
|
}
|
|
|
|
// ImportPricesRequest - POST /v1/product/import/prices
|
|
type ImportPricesRequest struct {
|
|
Items []struct {
|
|
OfferID string `json:"offer_id"`
|
|
Price float64 `json:"price"`
|
|
OldPrice float64 `json:"old_price,omitempty"`
|
|
PremiumPrice float64 `json:"premium_price,omitempty"`
|
|
PremiumPriceOld float64 `json:"premium_price_old,omitempty"`
|
|
} `json:"items"`
|
|
}
|
|
|
|
type ImportPricesResponse struct {
|
|
TaskID int64 `json:"task_id"`
|
|
}
|
|
|
|
func ImportPrices(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req ImportPricesRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
if len(req.Items) == 0 {
|
|
writeError(w, 400, "items is required and must not be empty")
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
for _, item := range req.Items {
|
|
if item.OfferID == "" {
|
|
writeError(w, 400, "offer_id is required for all items")
|
|
return
|
|
}
|
|
if item.Price <= 0 {
|
|
writeError(w, 400, "price must be positive")
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(ImportPricesResponse{
|
|
TaskID: 1234567890,
|
|
})
|
|
}
|
|
|
|
// ProductPricesRequest - POST /v4/product/info/prices
|
|
type ProductPricesRequest struct {
|
|
Filter struct {
|
|
OfferIDs []string `json:"offer_id"`
|
|
ProductIDs []int64 `json:"product_id"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type ProductPriceItem struct {
|
|
ProductID int64 `json:"product_id"`
|
|
OfferID string `json:"offer_id"`
|
|
Price float64 `json:"price"`
|
|
OldPrice float64 `json:"old_price,omitempty"`
|
|
PremiumPrice float64 `json:"premium_price,omitempty"`
|
|
PremiumPriceOld float64 `json:"premium_price_old,omitempty"`
|
|
}
|
|
|
|
func ProductPrices(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req ProductPricesRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
items := GetPricesFixtures()
|
|
|
|
if len(req.Filter.OfferIDs) > 0 {
|
|
filtered := make([]ProductPriceItem, 0)
|
|
for _, item := range items {
|
|
for _, oid := range req.Filter.OfferIDs {
|
|
if item.OfferID == oid {
|
|
filtered = append(filtered, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
items = filtered
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"items": items,
|
|
"total": len(items),
|
|
})
|
|
}
|
|
|
|
// ProductListRequest - GET /v1/product/list
|
|
type ProductListRequest struct {
|
|
Page int `json:"page"`
|
|
}
|
|
|
|
func ProductList(w http.ResponseWriter, r *http.Request) {
|
|
items := GetProductsFixtures()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"items": items,
|
|
"total": len(items),
|
|
})
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, code int, msg string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
json.NewEncoder(w).Encode(ErrorResponse{Code: code, Message: msg})
|
|
}
|
|
|
|
// Helper to parse offer_ids from query string
|
|
func parseOfferIDs(query string) []string {
|
|
if query == "" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(query, ",")
|
|
result := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|