fix products listing
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user