Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
141 lines
3.4 KiB
Go
141 lines
3.4 KiB
Go
package feeds
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// PresentFeed maps an input_feeds row to the public/legacy feed DTO.
|
|
// Legacy fields (item_path, is_active, last_synced, product_count) are always set;
|
|
// v2 fields (feed_type, sync_interval_minutes, last_synced_at, options) are included for dual-support.
|
|
func PresentFeed(feed map[string]any) map[string]any {
|
|
if feed == nil {
|
|
return nil
|
|
}
|
|
opts, _ := feed["options"].(map[string]any)
|
|
if opts == nil {
|
|
opts = map[string]any{}
|
|
}
|
|
itemPath, _ := opts["item_path"].(string)
|
|
if itemPath == "" {
|
|
itemPath, _ = feed["item_path"].(string)
|
|
}
|
|
status, _ := feed["status"].(string)
|
|
lastSynced := formatAPITime(feed["last_synced_at"])
|
|
productsUpdated := formatAPITime(feed["products_updated_at"])
|
|
productCount := intFromAny(feed["product_count"])
|
|
mappingFieldCount := intFromAny(feed["mapping_field_count"])
|
|
hasMappings := mappingFieldCount > 0
|
|
if v, ok := feed["has_mappings"].(bool); ok {
|
|
hasMappings = v || mappingFieldCount > 0
|
|
}
|
|
mappingIncomplete := !hasMappings
|
|
if v, ok := feed["mapping_incomplete"].(bool); ok {
|
|
mappingIncomplete = v
|
|
}
|
|
// last_data_at: live sync timestamp when present, else latest raw product update
|
|
// (covers MySQL→Postgres imports where last_synced_at was never set).
|
|
lastDataAt := lastSynced
|
|
if lastDataAt == nil {
|
|
lastDataAt = productsUpdated
|
|
}
|
|
|
|
out := map[string]any{
|
|
"id": stringifyID(feed["id"]),
|
|
"name": feed["name"],
|
|
"url": nullish(feed["url"]),
|
|
"item_path": itemPath,
|
|
"is_active": strings.EqualFold(status, "active"),
|
|
"product_count": productCount,
|
|
"mapping_field_count": mappingFieldCount,
|
|
"has_mappings": hasMappings,
|
|
"mapping_incomplete": mappingIncomplete,
|
|
"status": status,
|
|
"last_synced": lastSynced,
|
|
"created_at": formatAPITime(feed["created_at"]),
|
|
"updated_at": formatAPITime(feed["updated_at"]),
|
|
"feed_type": feed["feed_type"],
|
|
"sync_interval_minutes": feed["sync_interval_minutes"],
|
|
"last_synced_at": lastSynced,
|
|
"products_updated_at": productsUpdated,
|
|
"last_data_at": lastDataAt,
|
|
"options": opts,
|
|
}
|
|
if deltas, ok := opts["last_sync_deltas"].(map[string]any); ok && deltas != nil {
|
|
out["last_sync_deltas"] = deltas
|
|
}
|
|
return out
|
|
}
|
|
|
|
func intFromAny(v any) int {
|
|
switch t := v.(type) {
|
|
case int:
|
|
return t
|
|
case int32:
|
|
return int(t)
|
|
case int64:
|
|
return int(t)
|
|
case float64:
|
|
return int(t)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// PresentFeeds maps a page of feed rows through PresentFeed.
|
|
func PresentFeeds(items []map[string]any) []map[string]any {
|
|
out := make([]map[string]any, 0, len(items))
|
|
for _, item := range items {
|
|
out = append(out, PresentFeed(item))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func stringifyID(v any) any {
|
|
switch t := v.(type) {
|
|
case uuid.UUID:
|
|
return t.String()
|
|
case [16]byte:
|
|
return uuid.UUID(t).String()
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
func nullish(v any) any {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if s, ok := v.(string); ok && s == "" {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
func formatAPITime(v any) any {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
switch t := v.(type) {
|
|
case time.Time:
|
|
if t.IsZero() {
|
|
return nil
|
|
}
|
|
return t.UTC().Format(time.RFC3339)
|
|
case *time.Time:
|
|
if t == nil || t.IsZero() {
|
|
return nil
|
|
}
|
|
return t.UTC().Format(time.RFC3339)
|
|
case string:
|
|
if t == "" {
|
|
return nil
|
|
}
|
|
return t
|
|
default:
|
|
return v
|
|
}
|
|
}
|