package breeds import ( _ "embed" "encoding/json" "fmt" "strings" "unicode" "github.com/mozillazg/go-pinyin" "github.com/sundynix/pets-be/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) //go:embed data.json var data []byte // SortFields provides both full pinyin and initials, retaining Latin breed names. func SortFields(name string) (string, string, string) { args := pinyin.NewArgs() args.Fallback = func(r rune, _ pinyin.Args) []string { if unicode.IsLetter(r) || unicode.IsDigit(r) { return []string{strings.ToLower(string(r))} } return nil } words := pinyin.LazyPinyin(name, args) // Breed-name phrases with common alternative pronunciations. if strings.HasPrefix(name, "重庆") && len(words) > 0 { words[0] = "chong" } if strings.HasPrefix(name, "长毛") && len(words) > 0 { words[0] = "chang" } full := strings.Join(words, "") var initials strings.Builder for _, word := range words { if word != "" { initials.WriteByte(word[0]) } } initial := "#" if len(full) > 0 && full[0] >= 'a' && full[0] <= 'z' { initial = strings.ToUpper(full[:1]) } return full, initials.String(), initial } func Defaults() ([]model.Breed, error) { var rows []struct { Origin string `json:"origin"` BreedGroup string `json:"breed_group"` FCINumber string `json:"fci_number"` EnglishName string `json:"english_name"` Height string `json:"height"` Weight string `json:"weight"` Summary string `json:"summary"` Temperament string `json:"temperament"` Traits string `json:"traits"` CareNotes string `json:"care_notes"` EncyclopediaUpdatedAt string `json:"encyclopedia_updated_at"` Name string `json:"name"` Aliases string `json:"aliases"` Source string `json:"source"` SourceKey string `json:"source_key"` SourceURL string `json:"source_url"` } if err := json.Unmarshal(data, &rows); err != nil { return nil, err } result := make([]model.Breed, 0, len(rows)) for _, row := range rows { full, short, initial := SortFields(row.Name) key := row.SourceKey result = append(result, model.Breed{Origin: row.Origin, BreedGroup: row.BreedGroup, FCINumber: row.FCINumber, EnglishName: row.EnglishName, Height: row.Height, Weight: row.Weight, Summary: row.Summary, Temperament: row.Temperament, Traits: row.Traits, CareNotes: row.CareNotes, EncyclopediaUpdatedAt: row.EncyclopediaUpdatedAt, Name: row.Name, Aliases: row.Aliases, Pinyin: full, PinyinInitials: short, Initial: initial, Enabled: true, Source: row.Source, SourceKey: &key, SourceURL: row.SourceURL}) } return result, nil } // Existing entries (including edits and disabled breeds) are never overwritten. func Seed(db *gorm.DB) (int64, error) { rows, err := Defaults() if err != nil { return 0, fmt.Errorf("load breeds: %w", err) } result := db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(rows, 100) if result.Error != nil { return result.RowsAffected, result.Error } // Backfill newly introduced encyclopedia fields exactly once on untouched seed rows. // Future starts never overwrite administrator changes. for _, row := range rows { if row.Summary == "" { continue } if err := db.Model(&model.Breed{}).Where("source_key = ? AND name = ? AND (encyclopedia_updated_at IS NULL OR encyclopedia_updated_at = '') AND updated_at = created_at", row.SourceKey, row.Name).Updates(map[string]any{"origin": row.Origin, "breed_group": row.BreedGroup, "fci_number": row.FCINumber, "english_name": row.EnglishName, "height": row.Height, "weight": row.Weight, "summary": row.Summary, "temperament": row.Temperament, "traits": row.Traits, "care_notes": row.CareNotes, "encyclopedia_updated_at": row.EncyclopediaUpdatedAt}).Error; err != nil { return result.RowsAffected, err } } return result.RowsAffected, enrich(db) }