refactor: migrate ingredient aliases/categories to dedicated tables, drop spoonacular_id
- migration 012: create ingredient_categories + ingredient_category_translations tables (7 slugs, Russian names); add ingredient_aliases (ingredient_id, lang, alias) with GIN trigram index; migrate aliases JSONB from ingredient_mappings and ingredient_translations; drop aliases columns and spoonacular_id; add UNIQUE (canonical_name) as conflict key - ingredient/model: remove SpoonacularID, add CategoryName for localized display - ingredient/repository: conflict on canonical_name; GetByID/Search join category translations and aliases lateral; new UpsertAliases (pgx batch), UpsertCategoryTranslation; remove GetBySpoonacularID; split scan helpers into scanMappingWrite / scanMappingRead - gemini/recognition: add IngredientTranslation type; IngredientClassification now carries Translations []IngredientTranslation instead of CanonicalNameRu; update ClassifyIngredient prompt to English with structured translations array - recognition/handler: update ingredientRepo interface; saveClassification uses UpsertAliases and iterates Translations - recipe/model: remove SpoonacularID from RecipeIngredient - integration tests: remove SpoonacularID fixtures, replace GetBySpoonacularID tests with GetByID, add UpsertAliases and UpsertCategoryTranslation tests - Flutter: remove canonicalNameRu from IngredientMapping, add categoryName; displayName returns server-resolved canonicalName; regenerate .g.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,8 @@ import (
|
||||
type ingredientRepo interface {
|
||||
FuzzyMatch(ctx context.Context, name string) (*ingredient.IngredientMapping, error)
|
||||
Upsert(ctx context.Context, m *ingredient.IngredientMapping) (*ingredient.IngredientMapping, error)
|
||||
UpsertTranslation(ctx context.Context, id, lang, name string, aliases json.RawMessage) error
|
||||
UpsertTranslation(ctx context.Context, id, lang, name string) error
|
||||
UpsertAliases(ctx context.Context, id, lang string, aliases []string) error
|
||||
}
|
||||
|
||||
// Handler handles POST /ai/* recognition endpoints.
|
||||
@@ -212,11 +213,6 @@ func (h *Handler) saveClassification(ctx context.Context, c *gemini.IngredientCl
|
||||
return nil
|
||||
}
|
||||
|
||||
aliasesJSON, err := json.Marshal(c.Aliases)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := &ingredient.IngredientMapping{
|
||||
CanonicalName: c.CanonicalName,
|
||||
Category: strPtr(c.Category),
|
||||
@@ -226,7 +222,6 @@ func (h *Handler) saveClassification(ctx context.Context, c *gemini.IngredientCl
|
||||
FatPer100g: c.FatPer100g,
|
||||
CarbsPer100g: c.CarbsPer100g,
|
||||
StorageDays: intPtr(c.StorageDays),
|
||||
Aliases: aliasesJSON,
|
||||
}
|
||||
|
||||
saved, err := h.ingredientRepo.Upsert(ctx, m)
|
||||
@@ -235,12 +230,23 @@ func (h *Handler) saveClassification(ctx context.Context, c *gemini.IngredientCl
|
||||
return nil
|
||||
}
|
||||
|
||||
// Persist the Russian translation when Gemini provided one.
|
||||
if c.CanonicalNameRu != "" {
|
||||
if err := h.ingredientRepo.UpsertTranslation(ctx, saved.ID, "ru", c.CanonicalNameRu, json.RawMessage("[]")); err != nil {
|
||||
slog.Warn("upsert ingredient translation", "id", saved.ID, "err", err)
|
||||
if len(c.Aliases) > 0 {
|
||||
if err := h.ingredientRepo.UpsertAliases(ctx, saved.ID, "en", c.Aliases); err != nil {
|
||||
slog.Warn("upsert ingredient aliases", "id", saved.ID, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range c.Translations {
|
||||
if err := h.ingredientRepo.UpsertTranslation(ctx, saved.ID, t.Lang, t.Name); err != nil {
|
||||
slog.Warn("upsert ingredient translation", "id", saved.ID, "lang", t.Lang, "err", err)
|
||||
}
|
||||
if len(t.Aliases) > 0 {
|
||||
if err := h.ingredientRepo.UpsertAliases(ctx, saved.ID, t.Lang, t.Aliases); err != nil {
|
||||
slog.Warn("upsert ingredient translation aliases", "id", saved.ID, "lang", t.Lang, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saved
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user