From b6ace52adaa00772bcd889fa266241e9e125215c Mon Sep 17 00:00:00 2001 From: vikingowl Date: Sat, 18 Apr 2026 09:11:44 +0200 Subject: [PATCH] fix(discovery): add json tags to domain types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without snake_case json tags, Go serializes fields as PascalCase (ID, MarktName, etc.) — but the Svelte frontend reads snake_case. Every row.id on the client was undefined, which made Svelte 5 see identical 'undefined' keys across the {#each queue as row (row.id)} loop and throw each_key_duplicate. Adds explicit snake_case tags to Bucket, DiscoveredMarket, and RejectedDiscovery to match what the TypeScript types already expect. --- backend/internal/domain/discovery/model.go | 66 +++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/backend/internal/domain/discovery/model.go b/backend/internal/domain/discovery/model.go index 00847be..db8ba2c 100644 --- a/backend/internal/domain/discovery/model.go +++ b/backend/internal/domain/discovery/model.go @@ -9,47 +9,47 @@ import ( // Bucket is a scheduler row: one (land, region, year_month) tuple. type Bucket struct { - ID uuid.UUID - Land string - Region string - YearMonth string // 'YYYY-MM' - LastQueriedAt *time.Time - LastError string - CreatedAt time.Time + ID uuid.UUID `json:"id"` + Land string `json:"land"` + Region string `json:"region"` + YearMonth string `json:"year_month"` // 'YYYY-MM' + LastQueriedAt *time.Time `json:"last_queried_at"` + LastError string `json:"last_error"` + CreatedAt time.Time `json:"created_at"` } // DiscoveredMarket is a queue entry awaiting admin review. type DiscoveredMarket struct { - ID uuid.UUID - BucketID uuid.UUID - MarktName string - Stadt string - Bundesland string - Land string - StartDatum *time.Time - EndDatum *time.Time - Website string - Quellen []string - Extraktion string - Hinweis string - NameNormalized string - MatchedSeriesID *uuid.UUID - Status string // 'pending' | 'accepted' | 'rejected' - DiscoveredAt time.Time - ReviewedAt *time.Time - ReviewedBy *uuid.UUID - CreatedEditionID *uuid.UUID + ID uuid.UUID `json:"id"` + BucketID uuid.UUID `json:"bucket_id"` + MarktName string `json:"markt_name"` + Stadt string `json:"stadt"` + Bundesland string `json:"bundesland"` + Land string `json:"land"` + StartDatum *time.Time `json:"start_datum"` + EndDatum *time.Time `json:"end_datum"` + Website string `json:"website"` + Quellen []string `json:"quellen"` + Extraktion string `json:"extraktion"` + Hinweis string `json:"hinweis"` + NameNormalized string `json:"name_normalized"` + MatchedSeriesID *uuid.UUID `json:"matched_series_id"` + Status string `json:"status"` // 'pending' | 'accepted' | 'rejected' + DiscoveredAt time.Time `json:"discovered_at"` + ReviewedAt *time.Time `json:"reviewed_at"` + ReviewedBy *uuid.UUID `json:"reviewed_by"` + CreatedEditionID *uuid.UUID `json:"created_edition_id"` } // RejectedDiscovery stores a sticky rejection scoped to (normalized_name, city, year). type RejectedDiscovery struct { - ID uuid.UUID - NameNormalized string - Stadt string - Year int - RejectedAt time.Time - RejectedBy *uuid.UUID - Reason string + ID uuid.UUID `json:"id"` + NameNormalized string `json:"name_normalized"` + Stadt string `json:"stadt"` + Year int `json:"year"` + RejectedAt time.Time `json:"rejected_at"` + RejectedBy *uuid.UUID `json:"rejected_by"` + Reason string `json:"reason"` } // Pass0Response is the parsed shape of the Mistral Pass 0 agent reply.