fix products listing
This commit is contained in:
@@ -510,16 +510,31 @@ func (s *AuthService) generateAccessToken(user *model.Customer) (string, error)
|
||||
|
||||
func (s *AuthService) UpdateJWTToken(c fiber.Ctx) error {
|
||||
// Get user ID from JWT claims in context (set by auth middleware)
|
||||
claims, ok := c.Locals("jwt_claims").(*JWTClaims)
|
||||
if !ok || claims == nil {
|
||||
// claims, ok := c.Locals("jwt_claims").(*JWTClaims)
|
||||
// if !ok || claims == nil {
|
||||
// return c.Status(fiber.StatusUnauthorized).
|
||||
// JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
||||
// }
|
||||
// fmt.Printf("claims: %v\n", claims)
|
||||
// var user model.Customer
|
||||
// // Find user by ID
|
||||
// if err := s.db.First(&user, claims.UserID).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusUnauthorized).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
||||
}
|
||||
|
||||
var user model.Customer
|
||||
// Find user by ID
|
||||
if err := s.db.First(&user, claims.UserID).Error; err != nil {
|
||||
return err
|
||||
user := model.Customer{
|
||||
ID: userLocals.UserID,
|
||||
Email: userLocals.Email,
|
||||
Role: userLocals.Role,
|
||||
LangID: userLocals.LangID,
|
||||
CountryID: userLocals.CountryID,
|
||||
IsActive: userLocals.IsActive,
|
||||
}
|
||||
|
||||
// Parse language and country_id from query params
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"products-openai": {
|
||||
"source": "openAi",
|
||||
"model": "text-embedding-3-small",
|
||||
"apiKey": "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",
|
||||
"documentTemplate": "{{doc.Name}} is equipment used for {{doc.Description | truncatewords: 20}}"
|
||||
}
|
||||
}
|
||||
230
app/service/meiliService/indexation_params.go
Normal file
230
app/service/meiliService/indexation_params.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package meiliService
|
||||
|
||||
import "github.com/meilisearch/meilisearch-go"
|
||||
|
||||
type IndexationParams struct {
|
||||
LocalizedAttributes []*meilisearch.LocalizedAttributes `json:"localizedAttributes"`
|
||||
StopWords []string `json:"stopWords"`
|
||||
Synonyms map[string][]string `json:"synonyms"`
|
||||
}
|
||||
|
||||
var indexatio_params = map[uint]IndexationParams{
|
||||
1: IndexationParams{
|
||||
LocalizedAttributes: []*meilisearch.LocalizedAttributes{
|
||||
{AttributePatterns: []string{"*"}, Locales: []string{"pol"}},
|
||||
},
|
||||
StopWords: []string{
|
||||
"i", "w", "z", "na", "do", "się", "nie", "to", "że",
|
||||
"a", "o", "jak", "ale", "po", "za", "przez", "przy",
|
||||
"dla", "czy", "lub", "oraz", "ich", "jej", "jego",
|
||||
"ten", "ta", "te", "tego", "tej", "tym", "tych",
|
||||
"jest", "są", "być", "był", "była", "było", "będzie",
|
||||
"już", "jeszcze", "też", "tylko", "więc", "jednak",
|
||||
"co", "kto", "który", "która", "które", "ze", "by",
|
||||
"ze", "im", "go", "je", "tu", "tam", "tak",
|
||||
},
|
||||
Synonyms: map[string][]string{
|
||||
"plecak": {"plecaki", "plecaku", "plecaków", "tornister", "torba szkolna"},
|
||||
"tornister": {"plecak", "torba szkolna", "plecaki"},
|
||||
"piórnik": {"piórniki", "piórnika", "etui na przybory"},
|
||||
"zeszyt": {"zeszyty", "zeszytu", "zeszytów", "notatnik", "notes"},
|
||||
"kredka": {"kredki", "kredek", "kredkami", "ołówek kolorowy"},
|
||||
"farba": {"farby", "farb", "farbami", "farba plakatowa", "tempera"},
|
||||
"nożyczki": {"nożyczki dla dzieci", "nożyczki bezpieczne"},
|
||||
"linijka": {"linijki", "linijka plastikowa", "linijka drewniana"},
|
||||
"kalkulator": {"kalkulatory", "kalkulator szkolny", "kalkulator naukowy"},
|
||||
"długopis": {"długopisy", "długopisu", "długopisów", "pisak", "pióro"},
|
||||
"ołówek": {"ołówki", "ołówka", "ołówków", "grafitowy"},
|
||||
"gumka": {"gumki", "gumka do mazania", "korektor"},
|
||||
"temperówka": {"temperówki", "temperówka elektryczna"},
|
||||
"plastelina": {"plasteliny", "masa plastyczna", "modelina", "glinka"},
|
||||
"bibuła": {"bibułki", "bibuła marszczona", "krepa"},
|
||||
"brystol": {"brystole", "brystolu", "karton", "tektura"},
|
||||
"blok": {"bloki", "blok rysunkowy", "blok techniczny", "blok A4"},
|
||||
"klej": {"kleje", "kleju", "klej w sztyfcie", "klej PVA", "klej UHU"},
|
||||
"taśma": {"taśmy", "taśma klejąca", "taśma scotch"},
|
||||
"marker": {"markery", "mazak", "mazaki", "flamaster", "flamastry"},
|
||||
"pędzel": {"pędzle", "pędzla", "pędzli", "pędzel do farb"},
|
||||
"plakat": {"plakaty", "plansza edukacyjna", "poster"},
|
||||
"atlas": {"atlasy", "atlas geograficzny", "mapa"},
|
||||
"słownik": {"słowniki", "słownika", "słownik angielski"},
|
||||
"czytanka": {"czytanki", "lektura", "książka dla dzieci", "bajka"},
|
||||
"zabawka": {"zabawki", "zabawek", "gra edukacyjna", "puzzle"},
|
||||
"układanka": {"układanki", "puzzle", "mozaika"},
|
||||
"klocki": {"klocek", "klocków", "lego", "duplo"},
|
||||
"fartuch": {"fartuchy", "fartuch malarski", "fartuch plastyczny"},
|
||||
"teczka": {"teczki", "teczka na dokumenty", "segregator", "portfolio"},
|
||||
"koszulka": {"koszulki", "koszulka na dokumenty", "foliówka"},
|
||||
"ekierka": {"ekierki", "kątownik"},
|
||||
"cyrkiel": {"cyrkle", "kompas rysunkowy"},
|
||||
"globus": {"globusy", "kula ziemska"},
|
||||
"tablica": {"tablice", "tablica magnetyczna", "whiteboard", "flipchart"},
|
||||
"magnez": {"magnesy", "magnes tablicowy"},
|
||||
"kreda": {"kredy", "kreda tablicowa", "kreda kolorowa"},
|
||||
"przedszkole": {"żłobek", "edukacja przedszkolna", "wiek przedszkolny"},
|
||||
"szkoła": {"szkolny", "szkolna", "szkolne", "podstawówka"},
|
||||
},
|
||||
},
|
||||
2: IndexationParams{
|
||||
LocalizedAttributes: []*meilisearch.LocalizedAttributes{
|
||||
{AttributePatterns: []string{"*"}, Locales: []string{"eng"}},
|
||||
},
|
||||
StopWords: []string{
|
||||
"a", "an", "the", "and", "or", "but", "in", "on", "at",
|
||||
"to", "for", "of", "with", "by", "from", "is", "are",
|
||||
"was", "were", "be", "been", "being", "have", "has",
|
||||
"had", "do", "does", "did", "will", "would", "could",
|
||||
"should", "may", "might", "it", "its", "this", "that",
|
||||
"these", "those", "as", "if", "so", "than", "up",
|
||||
},
|
||||
Synonyms: map[string][]string{
|
||||
"backpack": {"rucksack", "school bag", "schoolbag", "knapsack"},
|
||||
"pencil case": {"pencil pouch", "pencil box", "stationery case"},
|
||||
"notebook": {"exercise book", "jotter", "notepad", "copybook"},
|
||||
"colored pencil": {"colour pencil", "colouring pencil", "crayon pencil"},
|
||||
"crayon": {"wax crayon", "colouring crayon", "wax pencil"},
|
||||
"paint": {"poster paint", "tempera", "watercolour", "finger paint"},
|
||||
"scissors": {"safety scissors", "kids scissors", "craft scissors"},
|
||||
"ruler": {"measuring ruler", "plastic ruler", "wooden ruler"},
|
||||
"calculator": {"scientific calculator", "school calculator"},
|
||||
"pen": {"ballpoint", "ballpoint pen", "biro", "rollerball"},
|
||||
"pencil": {"graphite pencil", "HB pencil", "drawing pencil"},
|
||||
"eraser": {"rubber", "correction eraser", "white eraser"},
|
||||
"sharpener": {"pencil sharpener", "electric sharpener"},
|
||||
"plasticine": {"modelling clay", "play-doh", "clay", "dough"},
|
||||
"tissue paper": {"crepe paper", "craft paper"},
|
||||
"cardboard": {"card", "art board", "bristol board"},
|
||||
"drawing pad": {"sketch pad", "art pad", "drawing block", "sketchbook"},
|
||||
"glue": {"glue stick", "PVA glue", "craft glue", "adhesive"},
|
||||
"tape": {"sticky tape", "sellotape", "scotch tape", "masking tape"},
|
||||
"marker": {"felt tip", "felt-tip pen", "highlighter", "marker pen"},
|
||||
"paintbrush": {"brush", "art brush", "watercolour brush"},
|
||||
"poster": {"educational poster", "wall chart", "learning poster"},
|
||||
"dictionary": {"word book", "vocabulary book", "language dictionary"},
|
||||
"book": {"reading book", "children book", "picture book", "reader"},
|
||||
"toy": {"educational toy", "learning toy", "kids toy"},
|
||||
"puzzle": {"jigsaw", "jigsaw puzzle", "floor puzzle"},
|
||||
"blocks": {"building blocks", "lego", "wooden blocks", "duplo"},
|
||||
"apron": {"art apron", "painting apron", "smock"},
|
||||
"folder": {"document folder", "ring binder", "portfolio"},
|
||||
"sleeve": {"document sleeve", "plastic sleeve", "page protector"},
|
||||
"compass": {"drawing compass", "geometry compass"},
|
||||
"set square": {"triangle ruler", "geometry set"},
|
||||
"globe": {"world globe", "earth globe"},
|
||||
"whiteboard": {"dry erase board", "magnetic board", "notice board"},
|
||||
"chalk": {"blackboard chalk", "coloured chalk", "sidewalk chalk"},
|
||||
"kindergarten": {"preschool", "nursery", "early years", "pre-k"},
|
||||
"school": {"primary school", "elementary school", "educational"},
|
||||
},
|
||||
},
|
||||
3: IndexationParams{
|
||||
LocalizedAttributes: []*meilisearch.LocalizedAttributes{
|
||||
{AttributePatterns: []string{"*"}, Locales: []string{"deu"}},
|
||||
},
|
||||
StopWords: []string{
|
||||
"und", "oder", "aber", "in", "an", "auf", "zu", "für",
|
||||
"von", "mit", "durch", "bei", "nach", "über", "unter",
|
||||
"ist", "sind", "war", "waren", "sein", "haben", "hat",
|
||||
"der", "die", "das", "den", "dem", "des", "ein", "eine",
|
||||
"einer", "einem", "einen", "eines", "sich", "auch",
|
||||
"nicht", "noch", "schon", "nur", "so", "wie", "wenn",
|
||||
"dann", "da", "hier", "dort", "ich", "du", "er", "sie",
|
||||
"es", "wir", "ihr", "als", "am", "im", "ins",
|
||||
},
|
||||
Synonyms: map[string][]string{
|
||||
"rucksack": {"schulrucksack", "schulranzen", "ranzen", "tornister"},
|
||||
"schulranzen": {"rucksack", "tornister", "schultasche"},
|
||||
"federmäppchen": {"mäppchen", "federmappe", "stiftemäppchen", "etui"},
|
||||
"heft": {"schulheft", "schreibheft", "lineatur", "notizheft"},
|
||||
"buntstift": {"buntstifte", "farbstift", "farbstifte", "malstift"},
|
||||
"wachsmalstift": {"wachsmalstifte", "wachskreide", "malwachs"},
|
||||
"farbe": {"schulfarbe", "plakatfarbe", "wasserfarbe", "fingerfarbe", "tempera"},
|
||||
"schere": {"kinderschere", "bastelschere", "sicherheitsschere"},
|
||||
"lineal": {"schullineal", "kunststofflineal", "holzlineal"},
|
||||
"taschenrechner": {"schulrechner", "wissenschaftlicher rechner", "rechner"},
|
||||
"kugelschreiber": {"kuli", "stift", "tintenroller", "füller"},
|
||||
"bleistift": {"zeichenbleistift", "graphitstift", "schulbleistift"},
|
||||
"radiergummi": {"radierer", "radiergummi", "tipp-ex"},
|
||||
"anspitzer": {"bleistiftspitzer", "elektrischer anspitzer"},
|
||||
"knete": {"knetmasse", "plastilin", "modellierton", "play-doh"},
|
||||
"seidenpapier": {"krepppapier", "krepp", "bastelpapier"},
|
||||
"karton": {"pappe", "zeichenkarton", "bristol"},
|
||||
"zeichenblock": {"malblock", "skizzenblock", "zeichenpapier"},
|
||||
"kleber": {"klebestift", "bastelkleber", "PVA-kleber", "UHU"},
|
||||
"klebeband": {"tesa", "scotch", "klebeband transparent", "kreativband"},
|
||||
"marker": {"filzstift", "faserstift", "textmarker", "edding"},
|
||||
"pinsel": {"malpinsel", "aquarellpinsel", "zeichenpinsel"},
|
||||
"poster": {"lernposter", "bildungsposter", "schulposter", "wandkarte"},
|
||||
"wörterbuch": {"schulwörterbuch", "vokabelbuch", "lexikon"},
|
||||
"buch": {"kinderbuch", "schulbuch", "lesebuch", "bilderbuch"},
|
||||
"spielzeug": {"lernspielzeug", "pädagogisches spielzeug", "kinderspielzeug"},
|
||||
"puzzle": {"legepuzzle", "bodenpuzzle", "steckpuzzle"},
|
||||
"baustein": {"bausteine", "lego", "holzbausteine", "duplo"},
|
||||
"schürze": {"malschürze", "bastelschürze", "kittel"},
|
||||
"mappe": {"schulmappe", "hefter", "ringbuch", "ordner"},
|
||||
"klarsichthülle": {"prospekthülle", "dokumentenhülle", "sichthülle"},
|
||||
"zirkel": {"zeichenzirkel", "geometriezirkel"},
|
||||
"geodreieck": {"winkelmesser", "zeichendreieck", "geometriedreieck"},
|
||||
"globus": {"erdkugel", "weltkugel", "schulglob"},
|
||||
"whiteboard": {"magnettafel", "schreibtafel", "flipchart"},
|
||||
"kreide": {"tafelkreide", "schulkreide", "bunkreide"},
|
||||
"kindergarten": {"kita", "krippe", "vorschule", "kiga"},
|
||||
"schule": {"grundschule", "volksschule", "schulbedarf"},
|
||||
},
|
||||
},
|
||||
4: IndexationParams{
|
||||
LocalizedAttributes: []*meilisearch.LocalizedAttributes{
|
||||
{AttributePatterns: []string{"*"}, Locales: []string{"ces"}},
|
||||
},
|
||||
StopWords: []string{
|
||||
"a", "i", "v", "na", "do", "se", "ne", "to", "že",
|
||||
"o", "jak", "ale", "po", "za", "pro", "při", "pro",
|
||||
"nebo", "či", "anebo", "jejich", "jeho", "její",
|
||||
"ten", "ta", "to", "toho", "té", "tom", "těch",
|
||||
"je", "jsou", "být", "byl", "byla", "bylo", "bude",
|
||||
"už", "ještě", "také", "jen", "tedy", "však",
|
||||
"co", "kdo", "který", "která", "které", "ze",
|
||||
"by", "mu", "ho", "ji", "tu", "tam", "tak",
|
||||
},
|
||||
Synonyms: map[string][]string{
|
||||
"batoh": {"batohy", "batohu", "batohů", "školní batoh", "školní taška", "aktovka"},
|
||||
"aktovka": {"aktovky", "školní aktovka", "batoh", "taška do školy"},
|
||||
"penál": {"penály", "penálu", "pouzdro na tužky", "pouzdro na psací potřeby"},
|
||||
"sešit": {"sešity", "sešitu", "sešitů", "poznámkový blok", "zápisník"},
|
||||
"pastelka": {"pastelky", "pastelku", "pastelkách", "barevná tužka", "barevné tužky"},
|
||||
"voskovka": {"voskovky", "vosková pastelka", "voskové pastelky"},
|
||||
"barva": {"barvy", "barev", "školní barva", "plakátová barva", "temperová barva", "vodová barva", "prstová barva"},
|
||||
"nůžky": {"dětské nůžky", "bezpečné nůžky", "školní nůžky"},
|
||||
"pravítko": {"pravítka", "plastové pravítko", "dřevěné pravítko"},
|
||||
"kalkulačka": {"kalkulačky", "školní kalkulačka", "vědecká kalkulačka"},
|
||||
"propiska": {"propiska", "propisky", "kuličkové pero", "pero", "roller"},
|
||||
"tužka": {"tužky", "grafitová tužka", "školní tužka", "kreslicí tužka"},
|
||||
"guma": {"gumy", "guma na mazání", "mazací guma", "korektor"},
|
||||
"ořezávátko": {"ořezávátka", "elektrické ořezávátko"},
|
||||
"plastelína": {"plastelíny", "modelovací hmota", "modelína", "play-doh", "hlína"},
|
||||
"hedvábný papír": {"krepový papír", "krepp", "krepa"},
|
||||
"karton": {"kartony", "výkres", "výkresy", "bristol", "čtvrtka"},
|
||||
"skicák": {"skicáky", "blok na kreslení", "kreslicí blok", "náčrtník"},
|
||||
"lepidlo": {"lepidla", "lepicí tyčinka", "PVA lepidlo", "UHU", "lepidlo v tyčince"},
|
||||
"lepicí páska": {"scotch", "průhledná páska", "izolepa"},
|
||||
"fix": {"fixy", "fixů", "fixů", "popisovač", "popisovače", "zvýrazňovač"},
|
||||
"štětec": {"štětce", "malířský štětec", "akvarelový štětec"},
|
||||
"plakát": {"plakáty", "vzdělávací plakát", "výuková tabule", "nástěnná mapa"},
|
||||
"slovník": {"slovníky", "školní slovník", "výkladový slovník"},
|
||||
"kniha": {"knihy", "dětská kniha", "čítanka", "učebnice", "obrázkové knihy"},
|
||||
"hračka": {"hračky", "vzdělávací hračka", "didaktická hračka"},
|
||||
"puzzle": {"skládačka", "skládačky", "mozaika", "jigsaw"},
|
||||
"kostky": {"stavebnice", "lego", "dřevěné kostky", "duplo"},
|
||||
"zástěra": {"zástěry", "malířská zástěra", "ochranný plášť"},
|
||||
"desky": {"složka", "složky", "pořadač", "kroužkový pořadač", "portfolio"},
|
||||
"fólie": {"průhledná fólie", "eurodesky", "plastová fólie"},
|
||||
"kružítko": {"kružítka", "rýsovací kružítko"},
|
||||
"úhloměr": {"pravítko s úhloměrem", "trojúhelník", "rýsovací trojúhelník"},
|
||||
"glóbus": {"glóbusy", "zeměkoule", "školní glóbus"},
|
||||
"tabule": {"magnetická tabule", "whiteboard", "flipchart", "školní tabule"},
|
||||
"křída": {"křídy", "barevná křída", "školní křída"},
|
||||
"školka": {"mateřská škola", "MŠ", "předškolní věk", "jesle"},
|
||||
"škola": {"školní", "základní škola", "ZŠ", "školní potřeby"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2,16 +2,23 @@ package meiliService
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
)
|
||||
|
||||
// MeiliIndexSettings holds the configurable index settings
|
||||
type MeiliIndexSettings struct {
|
||||
SearchableAttributes []string `json:"searchableAttributes"`
|
||||
DisplayedAttributes []string `json:"displayedAttributes"`
|
||||
FilterableAttributes []string `json:"filterableAttributes"`
|
||||
SortableAttributes []string `json:"sortableAttributes"`
|
||||
}
|
||||
|
||||
type MeiliService struct {
|
||||
productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo
|
||||
meiliClient meilisearch.ServiceManager
|
||||
@@ -30,43 +37,147 @@ func New() *MeiliService {
|
||||
}
|
||||
}
|
||||
|
||||
func getIndexName(id_lang uint) string {
|
||||
func GetIndexName(id_lang uint) string {
|
||||
return fmt.Sprintf("shop_%d_lang_%d", constdata.SHOP_ID, id_lang)
|
||||
}
|
||||
|
||||
// ==================================== FOR TESTING ONLY ====================================
|
||||
func (s *MeiliService) CreateIndex(id_lang uint) error {
|
||||
indexName := getIndexName(id_lang)
|
||||
indexName := GetIndexName(id_lang)
|
||||
|
||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
|
||||
const batchSize = 500
|
||||
offset := 0
|
||||
|
||||
for {
|
||||
// Get batch of products from repo (includes scanning)
|
||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
||||
}
|
||||
|
||||
// If no products returned, we're done
|
||||
if len(products) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Add batch to index
|
||||
if err := s.addBatchToIndex(indexName, products); err != nil {
|
||||
return fmt.Errorf("failed to add batch to index: %w", err)
|
||||
}
|
||||
|
||||
// Update offset for next batch
|
||||
offset += batchSize
|
||||
|
||||
fmt.Printf("Indexed %d products (offset: %d)\n", len(products), offset)
|
||||
}
|
||||
|
||||
// Configure filterable attributes
|
||||
filterableAttributes := []interface{}{
|
||||
"product_id",
|
||||
"category_id",
|
||||
"category_ids",
|
||||
"attr",
|
||||
"feat",
|
||||
"variations",
|
||||
"price",
|
||||
}
|
||||
task, err := s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get products: %w", err)
|
||||
return fmt.Errorf("failed to update filterable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for filterable task: %w", err)
|
||||
}
|
||||
|
||||
if len(products) == 0 {
|
||||
return nil
|
||||
// Configure sortable attributes
|
||||
sortableAttributes := []string{
|
||||
"price",
|
||||
"name",
|
||||
"product_id",
|
||||
"name",
|
||||
"category_ids",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateSortableAttributes(&sortableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update sortable attributes: %w", err)
|
||||
}
|
||||
|
||||
// Process products: prepare for indexing
|
||||
for i := range products {
|
||||
// Convert JSON fields to searchable strings
|
||||
if len(products[i].Features) > 0 {
|
||||
products[i].FeaturesStr = string(products[i].Features)
|
||||
}
|
||||
if len(products[i].Attributes) > 0 {
|
||||
products[i].AttributesStr = string(products[i].Attributes)
|
||||
}
|
||||
if len(products[i].AttributeFilters) > 0 {
|
||||
products[i].AttributeFiltersStr = string(products[i].AttributeFilters)
|
||||
}
|
||||
|
||||
// Build cover image URL from image ID
|
||||
if products[i].IDImage > 0 {
|
||||
products[i].CoverImage = config.Get().Image.ImagePrefix + fmt.Sprintf("/%d", products[i].IDImage)
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateSettings(&meilisearch.Settings{
|
||||
LocalizedAttributes: indexatio_params[id_lang].LocalizedAttributes,
|
||||
Synonyms: indexatio_params[id_lang].Synonyms,
|
||||
StopWords: indexatio_params[id_lang].StopWords,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ranking rules: %w", err)
|
||||
}
|
||||
|
||||
// Add documents to index
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for sortable task: %w", err)
|
||||
}
|
||||
|
||||
// Configure displayed attributes
|
||||
displayedAttributes := []string{
|
||||
"product_id",
|
||||
"name",
|
||||
"ean13",
|
||||
"reference",
|
||||
"variations",
|
||||
"id_image",
|
||||
"price",
|
||||
"category_name",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update displayed attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for displayed task: %w", err)
|
||||
}
|
||||
|
||||
// Configure searchable attributes
|
||||
searchableAttributes := []string{
|
||||
"name",
|
||||
"description",
|
||||
"ean13",
|
||||
"category_name",
|
||||
"reference",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update searchable attributes: %w", err)
|
||||
}
|
||||
|
||||
task, err = s.meiliClient.Index(indexName).UpdateFaceting(&meilisearch.Faceting{MaxValuesPerFacet: 5})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ranking rules: %w", err)
|
||||
}
|
||||
|
||||
// keyRequest := &meilisearch.Key{
|
||||
// Key: "my secret key", // ✅ provide your own token
|
||||
// Description: "Search-only key for frontend",
|
||||
// Actions: []string{"search", "settings.get"},
|
||||
// Indexes: []string{indexName}, // restrict to your index
|
||||
// }
|
||||
|
||||
// createdKey, err := s.meiliClient.CreateKey(keyRequest)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// fmt.Println("Custom search key created:", createdKey.Key)
|
||||
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for searchable task: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addBatchToIndex adds a batch of products to the Meilisearch index
|
||||
func (s *MeiliService) addBatchToIndex(indexName string, products []model.MeiliSearchProduct) error {
|
||||
primaryKey := "product_id"
|
||||
docOptions := &meilisearch.DocumentOptions{
|
||||
PrimaryKey: &primaryKey,
|
||||
@@ -86,112 +197,12 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
||||
return fmt.Errorf("task failed: %v", finishedTask.Error)
|
||||
}
|
||||
|
||||
// Configure filterable attributes
|
||||
filterableAttributes := []interface{}{
|
||||
"product_id",
|
||||
"category_id",
|
||||
"category_ids",
|
||||
"active",
|
||||
"attribute_filters",
|
||||
"features",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update filterable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for filterable task: %w", err)
|
||||
}
|
||||
|
||||
// Configure sortable attributes
|
||||
sortableAttributes := []string{
|
||||
"price",
|
||||
"name",
|
||||
"product_id",
|
||||
"category_ids",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateSortableAttributes(&sortableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update sortable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for sortable task: %w", err)
|
||||
}
|
||||
|
||||
// Configure displayed attributes
|
||||
displayedAttributes := []string{
|
||||
"product_id",
|
||||
"name",
|
||||
"ean13",
|
||||
"reference",
|
||||
"variations",
|
||||
"id_image",
|
||||
"price",
|
||||
"category_name",
|
||||
"category_ids",
|
||||
"attribute_filters",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update displayed attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for displayed task: %w", err)
|
||||
}
|
||||
|
||||
// Configure searchable attributes
|
||||
searchableAttributes := []string{
|
||||
"name",
|
||||
"description",
|
||||
"description_short",
|
||||
"usage",
|
||||
"features_str",
|
||||
"attributes_str",
|
||||
"reference",
|
||||
"ean13",
|
||||
"category_name",
|
||||
}
|
||||
task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update searchable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for searchable task: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================================== FOR TESTING ONLY ====================================
|
||||
func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
|
||||
indexName := getIndexName(id_lang)
|
||||
|
||||
searchReq := &meilisearch.SearchRequest{
|
||||
Limit: 4,
|
||||
Facets: []string{
|
||||
"CategoryID",
|
||||
},
|
||||
}
|
||||
|
||||
// Perform search
|
||||
results, err := s.meiliClient.Index(indexName).Search("mat", searchReq)
|
||||
if err != nil {
|
||||
fmt.Printf("Meilisearch error: %v\n", err)
|
||||
return meilisearch.SearchResponse{}, err
|
||||
}
|
||||
|
||||
fmt.Printf("Search results for query 'mat' in %s: %d hits\n", indexName, len(results.Hits))
|
||||
|
||||
return *results, nil
|
||||
}
|
||||
|
||||
// Search performs a full-text search on the specified index
|
||||
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
|
||||
indexName := getIndexName(id_lang)
|
||||
indexName := GetIndexName(id_lang)
|
||||
|
||||
filter_query := "Active = 1"
|
||||
if id_category != 0 {
|
||||
@@ -228,7 +239,7 @@ func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
|
||||
|
||||
// GetIndexSettings retrieves the current settings for a specific index
|
||||
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
|
||||
indexName := getIndexName(id_lang)
|
||||
indexName := GetIndexName(id_lang)
|
||||
|
||||
index := s.meiliClient.Index(indexName)
|
||||
|
||||
@@ -273,12 +284,62 @@ func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, e
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// remove all tags from HTML text
|
||||
func cleanHTML(s string) string {
|
||||
// Simple regex to remove all HTML tags
|
||||
re := regexp.MustCompile(`<[^>]*>`)
|
||||
result := re.ReplaceAllString(s, "")
|
||||
// Replace multiple spaces with single space
|
||||
result = regexp.MustCompile(`\s+`).ReplaceAllString(result, " ")
|
||||
return strings.TrimSpace(result)
|
||||
// UpdateIndexSettings updates the index settings (searchable, displayed, filterable, sortable attributes)
|
||||
func (s *MeiliService) UpdateIndexSettings(id_lang uint, settings MeiliIndexSettings) error {
|
||||
indexName := GetIndexName(id_lang)
|
||||
index := s.meiliClient.Index(indexName)
|
||||
|
||||
// Update searchable attributes
|
||||
if len(settings.SearchableAttributes) > 0 {
|
||||
task, err := index.UpdateSearchableAttributes(&settings.SearchableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update searchable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for searchable attributes task: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update displayed attributes
|
||||
if len(settings.DisplayedAttributes) > 0 {
|
||||
task, err := index.UpdateDisplayedAttributes(&settings.DisplayedAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update displayed attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for displayed attributes task: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update filterable attributes
|
||||
if len(settings.FilterableAttributes) > 0 {
|
||||
var filterable []interface{}
|
||||
for _, attr := range settings.FilterableAttributes {
|
||||
filterable = append(filterable, attr)
|
||||
}
|
||||
task, err := index.UpdateFilterableAttributes(&filterable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update filterable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for filterable attributes task: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update sortable attributes
|
||||
if len(settings.SortableAttributes) > 0 {
|
||||
task, err := index.UpdateSortableAttributes(&settings.SortableAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update sortable attributes: %w", err)
|
||||
}
|
||||
_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for sortable attributes task: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
233
app/service/searchService/searchService.go
Normal file
233
app/service/searchService/searchService.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package searchservice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
attributerepo "git.ma-al.com/goc_daniel/b2b/app/repos/attributeRepo"
|
||||
categoryrepo "git.ma-al.com/goc_daniel/b2b/app/repos/categoryRepo"
|
||||
featurerepo "git.ma-al.com/goc_daniel/b2b/app/repos/featureRepo"
|
||||
searchrepo "git.ma-al.com/goc_daniel/b2b/app/repos/searchRepo"
|
||||
)
|
||||
|
||||
type SearchService struct {
|
||||
searchRepo searchrepo.UISearchRepo
|
||||
attributeRepo attributerepo.UIAttributeRepo
|
||||
featureRepo featurerepo.UIFeatureRepo
|
||||
categoryRepo categoryrepo.UICategoryRepo
|
||||
}
|
||||
|
||||
func New() *SearchService {
|
||||
return &SearchService{
|
||||
searchRepo: searchrepo.New(),
|
||||
attributeRepo: attributerepo.New(),
|
||||
featureRepo: featurerepo.New(),
|
||||
categoryRepo: categoryrepo.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchService) Search(index string, body []byte, idLang uint) (*searchrepo.SearchProxyResponse, error) {
|
||||
resp, err := s.searchRepo.Search(index, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(resp.Body, &data); err == nil {
|
||||
data = s.filterEmptyFacets(data)
|
||||
data = s.enrichFacetTranslations(data, idLang)
|
||||
resp.Body, _ = json.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *SearchService) GetIndexSettings(index string) (*searchrepo.SearchProxyResponse, error) {
|
||||
return s.searchRepo.GetIndexSettings(index)
|
||||
}
|
||||
|
||||
func (s *SearchService) IsIndexNotFound(body []byte) bool {
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return false
|
||||
}
|
||||
if code, ok := resp["code"].(string); ok {
|
||||
return code == "index_not_found"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SearchService) filterEmptyFacets(data map[string]interface{}) map[string]interface{} {
|
||||
if facets, ok := data["facetDistribution"].(map[string]interface{}); ok {
|
||||
filtered := make(map[string]interface{})
|
||||
for k, v := range facets {
|
||||
if m, ok := v.(map[string]interface{}); ok && len(m) > 0 {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
data["facetDistribution"] = filtered
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *SearchService) enrichFacetTranslations(data map[string]interface{}, idLang uint) map[string]interface{} {
|
||||
facets, ok := data["facetDistribution"].(map[string]interface{})
|
||||
if !ok {
|
||||
return data
|
||||
}
|
||||
|
||||
groupIDs := []uint{}
|
||||
attrIDs := []uint{}
|
||||
|
||||
featureIDs := []uint{}
|
||||
featureValueIDs := []uint{}
|
||||
|
||||
categoryIDs := []uint{}
|
||||
|
||||
for key, values := range facets {
|
||||
valueMap, ok := values.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, "attr.") {
|
||||
groupIDStr := strings.TrimPrefix(key, "attr.")
|
||||
groupID, err := strconv.ParseUint(groupIDStr, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
groupIDs = append(groupIDs, uint(groupID))
|
||||
|
||||
for attrKey := range valueMap {
|
||||
attrID, err := strconv.ParseUint(attrKey, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
attrIDs = append(attrIDs, uint(attrID))
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, "feat.") {
|
||||
featureIDStr := strings.TrimPrefix(key, "feat.")
|
||||
featureID, err := strconv.ParseUint(featureIDStr, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
featureIDs = append(featureIDs, uint(featureID))
|
||||
|
||||
for valueKey := range valueMap {
|
||||
valueID, err := strconv.ParseUint(valueKey, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
featureValueIDs = append(featureValueIDs, uint(valueID))
|
||||
}
|
||||
}
|
||||
|
||||
if key == "category_ids" {
|
||||
for catKey := range valueMap {
|
||||
catID, err := strconv.ParseUint(catKey, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
categoryIDs = append(categoryIDs, uint(catID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attributeGroups, _ := s.attributeRepo.GetAttributeGroupsWithAttributes(groupIDs, attrIDs, idLang)
|
||||
featureGroups, _ := s.featureRepo.GetFeaturesWithValues(featureIDs, featureValueIDs, idLang)
|
||||
categoryTranslations, _ := s.categoryRepo.GetCategoryTranslations(categoryIDs, idLang)
|
||||
|
||||
translations := make(map[string]interface{})
|
||||
|
||||
for _, groupID := range groupIDs {
|
||||
key := fmt.Sprintf("attr.%d", groupID)
|
||||
group, ok := attributeGroups[groupID]
|
||||
if !ok {
|
||||
group = attributerepo.AttributeGroupWithAttrs{
|
||||
GroupName: key,
|
||||
Attrs: make(map[uint]attributerepo.AttributeWithColor),
|
||||
}
|
||||
}
|
||||
|
||||
valueTranslations := make(map[string]interface{})
|
||||
for attrID, attr := range group.Attrs {
|
||||
attrName := attr.Name
|
||||
if attrName == "" {
|
||||
attrName = strconv.FormatUint(uint64(attrID), 10)
|
||||
}
|
||||
entry := map[string]string{"t": attrName}
|
||||
if attr.Color != "" {
|
||||
entry["c"] = attr.Color
|
||||
}
|
||||
valueTranslations[strconv.FormatUint(uint64(attrID), 10)] = entry
|
||||
}
|
||||
|
||||
groupName := group.GroupName
|
||||
if groupName == "" {
|
||||
groupName = key
|
||||
}
|
||||
|
||||
translations[key] = map[string]interface{}{
|
||||
"groupName": groupName,
|
||||
"values": valueTranslations,
|
||||
}
|
||||
}
|
||||
|
||||
for _, featureID := range featureIDs {
|
||||
key := fmt.Sprintf("feat.%d", featureID)
|
||||
feature, ok := featureGroups[featureID]
|
||||
if !ok {
|
||||
feature = featurerepo.FeatureGroupWithValues{
|
||||
FeatureName: key,
|
||||
Values: make(map[uint]string),
|
||||
}
|
||||
}
|
||||
|
||||
valueTranslations := make(map[string]interface{})
|
||||
for valueID, valueName := range feature.Values {
|
||||
if valueName == "" {
|
||||
valueName = strconv.FormatUint(uint64(valueID), 10)
|
||||
}
|
||||
valueTranslations[strconv.FormatUint(uint64(valueID), 10)] = map[string]string{
|
||||
"t": valueName,
|
||||
}
|
||||
}
|
||||
|
||||
featureName := feature.FeatureName
|
||||
if featureName == "" {
|
||||
featureName = key
|
||||
}
|
||||
|
||||
translations[key] = map[string]interface{}{
|
||||
"groupName": featureName,
|
||||
"values": valueTranslations,
|
||||
}
|
||||
}
|
||||
|
||||
categoryValueTranslations := make(map[string]interface{})
|
||||
for _, catID := range categoryIDs {
|
||||
catName := categoryTranslations[catID]
|
||||
if catName == "" {
|
||||
catName = strconv.FormatUint(uint64(catID), 10)
|
||||
}
|
||||
categoryValueTranslations[strconv.FormatUint(uint64(catID), 10)] = map[string]string{
|
||||
"t": catName,
|
||||
}
|
||||
}
|
||||
|
||||
if len(categoryValueTranslations) > 0 {
|
||||
translations["category_ids"] = map[string]interface{}{
|
||||
"groupName": "category_ids",
|
||||
"values": categoryValueTranslations,
|
||||
}
|
||||
}
|
||||
|
||||
data["facetTranslations"] = translations
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user