package meiliService import ( "fmt" "regexp" "strings" "time" "git.ma-al.com/goc_daniel/b2b/app/config" "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" ) type MeiliService struct { productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo meiliClient meilisearch.ServiceManager } func New() *MeiliService { client := meilisearch.New( config.Get().MailiSearch.ServerURL, meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey), ) return &MeiliService{ meiliClient: client, productDescriptionRepo: productDescriptionRepo.New(), } } 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) products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang) if err != nil { return fmt.Errorf("failed to get products: %w", err) } if len(products) == 0 { return nil } // 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) } } // Add documents to index 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("failed to add documents: %w", err) } finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond) if err != nil { return fmt.Errorf("failed to wait for task: %w", err) } if finishedTask.Status == "failed" { 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) filter_query := "Active = 1" if id_category != 0 { // Use CategoryIDs to include products from child categories filter_query += fmt.Sprintf(" AND CategoryIDs = %d", id_category) } searchReq := &meilisearch.SearchRequest{ Limit: 30, Facets: []string{ "CategoryID", }, Filter: filter_query, } results, err := s.meiliClient.Index(indexName).Search(query, searchReq) if err != nil { fmt.Printf("Meilisearch search error: %v\n", err) return meilisearch.SearchResponse{}, err } return *results, nil } // HealthCheck checks if Meilisearch is healthy and accessible func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) { health, err := s.meiliClient.Health() if err != nil { return nil, fmt.Errorf("meilisearch health check failed: %w", err) } return health, nil } // GetIndexSettings retrieves the current settings for a specific index func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) { indexName := getIndexName(id_lang) index := s.meiliClient.Index(indexName) 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 } // 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) }