feat: add multi-source breed encyclopedia and two-hour refresh sessions
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user