Files
descrybe/apps/api/internal/woocommerce/sync_scope.go
T

98 lines
2.4 KiB
Go
Raw Normal View History

package woocommerce
import (
"fmt"
"strings"
"github.com/google/uuid"
)
const (
maxSyncOnlyIDs = 500
maxCategoryFilterLen = 200
)
// ProductSyncScope is an optional one-shot filter for EnqueueSync / POST /sync.
// Empty fields mean "no filter" (sync recent processed products up to SyncLimit).
type ProductSyncScope struct {
Limit int `json:"sync_limit,omitempty"`
Status string `json:"status,omitempty"`
Category string `json:"category,omitempty"`
ProductIDs []string `json:"product_ids,omitempty"`
}
func clearOneShotSyncFilters(opt *SyncOptions) {
if opt == nil {
return
}
opt.SyncFilterStatus = ""
opt.SyncFilterCategory = ""
opt.SyncOnlyIDs = nil
}
func normalizeSyncFilterStatus(status string) (string, error) {
s := strings.TrimSpace(strings.ToLower(status))
if s == "" {
return "", nil
}
switch s {
case "completed", "needs_review", "error", "processing", "processed":
return s, nil
default:
return "", fmt.Errorf("%w: invalid status", ErrInvalidSyncScope)
}
}
func applyProductSyncScope(opt *SyncOptions, scope ProductSyncScope) error {
if opt == nil {
return fmt.Errorf("%w: missing options", ErrInvalidSyncScope)
}
clearOneShotSyncFilters(opt)
if scope.Limit > 0 {
if scope.Limit > maxSyncLimit {
return fmt.Errorf("%w: sync_limit max %d", ErrInvalidSyncScope, maxSyncLimit)
}
opt.SyncLimit = scope.Limit
}
status, err := normalizeSyncFilterStatus(scope.Status)
if err != nil {
return err
}
opt.SyncFilterStatus = status
cat := strings.TrimSpace(scope.Category)
if len(cat) > maxCategoryFilterLen {
return fmt.Errorf("%w: category too long", ErrInvalidSyncScope)
}
opt.SyncFilterCategory = cat
if len(scope.ProductIDs) > maxSyncOnlyIDs {
return fmt.Errorf("%w: product_ids max %d", ErrInvalidSyncScope, maxSyncOnlyIDs)
}
cleaned := make([]string, 0, len(scope.ProductIDs))
seen := make(map[string]struct{}, len(scope.ProductIDs))
for _, raw := range scope.ProductIDs {
id := strings.TrimSpace(raw)
if id == "" {
continue
}
parsed, err := uuid.Parse(id)
if err != nil {
return fmt.Errorf("%w: invalid product_ids", ErrInvalidSyncScope)
}
key := parsed.String()
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
cleaned = append(cleaned, key)
}
opt.SyncOnlyIDs = cleaned
return nil
}
func pruneSyncOnlyIDs(ids []string, max int) []string {
if max <= 0 || len(ids) <= max {
return ids
}
return ids[:max]
}