Merge pull request 'fix top menu embeding struct' (#34) from routing into main
Reviewed-on: #34
This commit was merged in pull request #34.
This commit is contained in:
2
.env
2
.env
@@ -57,4 +57,4 @@ FILE_MAAL_PL_USER=git_operator
|
|||||||
FILE_MAAL_PL_PASSWORD=1FnwqcEgIUjQHjt1
|
FILE_MAAL_PL_PASSWORD=1FnwqcEgIUjQHjt1
|
||||||
|
|
||||||
IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta
|
IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta
|
||||||
CORS_ORGIN=https://www.naluconcept.com
|
CORS_ORGIN=https://www.naluconcept.com
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type Config struct {
|
|||||||
GoogleTranslate GoogleTranslateConfig
|
GoogleTranslate GoogleTranslateConfig
|
||||||
Image ImageConfig
|
Image ImageConfig
|
||||||
Cors CorsConfig
|
Cors CorsConfig
|
||||||
|
MailiSearch MeiliSearchConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type I18n struct {
|
type I18n struct {
|
||||||
@@ -38,6 +39,11 @@ type CorsConfig struct {
|
|||||||
Origins []string `env:"CORS_ORGIN"`
|
Origins []string `env:"CORS_ORGIN"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MeiliSearchConfig struct {
|
||||||
|
ServerURL string `env:"MEILISEARCH_URL"`
|
||||||
|
ApiKey string `env:"MEILISEARCH_API_KEY"`
|
||||||
|
}
|
||||||
|
|
||||||
type ImageConfig struct {
|
type ImageConfig struct {
|
||||||
ImagePrefix string `env:"IMAGE_PREFIX"`
|
ImagePrefix string `env:"IMAGE_PREFIX"`
|
||||||
}
|
}
|
||||||
@@ -186,6 +192,12 @@ func load() *Config {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
|
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = loadEnv(&cfg.MailiSearch)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
|
||||||
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
// for testing purposes only. Must be removed before proper release.
|
// for testing purposes only. Must be removed before proper release.
|
||||||
r.Get("/create-index", handler.CreateIndex)
|
r.Get("/create-index", handler.CreateIndex)
|
||||||
r.Get("/test", handler.Test)
|
r.Get("/test", handler.Test)
|
||||||
|
r.Get("/settings", handler.GetSettings)
|
||||||
|
|
||||||
// for all users
|
// for all users
|
||||||
r.Get("/search", handler.Search)
|
r.Get("/search", handler.Search)
|
||||||
@@ -77,13 +78,6 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
|||||||
|
|
||||||
query := c.Query("query")
|
query := c.Query("query")
|
||||||
|
|
||||||
limit_attribute := c.Query("limit")
|
|
||||||
limit, err := strconv.Atoi(limit_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
id_category_attribute := c.Query("id_category", "0")
|
id_category_attribute := c.Query("id_category", "0")
|
||||||
id_category, err := strconv.Atoi(id_category_attribute)
|
id_category, err := strconv.Atoi(id_category_attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,21 +85,7 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
price_lower_bound_attribute := c.Query("price_lower_bound", "-1.0")
|
meili_response, err := h.meiliService.Search(id_lang, query, uint(id_category))
|
||||||
price_lower_bound, err := strconv.ParseFloat(price_lower_bound_attribute, 64)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
price_upper_bound_attribute := c.Query("price_upper_bound", "-1.0")
|
|
||||||
price_upper_bound, err := strconv.ParseFloat(price_upper_bound_attribute, 64)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
meili_response, err := h.meiliService.Search(id_lang, query, uint(limit), uint(id_category), price_lower_bound, price_upper_bound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
@@ -113,3 +93,19 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
|||||||
|
|
||||||
return c.JSON(response.Make(&meili_response, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&meili_response, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
|
||||||
|
id_lang, ok := c.Locals("langID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
settings, err := h.meiliService.GetIndexSettings(id_lang)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&settings, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ type ProductRow struct {
|
|||||||
type MeiliSearchProduct struct {
|
type MeiliSearchProduct struct {
|
||||||
ProductID uint `gorm:"column:id_product"`
|
ProductID uint `gorm:"column:id_product"`
|
||||||
Name string `gorm:"column:name"`
|
Name string `gorm:"column:name"`
|
||||||
Active uint8 `gorm:"column:active"`
|
|
||||||
Price float64 `gorm:"column:price"`
|
|
||||||
Description string `gorm:"column:description"`
|
Description string `gorm:"column:description"`
|
||||||
DescriptionShort string `gorm:"column:description_short"`
|
DescriptionShort string `gorm:"column:description_short"`
|
||||||
Usage string `gorm:"column:used_for"`
|
Usage string `gorm:"column:used_for"`
|
||||||
@@ -44,4 +42,6 @@ type MeiliSearchProduct struct {
|
|||||||
CategoryID uint `gorm:"column:id_category"`
|
CategoryID uint `gorm:"column:id_category"`
|
||||||
CategoryName string `gorm:"column:category_name"`
|
CategoryName string `gorm:"column:category_name"`
|
||||||
Variations uint `gorm:"column:variations"`
|
Variations uint `gorm:"column:variations"`
|
||||||
|
CategoryIDs []uint `gorm:"-"` // All category IDs including children for filtering
|
||||||
|
CoverImage string `gorm:"-"` // Cover image URL (not indexed)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type B2BTopMenu struct {
|
import "encoding/json"
|
||||||
MenuID int `gorm:"column:menu_id;primaryKey;autoIncrement" json:"menu_id"`
|
|
||||||
Label string `gorm:"column:label;type:longtext;not null;default:'{}'" json:"label"`
|
|
||||||
ParentID *int `gorm:"column:parent_id;index:FK_b2b_top_menu_parent_id" json:"parent_id,omitempty"`
|
|
||||||
Params string `gorm:"column:params;type:longtext;not null;default:'{}'" json:"params"`
|
|
||||||
Active int8 `gorm:"column:active;type:tinyint;not null;default:1" json:"active"`
|
|
||||||
Position int `gorm:"column:position;not null;default:1" json:"position"`
|
|
||||||
|
|
||||||
Parent *B2BTopMenu `gorm:"foreignKey:ParentID;references:MenuID;constraint:OnDelete:RESTRICT,OnUpdate:RESTRICT" json:"parent,omitempty"`
|
type B2BTopMenu struct {
|
||||||
Children []B2BTopMenu `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
MenuID int `gorm:"column:menu_id;primaryKey;autoIncrement" json:"menu_id"`
|
||||||
|
Label json.RawMessage `gorm:"column:label;type:longtext;not null;default:'{}'" json:"label"`
|
||||||
|
ParentID *int `gorm:"column:parent_id;index:FK_b2b_top_menu_parent_id" json:"parent_id,omitempty"`
|
||||||
|
Params string `gorm:"column:params;type:longtext;not null;default:'{}'" json:"params"`
|
||||||
|
Active int8 `gorm:"column:active;type:tinyint;not null;default:1" json:"active"`
|
||||||
|
Position int `gorm:"column:position;not null;default:1" json:"position"`
|
||||||
|
|
||||||
|
Parent *B2BTopMenu `gorm:"foreignKey:ParentID;references:MenuID;constraint:OnDelete:RESTRICT,OnUpdate:RESTRICT" json:"parent,omitempty"`
|
||||||
|
Children []*B2BTopMenu `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (B2BTopMenu) TableName() string {
|
func (B2BTopMenu) TableName() string {
|
||||||
|
|||||||
@@ -79,36 +79,96 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint
|
|||||||
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
|
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
|
||||||
var products []model.MeiliSearchProduct
|
var products []model.MeiliSearchProduct
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.Debug().
|
||||||
|
// Select(`
|
||||||
|
// ps.id_product AS id_product,
|
||||||
|
// pl.name AS name,
|
||||||
|
// ps.price AS price,
|
||||||
|
// pl.description AS description,
|
||||||
|
// pl.description_short AS description_short,
|
||||||
|
// pl.usage AS used_for,
|
||||||
|
// p.ean13 AS ean13,
|
||||||
|
// p.reference AS reference,
|
||||||
|
// p.width AS width,
|
||||||
|
// p.height AS height,
|
||||||
|
// p.depth AS depth,
|
||||||
|
// p.weight AS weight,
|
||||||
|
// ps.id_category_default AS id_category,
|
||||||
|
// cl.name AS category_name,
|
||||||
|
// COUNT(DISTINCT pas.id_product_attribute) AS variations
|
||||||
|
// `).
|
||||||
|
// Table("ps_product_shop AS ps").
|
||||||
|
// Joins("LEFT JOIN ps_product_lang AS pl ON ps.id_product = pl.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
|
// Joins("LEFT JOIN ps_product AS p ON p.id_product = ps.id_product").
|
||||||
|
// Joins("LEFT JOIN ps_category_lang AS cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
|
// Joins("LEFT JOIN ps_product_attribute_shop AS pas ON pas.id_product = ps.id_product AND pas.id_shop = ?", constdata.SHOP_ID).
|
||||||
|
// Where("ps.id_shop = ? AND ps.active = 1", constdata.SHOP_ID).
|
||||||
|
// Group("ps.id_product").
|
||||||
|
|
||||||
Select(`
|
Select(`
|
||||||
ps.id_product AS id_product,
|
ps.id_product AS id_product,
|
||||||
pl.name AS name,
|
pl.name AS name,
|
||||||
ps.active AS active,
|
|
||||||
ps.price AS price,
|
|
||||||
pl.description AS description,
|
pl.description AS description,
|
||||||
pl.description_short AS description_short,
|
pl.description_short AS description_short,
|
||||||
pl.usage AS used_for,
|
pl.usage AS used_for,
|
||||||
p.ean13 AS ean13,
|
p.ean13 AS ean13,
|
||||||
p.reference AS reference,
|
p.reference AS reference,
|
||||||
p.width AS width,
|
|
||||||
p.height AS height,
|
|
||||||
p.depth AS depth,
|
|
||||||
p.weight AS weight,
|
|
||||||
ps.id_category_default AS id_category,
|
ps.id_category_default AS id_category,
|
||||||
cl.name AS category_name,
|
cl.name AS category_name,
|
||||||
COUNT(DISTINCT pas.id_product_attribute) AS variations
|
cl.link_rewrite as link_rewrite,
|
||||||
|
COUNT(DISTINCT pas.id_product_attribute) AS variations,
|
||||||
|
pis.id_image AS id_image,
|
||||||
|
GROUP_CONCAT(DISTINCT pcp.id_category) AS category_ids
|
||||||
`).
|
`).
|
||||||
Table("ps_product_shop AS ps").
|
Table("ps_product_shop AS ps").
|
||||||
Joins("LEFT JOIN ps_product_lang AS pl ON ps.id_product = pl.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
Joins("LEFT JOIN ps_product_lang AS pl ON ps.id_product = pl.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
Joins("LEFT JOIN ps_product AS p ON p.id_product = ps.id_product").
|
Joins("LEFT JOIN ps_product AS p ON p.id_product = ps.id_product").
|
||||||
Joins("LEFT JOIN ps_category_lang AS cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
Joins("LEFT JOIN ps_category_lang AS cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
Joins("LEFT JOIN ps_product_attribute_shop AS pas ON pas.id_product = ps.id_product AND pas.id_shop = ?", constdata.SHOP_ID).
|
Joins("LEFT JOIN ps_product_attribute_shop AS pas ON pas.id_product = ps.id_product AND pas.id_shop = ?", constdata.SHOP_ID).
|
||||||
Where("ps.id_shop = ?", constdata.SHOP_ID).
|
Joins("JOIN ps_image_shop AS pis ON pis.id_product = ps.id_product AND pis.cover = 1").
|
||||||
Group("ps.id_product").
|
Joins("JOIN ps_category_product AS pcp ON pcp.id_product = ps.id_product").
|
||||||
|
Where("ps.id_shop = ? AND ps.active = 1", constdata.SHOP_ID).
|
||||||
|
Group("pcp.id_product, ps.id_product").
|
||||||
Scan(&products).Error
|
Scan(&products).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return products, fmt.Errorf("database error: %w", err)
|
return products, fmt.Errorf("database error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all category IDs for each product (including child categories)
|
||||||
|
for i := range products {
|
||||||
|
var categoryIDs []uint
|
||||||
|
// Find all parent categories and their children using nested set
|
||||||
|
err := db.DB.
|
||||||
|
Table("ps_category AS c").
|
||||||
|
Select("c.id_category").
|
||||||
|
Joins("JOIN ps_category_product AS cp ON cp.id_category = c.id_category").
|
||||||
|
Joins("JOIN ps_category AS parent ON c.nleft >= parent.nleft AND c.nright <= parent.nright AND parent.id_category = ?", products[i].CategoryID).
|
||||||
|
Where("cp.id_product = ?", products[i].ProductID).
|
||||||
|
Group("c.id_category").
|
||||||
|
Pluck("c.id_category", &categoryIDs).Error
|
||||||
|
if err != nil {
|
||||||
|
continue // Skip if error, use default category
|
||||||
|
}
|
||||||
|
if len(categoryIDs) > 0 {
|
||||||
|
products[i].CategoryIDs = categoryIDs
|
||||||
|
} else {
|
||||||
|
products[i].CategoryIDs = []uint{products[i].CategoryID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cover image for the product
|
||||||
|
var imageID int
|
||||||
|
err = db.DB.
|
||||||
|
Table("ps_image AS i").
|
||||||
|
Select("i.id_image").
|
||||||
|
Joins("LEFT JOIN ps_image_shop AS iss ON iss.id_image = i.id_image AND iss.id_shop = ?", constdata.SHOP_ID).
|
||||||
|
Where("i.id_product = ? AND (i.cover = 1 OR i.cover IS TRUE)", products[i].ProductID).
|
||||||
|
Order("i.position ASC").
|
||||||
|
Limit(1).
|
||||||
|
Pluck("i.id_image", &imageID).Error
|
||||||
|
if err == nil && imageID > 0 {
|
||||||
|
products[i].CoverImage = fmt.Sprintf("%d/%d.jpg", products[i].ProductID, imageID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return products, nil
|
return products, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package meiliService
|
package meiliService
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"regexp"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"github.com/meilisearch/meilisearch-go"
|
"github.com/meilisearch/meilisearch-go"
|
||||||
@@ -20,12 +19,10 @@ type MeiliService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New() *MeiliService {
|
func New() *MeiliService {
|
||||||
meiliURL := os.Getenv("MEILISEARCH_URL")
|
|
||||||
meiliAPIKey := os.Getenv("MEILISEARCH_API_KEY")
|
|
||||||
|
|
||||||
client := meilisearch.New(
|
client := meilisearch.New(
|
||||||
meiliURL,
|
config.Get().MailiSearch.ServerURL,
|
||||||
meilisearch.WithAPIKey(meiliAPIKey),
|
meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey),
|
||||||
)
|
)
|
||||||
|
|
||||||
return &MeiliService{
|
return &MeiliService{
|
||||||
@@ -40,32 +37,9 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
|||||||
|
|
||||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
|
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
|
||||||
for i := 0; i < len(products); i++ {
|
for i := 0; i < len(products); i++ {
|
||||||
products[i].Description, err = cleanHTML(products[i].Description)
|
products[i].Description = cleanHTML(products[i].Description)
|
||||||
if err != nil {
|
products[i].DescriptionShort = cleanHTML(products[i].DescriptionShort)
|
||||||
fmt.Printf("products[i].Description: %v\n", products[i].Description)
|
products[i].Usage = cleanHTML(products[i].Usage)
|
||||||
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
|
|
||||||
fmt.Println("failed at description")
|
|
||||||
fmt.Printf("err: %v\n", err)
|
|
||||||
return 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryKey := "ProductID"
|
primaryKey := "ProductID"
|
||||||
@@ -83,7 +57,7 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
|||||||
|
|
||||||
filterableAttributes := []interface{}{
|
filterableAttributes := []interface{}{
|
||||||
"CategoryID",
|
"CategoryID",
|
||||||
"Price",
|
"CategoryIDs",
|
||||||
}
|
}
|
||||||
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -96,11 +70,10 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
|||||||
displayedAttributes := []string{
|
displayedAttributes := []string{
|
||||||
"ProductID",
|
"ProductID",
|
||||||
"Name",
|
"Name",
|
||||||
"Active",
|
|
||||||
"Price",
|
|
||||||
"EAN13",
|
"EAN13",
|
||||||
"Reference",
|
"Reference",
|
||||||
"Variations",
|
"Variations",
|
||||||
|
"CoverImage",
|
||||||
}
|
}
|
||||||
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -154,28 +127,17 @@ func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search performs a full-text search on the specified index
|
// 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) {
|
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
|
||||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
||||||
|
|
||||||
filter_query := ""
|
filter_query := "Active = 1"
|
||||||
if id_category != 0 {
|
if id_category != 0 {
|
||||||
filter_query = "CategoryID = " + strconv.FormatUint(uint64(id_category), 10)
|
// Use CategoryIDs to include products from child categories
|
||||||
}
|
filter_query += fmt.Sprintf(" AND CategoryIDs = %d", id_category)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchReq := &meilisearch.SearchRequest{
|
searchReq := &meilisearch.SearchRequest{
|
||||||
Limit: int64(limit),
|
Limit: 30,
|
||||||
Facets: []string{
|
Facets: []string{
|
||||||
"CategoryID",
|
"CategoryID",
|
||||||
},
|
},
|
||||||
@@ -201,40 +163,59 @@ func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
|
|||||||
return health, nil
|
return health, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all tags from HTML text
|
// GetIndexSettings retrieves the current settings for a specific index
|
||||||
func cleanHTML(s string) (string, error) {
|
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
|
||||||
r := strings.NewReader(s)
|
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
||||||
d := xml.NewDecoder(r)
|
|
||||||
|
|
||||||
text := ""
|
index := s.meiliClient.Index(indexName)
|
||||||
|
|
||||||
// Configure the decoder for HTML; leave off strict and autoclose for XHTML
|
result := make(map[string]interface{})
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := token.(type) {
|
// Get searchable attributes
|
||||||
case xml.StartElement:
|
searchable, err := index.GetSearchableAttributes()
|
||||||
if len(text) > 0 && text[len(text)-1] != '\n' {
|
if err == nil {
|
||||||
text += " \n "
|
result["searchableAttributes"] = searchable
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
case xml.CharData:
|
|
||||||
if strings.TrimSpace(string(v)) != "" {
|
|
||||||
text += string(v)
|
|
||||||
}
|
|
||||||
case xml.Comment:
|
|
||||||
case xml.ProcInst:
|
|
||||||
case xml.Directive:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return text, nil
|
// 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,40 +98,37 @@ func (a ByPosition) Len() int { return len(a) }
|
|||||||
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||||
|
|
||||||
func (s *MenuService) GetTopMenu(id uint) ([]model.B2BTopMenu, error) {
|
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
|
||||||
items, err := s.routesRepo.GetTopMenu(id)
|
items, err := s.routesRepo.GetTopMenu(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(items) == 0 {
|
menuMap := make(map[int]*model.B2BTopMenu, len(items))
|
||||||
return []model.B2BTopMenu{}, nil
|
roots := make([]*model.B2BTopMenu, 0)
|
||||||
}
|
|
||||||
|
|
||||||
// Build a map with empty children slices
|
|
||||||
itemMap := make(map[int]model.B2BTopMenu, len(items))
|
|
||||||
for i := range items {
|
for i := range items {
|
||||||
items[i].Children = []model.B2BTopMenu{}
|
menu := &items[i]
|
||||||
itemMap[items[i].MenuID] = items[i]
|
menu.Children = make([]*model.B2BTopMenu, 0)
|
||||||
|
menuMap[menu.MenuID] = menu
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the tree
|
for i := range items {
|
||||||
var roots []model.B2BTopMenu
|
menu := &items[i]
|
||||||
for _, item := range itemMap {
|
|
||||||
if item.ParentID == nil || *item.ParentID == 0 {
|
if menu.ParentID == nil {
|
||||||
roots = append(roots, itemMap[item.MenuID])
|
roots = append(roots, menu)
|
||||||
} else {
|
continue
|
||||||
parentID := *item.ParentID
|
|
||||||
if parent, exists := itemMap[parentID]; exists {
|
|
||||||
parent.Children = append(parent.Children, item)
|
|
||||||
itemMap[parentID] = parent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update roots with children
|
parent, ok := menuMap[*menu.ParentID]
|
||||||
for i := range roots {
|
if !ok {
|
||||||
roots[i] = itemMap[roots[i].MenuID]
|
// fallback for orphaned nodes
|
||||||
|
roots = append(roots, menu)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.Children = append(parent.Children, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
return roots, nil
|
return roots, nil
|
||||||
|
|||||||
@@ -43,32 +43,16 @@ CREATE TABLE IF NOT EXISTS b2b_top_menu (
|
|||||||
INDEX FK_b2b_top_menu_parent_id_idx (parent_id ASC)
|
INDEX FK_b2b_top_menu_parent_id_idx (parent_id ASC)
|
||||||
) ENGINE = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
INSERT IGNORE INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`)
|
|
||||||
VALUES
|
|
||||||
-- ROOT
|
|
||||||
(1, JSON_COMPACT('{"name":"root","trans":{"pl":"Menu główne","en":"Main Menu","de":"Hauptmenü"}}'), NULL, '{}'),
|
|
||||||
|
|
||||||
-- LEVEL 1
|
INSERT INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES
|
||||||
(2, JSON_COMPACT('{"name":"dashboard","trans":{"pl":"Panel","en":"Dashboard","de":"Dashboard"}}'), 1, '{}'),
|
(1, JSON_COMPACT('{"name":"root","trans":{"pl":{"label":"Menu główne"},"en":{"label":"Main Menu"},"de":{"label":"Hauptmenü"}}}'),NULL,'{}',1,1),
|
||||||
(3, JSON_COMPACT('{"name":"orders","trans":{"pl":"Zamówienia","en":"Orders","de":"Bestellungen"}}'), 1, '{}'),
|
(3, JSON_COMPACT('{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}'),1,'{}',1,1),
|
||||||
(4, JSON_COMPACT('{"name":"customers","trans":{"pl":"Klienci","en":"Customers","de":"Kunden"}}'), 1, '{}'),
|
(9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,'{}',1,1);
|
||||||
(5, JSON_COMPACT('{"name":"products","trans":{"pl":"Produkty","en":"Products","de":"Produkte"}}'), 1, '{}'),
|
|
||||||
(6, JSON_COMPACT('{"name":"reports","trans":{"pl":"Raporty","en":"Reports","de":"Berichte"}}'), 1, '{}'),
|
|
||||||
|
|
||||||
-- LEVEL 2 (Orders)
|
|
||||||
(7, JSON_COMPACT('{"name":"order_list","trans":{"pl":"Lista zamówień","en":"Order List","de":"Bestellliste"}}'), 3, '{}'),
|
|
||||||
(8, JSON_COMPACT('{"name":"pending_orders","trans":{"pl":"Oczekujące zamówienia","en":"Pending Orders","de":"Ausstehende Bestellungen"}}'), 3, '{}'),
|
|
||||||
(9, JSON_COMPACT('{"name":"carts","trans":{"pl":"Koszyki","en":"Carts","de":"Warenkörbe"}}'), 3, '{}'),
|
|
||||||
|
|
||||||
-- LEVEL 2 (Products)
|
|
||||||
(10, JSON_COMPACT('{"name":"product_list","trans":{"pl":"Lista produktów","en":"Product List","de":"Produktliste"}}'), 5, '{}'),
|
|
||||||
(11, JSON_COMPACT('{"name":"categories","trans":{"pl":"Kategorie","en":"Categories","de":"Kategorien"}}'), 5, '{}'),
|
|
||||||
(12, JSON_COMPACT('{"name":"inventory","trans":{"pl":"Magazyn","en":"Inventory","de":"Lagerbestand"}}'), 5, '{}'),
|
|
||||||
|
|
||||||
-- LEVEL 2 (Customers)
|
|
||||||
(13, JSON_COMPACT('{"name":"customer_list","trans":{"pl":"Lista klientów","en":"Customer List","de":"Kundenliste"}}'), 4, '{}'),
|
|
||||||
(14, JSON_COMPACT('{"name":"customer_groups","trans":{"pl":"Grupy klientów","en":"Customer Groups","de":"Kundengruppen"}}'), 4, '{}');
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
|
|
||||||
DROP TABLE IF EXISTS b2b_routes;
|
DROP TABLE IF EXISTS b2b_routes;
|
||||||
|
DROP TABLE IF EXISTS b2b_top_menu;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user