'routing, searching, fixes'

This commit is contained in:
2026-03-30 01:03:02 +02:00
462 changed files with 10774 additions and 4539 deletions

View File

@@ -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}}"
}
}

View 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: {
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: {
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: {
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: {
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"},
},
},
}

View File

@@ -1,31 +1,34 @@
package meiliService
import (
"encoding/xml"
"fmt"
"io"
"os"
"strconv"
"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
}
func New() *MeiliService {
meiliURL := os.Getenv("MEILISEARCH_URL")
meiliAPIKey := os.Getenv("MEILISEARCH_API_KEY")
client := meilisearch.New(
meiliURL,
meilisearch.WithAPIKey(meiliAPIKey),
config.Get().MailiSearch.ServerURL,
meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey),
)
return &MeiliService{
@@ -34,148 +37,181 @@ func New() *MeiliService {
}
}
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 := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
indexName := GetIndexName(id_lang)
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
for i := 0; i < len(products); i++ {
products[i].Description, err = cleanHTML(products[i].Description)
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 {
fmt.Printf("products[i].Description: %v\n", products[i].Description)
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
fmt.Println("failed at description")
fmt.Printf("err: %v\n", err)
return err
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
}
products[i].DescriptionShort, err = cleanHTML(products[i].DescriptionShort)
if err != nil {
fmt.Printf("products[i].DescriptionShort: %v\n", products[i].DescriptionShort)
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
fmt.Println("failed at description short")
fmt.Printf("err: %v\n", err)
return err
// If no products returned, we're done
if len(products) == 0 {
break
}
products[i].Usage, err = cleanHTML(products[i].Usage)
if err != nil {
fmt.Printf("products[i].Usage: %v\n", products[i].Usage)
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
fmt.Println("failed at usage")
fmt.Printf("err: %v\n", err)
return err
// 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)
}
primaryKey := "ProductID"
// 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 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",
"name",
"category_ids",
}
task, err = s.meiliClient.Index(indexName).UpdateSortableAttributes(&sortableAttributes)
if err != nil {
return fmt.Errorf("failed to update sortable attributes: %w", err)
}
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)
}
_, 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,
SkipCreation: false,
}
task, err := s.meiliClient.Index(indexName).AddDocuments(products, docOptions)
if err != nil {
return fmt.Errorf("meili AddDocuments error: %w", err)
return fmt.Errorf("failed to add documents: %w", err)
}
finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
fmt.Printf("Task status: %s\n", finishedTask.Status)
fmt.Printf("Task error: %s\n", finishedTask.Error)
filterableAttributes := []interface{}{
"CategoryID",
"Price",
}
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
if err != nil {
return fmt.Errorf("meili AddDocuments error: %w", err)
return fmt.Errorf("failed to wait for task: %w", err)
}
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
fmt.Printf("Task status: %s\n", finishedTask.Status)
fmt.Printf("Task error: %s\n", finishedTask.Error)
displayedAttributes := []string{
"ProductID",
"Name",
"Active",
"Price",
"EAN13",
"Reference",
"Variations",
}
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
if err != nil {
return fmt.Errorf("meili AddDocuments error: %w", err)
}
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
fmt.Printf("Task status: %s\n", finishedTask.Status)
fmt.Printf("Task error: %s\n", finishedTask.Error)
searchableAttributes := []string{
"Name",
"DescriptionShort",
"Reference",
"EAN13",
"CategoryName",
"Description",
"Usage",
}
task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
if err != nil {
return fmt.Errorf("meili AddDocuments error: %w", err)
}
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
fmt.Printf("Task status: %s\n", finishedTask.Status)
fmt.Printf("Task error: %s\n", finishedTask.Error)
return err
}
// ==================================== FOR TESTING ONLY ====================================
func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
searchReq := &meilisearch.SearchRequest{
Limit: 4,
Facets: []string{
"CategoryID",
},
if finishedTask.Status == "failed" {
return fmt.Errorf("task failed: %v", finishedTask.Error)
}
// 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
return nil
}
// Search performs a full-text search on the specified index
func (s *MeiliService) Search(id_lang uint, query string, limit uint, id_category uint, price_lower_bound float64, price_upper_bound float64) (meilisearch.SearchResponse, error) {
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
indexName := GetIndexName(id_lang)
filter_query := ""
filter_query := "Active = 1"
if id_category != 0 {
filter_query = "CategoryID = " + strconv.FormatUint(uint64(id_category), 10)
}
if price_lower_bound > 0.0 {
if filter_query != "" {
filter_query += " AND "
}
filter_query += "Price >= " + strconv.FormatFloat(price_lower_bound, 'f', -1, 64)
}
if price_upper_bound > 0.0 {
if filter_query != "" {
filter_query += " AND "
}
filter_query += "Price <= " + strconv.FormatFloat(price_upper_bound, 'f', -1, 64)
// Use CategoryIDs to include products from child categories
filter_query += fmt.Sprintf(" AND CategoryIDs = %d", id_category)
}
searchReq := &meilisearch.SearchRequest{
Limit: int64(limit),
Limit: 30,
Facets: []string{
"CategoryID",
},
@@ -201,40 +237,109 @@ func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
return health, nil
}
// remove all tags from HTML text
func cleanHTML(s string) (string, error) {
r := strings.NewReader(s)
d := xml.NewDecoder(r)
// GetIndexSettings retrieves the current settings for a specific index
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
indexName := GetIndexName(id_lang)
text := ""
index := s.meiliClient.Index(indexName)
// Configure the decoder for HTML; leave off strict and autoclose for XHTML
d.Strict = true
d.AutoClose = xml.HTMLAutoClose
d.Entity = xml.HTMLEntity
for {
token, err := d.Token()
if err == io.EOF {
break
} else if err != nil {
return text, err
result := make(map[string]interface{})
// Get searchable attributes
searchable, err := index.GetSearchableAttributes()
if err == nil {
result["searchableAttributes"] = searchable
}
// Get filterable attributes
filterable, err := index.GetFilterableAttributes()
if err == nil {
result["filterableAttributes"] = filterable
}
// Get displayed attributes
displayed, err := index.GetDisplayedAttributes()
if err == nil {
result["displayedAttributes"] = displayed
}
// Get ranking rules
ranking, err := index.GetRankingRules()
if err == nil {
result["rankingRules"] = ranking
}
// Get distinct attribute
distinct, err := index.GetDistinctAttribute()
if err == nil && distinct != nil {
result["distinctAttribute"] = *distinct
}
// Get typo tolerance
typo, err := index.GetTypoTolerance()
if err == nil {
result["typoTolerance"] = typo
}
return result, nil
}
// 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)
}
switch v := token.(type) {
case xml.StartElement:
if len(text) > 0 && text[len(text)-1] != '\n' {
text += " \n "
}
case xml.EndElement:
case xml.CharData:
if strings.TrimSpace(string(v)) != "" {
text += string(v)
}
case xml.Comment:
case xml.ProcInst:
case xml.Directive:
_, err = s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
if err != nil {
return fmt.Errorf("failed to wait for searchable attributes task: %w", err)
}
}
return text, nil
// 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
}