diff --git a/backend/internal/data/ent/item_predicates.go b/backend/internal/data/ent/item_predicates.go index c124a2af1..559da9e32 100644 --- a/backend/internal/data/ent/item_predicates.go +++ b/backend/internal/data/ent/item_predicates.go @@ -5,7 +5,8 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/item" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" conf "github.com/sysadminsmedia/homebox/backend/internal/sys/config" - "github.com/sysadminsmedia/homebox/backend/pkgs/textutils" + // "github.com/sysadminsmedia/homebox/backend/pkgs/textutils" + "strings" ) // AccentInsensitiveContains creates a predicate that performs accent-insensitive text search. @@ -19,7 +20,8 @@ func AccentInsensitiveContains(field string, searchValue string) predicate.Item } // Normalize the search value - normalizedSearch := textutils.NormalizeSearchQuery(searchValue) + // normalizedSearch := textutils.NormalizeSearchQuery(searchValue) + normalizedSearch := NormalizeString(searchValue) return predicate.Item(func(s *sql.Selector) { dialect := s.Dialect() @@ -59,35 +61,71 @@ func buildSQLiteNormalizeExpression(fieldExpr string) string { return buildGenericNormalizeExpression(fieldExpr) } -// buildGenericNormalizeExpression creates a database-agnostic expression to normalize common accented characters +type accentPair struct { + from string + to string +} + +var accentReplacements = []accentPair{ + // Spanish + {"á", "a"}, {"é", "e"}, {"í", "i"}, {"ó", "o"}, {"ú", "u"}, {"ñ", "n"}, + {"Á", "A"}, {"É", "E"}, {"Í", "I"}, {"Ó", "O"}, {"Ú", "U"}, {"Ñ", "N"}, + + // French + {"è", "e"}, {"ê", "e"}, {"à", "a"}, {"ç", "c"}, + {"È", "E"}, {"Ê", "E"}, {"À", "A"}, {"Ç", "C"}, + + // German / Portuguese + {"ä", "a"}, {"ö", "o"}, {"ü", "u"}, {"ã", "a"}, {"õ", "o"}, + {"Ä", "A"}, {"Ö", "O"}, {"Ü", "U"}, {"Ã", "A"}, {"Õ", "O"}, + + // Greek lowercase + {"α", "a"}, {"β", "v"}, {"γ", "g"}, {"δ", "d"}, {"ε", "e"}, + {"ζ", "z"}, {"η", "i"}, {"θ", "th"}, {"ι", "i"}, {"κ", "k"}, + {"λ", "l"}, {"μ", "m"}, {"ν", "n"}, {"ξ", "x"}, {"ο", "o"}, + {"π", "p"}, {"ρ", "r"}, {"σ", "s"}, {"ς", "s"}, {"τ", "t"}, + {"υ", "y"}, {"φ", "f"}, {"χ", "ch"}, {"ψ", "ps"}, {"ω", "o"}, + + // Greek accented lowercase + {"ά", "a"}, {"έ", "e"}, {"ή", "i"}, {"ί", "i"}, {"ϊ", "i"}, {"ΐ", "i"}, + {"ό", "o"}, {"ώ", "o"}, {"ύ", "y"}, {"ϋ", "y"}, {"ΰ", "y"}, + + // Greek uppercase + {"Α", "A"}, {"Β", "V"}, {"Γ", "G"}, {"Δ", "D"}, {"Ε", "E"}, + {"Ζ", "Z"}, {"Η", "I"}, {"Θ", "TH"}, {"Ι", "I"}, {"Κ", "K"}, + {"Λ", "L"}, {"Μ", "M"}, {"Ν", "N"}, {"Ξ", "X"}, {"Ο", "O"}, + {"Π", "P"}, {"Ρ", "R"}, {"Σ", "S"}, {"Τ", "T"}, {"Υ", "Y"}, + {"Φ", "F"}, {"Χ", "CH"}, {"Ψ", "PS"}, {"Ω", "O"}, + + // Greek accented uppercase + {"Ά", "A"}, {"Έ", "E"}, {"Ή", "I"}, {"Ί", "I"}, {"Ϊ", "I"}, + {"Ό", "O"}, {"Ώ", "O"}, {"Ύ", "Y"}, {"Ϋ", "Y"}, +} + func buildGenericNormalizeExpression(fieldExpr string) string { - // Chain REPLACE functions to handle the most common accented characters - // Focused on the most frequently used accents in Spanish, French, and Portuguese - // Ordered by frequency of use for better performance normalized := fieldExpr - // Most common accented characters ordered by frequency - commonAccents := []struct { - from, to string - }{ - // Spanish - most common - {"á", "a"}, {"é", "e"}, {"í", "i"}, {"ó", "o"}, {"ú", "u"}, {"ñ", "n"}, - {"Á", "A"}, {"É", "E"}, {"Í", "I"}, {"Ó", "O"}, {"Ú", "U"}, {"Ñ", "N"}, - - // French - most common - {"è", "e"}, {"ê", "e"}, {"à", "a"}, {"ç", "c"}, - {"È", "E"}, {"Ê", "E"}, {"À", "A"}, {"Ç", "C"}, - - // German umlauts and Portuguese - common - {"ä", "a"}, {"ö", "o"}, {"ü", "u"}, {"ã", "a"}, {"õ", "o"}, - {"Ä", "A"}, {"Ö", "O"}, {"Ü", "U"}, {"Ã", "A"}, {"Õ", "O"}, + for _, r := range accentReplacements { + normalized = "REPLACE(" + normalized + ", '" + r.from + "', '" + r.to + "')" } + return normalized +} + +var accentReplacer = strings.NewReplacer(flattenReplacements(accentReplacements)...) - for _, accent := range commonAccents { - normalized = "REPLACE(" + normalized + ", '" + accent.from + "', '" + accent.to + "')" +func flattenReplacements(pairs []accentPair) []string { + out := make([]string, 0, len(pairs)*2) + for _, p := range pairs { + out = append(out, p.from, p.to) } + return out +} - return normalized +func NormalizeString(input string) string { + if input == "" { + return "" + } + return strings.ToLower(accentReplacer.Replace(input)) } // ItemNameAccentInsensitiveContains creates an accent-insensitive search predicate for the item name field. diff --git a/docker-compose.yml.back b/docker-compose.yml.back new file mode 100644 index 000000000..4f7fa53e8 --- /dev/null +++ b/docker-compose.yml.back @@ -0,0 +1,19 @@ +services: + homebox: + image: homebox + build: + context: . + dockerfile: ./Dockerfile + args: + - COMMIT=head + - BUILD_TIME=0001-01-01T00:00:00Z + x-bake: + platforms: + - linux/amd64 + - linux/arm64 + - linux/arm + environment: + - HBOX_DEBUG=true + - HBOX_LOGGER_LEVEL=-1 + ports: + - 3100:7745