Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package shopify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBatchSize = 25
|
||||
defaultSyncLimit = 200
|
||||
maxBatchSize = 100
|
||||
maxSyncLimit = 1000
|
||||
maxOrdersSyncLimit = 5000
|
||||
maxProductIDMap = 5000
|
||||
maxScheduleIntervalH = 168 // 7 days
|
||||
)
|
||||
|
||||
// SyncOptions is stored as JSON on shopify_configs.sync_options.
|
||||
type SyncOptions struct {
|
||||
ProductIDs map[string]int64 `json:"product_ids"`
|
||||
MatchStrategy string `json:"match_strategy"`
|
||||
BatchSize int `json:"batch_size"`
|
||||
SyncLimit int `json:"sync_limit"`
|
||||
PendingSync bool `json:"pending_sync"`
|
||||
PendingOrdersSync bool `json:"pending_orders_sync"`
|
||||
LastSyncStatus string `json:"last_sync_status"`
|
||||
LastSyncError string `json:"last_sync_error"`
|
||||
LastSyncSummary *SyncSummary `json:"last_sync_summary,omitempty"`
|
||||
LastOrdersSyncStatus string `json:"last_orders_sync_status"`
|
||||
LastOrdersSyncError string `json:"last_orders_sync_error"`
|
||||
LastOrdersSyncedAt *time.Time `json:"last_orders_synced_at,omitempty"`
|
||||
OrdersSyncLimit int `json:"orders_sync_limit"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
ScheduleIntervalHours int `json:"schedule_interval_hours"`
|
||||
SchedulePaused bool `json:"schedule_paused"`
|
||||
// One-shot selection for the next product sync (cleared when sync finishes).
|
||||
SyncFilterStatus string `json:"sync_filter_status,omitempty"`
|
||||
SyncFilterCategory string `json:"sync_filter_category,omitempty"`
|
||||
SyncOnlyIDs []string `json:"sync_only_ids,omitempty"`
|
||||
|
||||
// Auth (Dev Dashboard client_credentials). Legacy permanent tokens leave these empty.
|
||||
AuthMode string `json:"auth_mode,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ClientSecretEnc string `json:"client_secret_enc,omitempty"`
|
||||
TokenExpiresAt *time.Time `json:"token_expires_at,omitempty"`
|
||||
}
|
||||
|
||||
func parseSyncOptions(raw []byte) SyncOptions {
|
||||
opt := SyncOptions{
|
||||
ProductIDs: map[string]int64{},
|
||||
MatchStrategy: "sku",
|
||||
BatchSize: defaultBatchSize,
|
||||
SyncLimit: defaultSyncLimit,
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return opt
|
||||
}
|
||||
_ = json.Unmarshal(raw, &opt)
|
||||
if opt.ProductIDs == nil {
|
||||
opt.ProductIDs = map[string]int64{}
|
||||
}
|
||||
pruneStringInt64Map(opt.ProductIDs, maxProductIDMap)
|
||||
if opt.MatchStrategy == "" {
|
||||
opt.MatchStrategy = "sku"
|
||||
}
|
||||
if opt.BatchSize <= 0 || opt.BatchSize > maxBatchSize {
|
||||
opt.BatchSize = defaultBatchSize
|
||||
}
|
||||
if opt.SyncLimit <= 0 || opt.SyncLimit > maxSyncLimit {
|
||||
opt.SyncLimit = defaultSyncLimit
|
||||
}
|
||||
if opt.OrdersSyncLimit < 0 || opt.OrdersSyncLimit > maxOrdersSyncLimit {
|
||||
opt.OrdersSyncLimit = 0
|
||||
}
|
||||
if opt.ScheduleIntervalHours < 0 || opt.ScheduleIntervalHours > maxScheduleIntervalH {
|
||||
opt.ScheduleIntervalHours = 0
|
||||
}
|
||||
opt.SyncOnlyIDs = pruneSyncOnlyIDs(opt.SyncOnlyIDs, maxSyncOnlyIDs)
|
||||
return opt
|
||||
}
|
||||
|
||||
// resolveScheduleInterval maps schedule_interval_hours to a duration (default when hours<=0).
|
||||
func resolveScheduleInterval(hours int, defaultInterval time.Duration) time.Duration {
|
||||
if defaultInterval <= 0 {
|
||||
defaultInterval = 6 * time.Hour
|
||||
}
|
||||
if hours > 0 {
|
||||
return time.Duration(hours) * time.Hour
|
||||
}
|
||||
return defaultInterval
|
||||
}
|
||||
|
||||
// isDueForSchedule reports whether last sync is missing or older than interval.
|
||||
func isDueForSchedule(last *time.Time, now time.Time, interval time.Duration) bool {
|
||||
if last == nil {
|
||||
return true
|
||||
}
|
||||
return now.Sub(last.UTC()) >= interval
|
||||
}
|
||||
|
||||
// shouldEnqueueScheduled reports whether auto product sync should run now.
|
||||
// Paused schedules never enqueue; interval 0 uses defaultInterval (worker default 6h).
|
||||
func shouldEnqueueScheduled(paused bool, hours int, last *time.Time, now time.Time, defaultInterval time.Duration) bool {
|
||||
if paused {
|
||||
return false
|
||||
}
|
||||
return isDueForSchedule(last, now, resolveScheduleInterval(hours, defaultInterval))
|
||||
}
|
||||
|
||||
func firstNonEmpty(vals ...string) string {
|
||||
for _, v := range vals {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func pruneStringInt64Map(m map[string]int64, max int) {
|
||||
if max <= 0 || len(m) <= max {
|
||||
return
|
||||
}
|
||||
n := len(m) - max
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
n--
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user