Files
ozon-mock/internal/handlers/products.go
Rinat 842f87f576 Initial commit: ozon-mock Go server
- Mock Ozon Seller API endpoints
- Products, Postings, Finance, Returns, Analytics
- JSON fixtures for testing
- Docker support
- Gitea Actions CI/CD
2026-07-15 16:19:24 +03:00

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
}