101 lines
4.0 KiB
Go
101 lines
4.0 KiB
Go
package breeds
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/sundynix/pets-be/internal/model"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"reflect"
|
|
)
|
|
|
|
//go:embed enrichment.json
|
|
var enrichmentData []byte
|
|
|
|
type Enrichment struct {
|
|
SourceKey string `json:"source_key"`
|
|
Name string `json:"name"`
|
|
Fields map[string]string `json:"fields"`
|
|
FieldSources map[string]string `json:"field_sources"`
|
|
References []model.BreedReference `json:"references"`
|
|
}
|
|
|
|
func Enrichments() ([]Enrichment, error) {
|
|
var rows []Enrichment
|
|
err := json.Unmarshal(enrichmentData, &rows)
|
|
return rows, err
|
|
}
|
|
|
|
// Reference facts remain available even when an editor locks the display content.
|
|
func enrich(db *gorm.DB) error {
|
|
rows, err := Enrichments()
|
|
if err != nil {
|
|
return fmt.Errorf("load breed sources: %w", err)
|
|
}
|
|
defaults, err := Defaults()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseline := map[string]model.Breed{}
|
|
for _, breed := range defaults {
|
|
baseline[*breed.SourceKey] = breed
|
|
}
|
|
for _, item := range rows {
|
|
var breed model.Breed
|
|
if err := db.Where("source_key = ?", item.SourceKey).First(&breed).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, source := range item.References {
|
|
source.BreedID = breed.ID
|
|
// Source snapshots are read-only in admin; update only when factual metadata changes.
|
|
if err := db.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "breed_id"}, {Name: "provider"}}, DoUpdates: clause.AssignmentColumns([]string{"external_id", "url", "retrieved_at", "facts"}), Where: clause.Where{Exprs: []clause.Expression{clause.Expr{SQL: "sundynix_breed_reference.facts IS DISTINCT FROM EXCLUDED.facts OR sundynix_breed_reference.url IS DISTINCT FROM EXCLUDED.url OR sundynix_breed_reference.retrieved_at IS DISTINCT FROM EXCLUDED.retrieved_at"}}}}).Create(&source).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if breed.EditorialLocked || breed.Name != item.Name {
|
|
continue
|
|
}
|
|
original := baseline[item.SourceKey]
|
|
updates := map[string]any{"english_name": original.EnglishName, "origin": original.Origin, "breed_group": original.BreedGroup, "fci_number": original.FCINumber, "height": original.Height, "weight": original.Weight, "summary": original.Summary, "temperament": original.Temperament, "traits": original.Traits, "care_notes": original.CareNotes, "life_span": "", "exercise": "", "grooming": ""}
|
|
for field, value := range item.Fields {
|
|
switch field {
|
|
case "english_name", "origin", "breed_group", "fci_number", "height", "weight", "life_span", "exercise", "grooming", "temperament", "traits", "summary", "care_notes":
|
|
updates[field] = value
|
|
default:
|
|
return fmt.Errorf("unsupported breed field: %s", field)
|
|
}
|
|
}
|
|
origins, err := json.Marshal(item.FieldSources)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updates["field_sources"] = string(origins)
|
|
latest := original.EncyclopediaUpdatedAt
|
|
for _, ref := range item.References {
|
|
if ref.RetrievedAt > latest {
|
|
latest = ref.RetrievedAt
|
|
}
|
|
}
|
|
updates["encyclopedia_updated_at"] = latest
|
|
// Skip unchanged columns so ordinary starts do not rewrite every breed.
|
|
current := map[string]string{"english_name": breed.EnglishName, "origin": breed.Origin, "breed_group": breed.BreedGroup, "fci_number": breed.FCINumber, "height": breed.Height, "weight": breed.Weight, "life_span": breed.LifeSpan, "exercise": breed.Exercise, "grooming": breed.Grooming, "temperament": breed.Temperament, "traits": breed.Traits, "summary": breed.Summary, "care_notes": breed.CareNotes, "encyclopedia_updated_at": breed.EncyclopediaUpdatedAt}
|
|
for key, value := range updates {
|
|
if key != "field_sources" && current[key] == value {
|
|
delete(updates, key)
|
|
}
|
|
}
|
|
if reflect.DeepEqual(breed.FieldSources, item.FieldSources) {
|
|
delete(updates, "field_sources")
|
|
}
|
|
if len(updates) == 0 {
|
|
continue
|
|
}
|
|
// UpdateColumns avoids advancing UpdatedAt on each application start.
|
|
if err := db.Model(&breed).Where("editorial_locked = false AND name = ?", item.Name).UpdateColumns(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|