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,140 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user