Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures

This commit is contained in:
2026-03-30 15:17:53 +02:00
444 changed files with 10123 additions and 3830 deletions

View File

@@ -0,0 +1,109 @@
package attributerepo
import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
)
type AttributeWithColor struct {
ID uint
Name string
Color string
}
type AttributeGroupWithAttrs struct {
GroupName string
Attrs map[uint]AttributeWithColor
}
type UIAttributeRepo interface {
GetAttributeGroupsWithAttributes(groupIDs []uint, attrIDs []uint, idLang uint) (map[uint]AttributeGroupWithAttrs, error)
}
type AttributeRepo struct{}
func New() UIAttributeRepo {
return &AttributeRepo{}
}
func (r *AttributeRepo) GetAttributeGroupsWithAttributes(groupIDs []uint, attrIDs []uint, idLang uint) (map[uint]AttributeGroupWithAttrs, error) {
result := make(map[uint]AttributeGroupWithAttrs)
if len(groupIDs) == 0 && len(attrIDs) == 0 {
return result, nil
}
if len(attrIDs) > 0 {
type attrResult struct {
IDAttribute int32
IDAttributeGroup int32
GroupName string
AttrName string
Color string
}
var attrs []attrResult
if err := db.Get().
Model(dbmodel.PsAttribute{}).
Select(`
ps_attribute.id_attribute,
ps_attribute.id_attribute_group,
COALESCE(ps_attribute_group_lang.name, '') as group_name,
COALESCE(ps_attribute_lang.name, '') as attr_name,
ps_attribute.color
`).
Joins("LEFT JOIN ps_attribute_lang ON ps_attribute_lang.id_attribute = ps_attribute.id_attribute AND ps_attribute_lang.id_lang = ?", idLang).
Joins("LEFT JOIN ps_attribute_group_lang ON ps_attribute_group_lang.id_attribute_group = ps_attribute.id_attribute_group AND ps_attribute_group_lang.id_lang = ?", idLang).
Where("ps_attribute.id_attribute IN ?", attrIDs).
Scan(&attrs).Error; err != nil {
return nil, err
}
for _, a := range attrs {
if _, exists := result[uint(a.IDAttributeGroup)]; !exists {
result[uint(a.IDAttributeGroup)] = AttributeGroupWithAttrs{
GroupName: a.GroupName,
Attrs: make(map[uint]AttributeWithColor),
}
}
result[uint(a.IDAttributeGroup)].Attrs[uint(a.IDAttribute)] = AttributeWithColor{
ID: uint(a.IDAttribute),
Name: a.AttrName,
Color: a.Color,
}
}
}
if len(groupIDs) > 0 {
type groupResult struct {
IDAttributeGroup int32
GroupName string
}
var groups []groupResult
if err := db.Get().
Model(dbmodel.PsAttributeGroupLang{}).
Select("ps_attribute_group_lang.id_attribute_group, COALESCE(ps_attribute_group_lang.name, '') as group_name").
Where("ps_attribute_group_lang.id_attribute_group IN ?", groupIDs).
Where("ps_attribute_group_lang.id_lang = ?", idLang).
Scan(&groups).Error; err != nil {
return nil, err
}
for _, g := range groups {
if existing, ok := result[uint(g.IDAttributeGroup)]; ok {
if g.GroupName != "" {
existing.GroupName = g.GroupName
result[uint(g.IDAttributeGroup)] = existing
}
} else {
result[uint(g.IDAttributeGroup)] = AttributeGroupWithAttrs{
GroupName: g.GroupName,
Attrs: make(map[uint]AttributeWithColor),
}
}
}
}
return result, nil
}

View File

@@ -3,11 +3,12 @@ package categoriesRepo
import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
)
type UICategoriesRepo interface {
GetAllCategories(id_lang uint) ([]model.ScannedCategory, error)
GetAllCategories(idLang uint) ([]model.ScannedCategory, error)
}
type CategoriesRepo struct{}
@@ -16,11 +17,16 @@ func New() UICategoriesRepo {
return &CategoriesRepo{}
}
func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCategory, error) {
func (r *CategoriesRepo) GetAllCategories(idLang uint) ([]model.ScannedCategory, error) {
var allCategories []model.ScannedCategory
err := db.DB.
Table("ps_category").
categoryTbl := (&dbmodel.PsCategory{}).TableName()
categoryLangTbl := (&dbmodel.PsCategoryLang{}).TableName()
categoryShopTbl := (&dbmodel.PsCategoryShop{}).TableName()
langTbl := (&dbmodel.PsLang{}).TableName()
err := db.Get().
Model(dbmodel.PsCategory{}).
Select(`
ps_category.id_category AS category_id,
ps_category_lang.name AS name,
@@ -31,21 +37,12 @@ func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCateg
ps_category_lang.link_rewrite AS link_rewrite,
ps_lang.iso_code AS iso_code
`).
Joins(`
LEFT JOIN ps_category_lang
ON ps_category_lang.id_category = ps_category.id_category
AND ps_category_lang.id_shop = ?
AND ps_category_lang.id_lang = ?
`, constdata.SHOP_ID, id_lang).
Joins(`
LEFT JOIN ps_category_shop
ON ps_category_shop.id_category = ps_category.id_category
AND ps_category_shop.id_shop = ?
`, constdata.SHOP_ID).
Joins(`
JOIN ps_lang
ON ps_lang.id_lang = ps_category_lang.id_lang
`).
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ? AND ??.id_lang = ?`,
categoryLangTbl, categoryLangTbl, categoryTbl, categoryLangTbl, constdata.SHOP_ID, categoryLangTbl, idLang).
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ?`,
categoryShopTbl, categoryShopTbl, categoryTbl, categoryShopTbl, constdata.SHOP_ID).
Joins(`JOIN ? ON ??.id_lang = ??.id_lang`,
langTbl, langTbl, categoryLangTbl).
Scan(&allCategories).Error
return allCategories, err

View File

@@ -0,0 +1,44 @@
package categoryrepo
import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
)
type UICategoryRepo interface {
GetCategoryTranslations(ids []uint, idLang uint) (map[uint]string, error)
}
type CategoryRepo struct{}
func New() UICategoryRepo {
return &CategoryRepo{}
}
func (r *CategoryRepo) GetCategoryTranslations(ids []uint, idLang uint) (map[uint]string, error) {
if len(ids) == 0 {
return map[uint]string{}, nil
}
type result struct {
IDCategory int32
Name string
}
var results []result
if err := db.Get().
Model(dbmodel.PsCategoryLang{}).
Select("ps_category_lang.id_category, COALESCE(ps_category_lang.name, '') as name").
Where("ps_category_lang.id_category IN ?", ids).
Where("ps_category_lang.id_lang = ?", idLang).
Scan(&results).Error; err != nil {
return nil, err
}
translations := make(map[uint]string, len(results))
for _, res := range results {
translations[uint(res.IDCategory)] = res.Name
}
return translations, nil
}

View File

@@ -0,0 +1,95 @@
package featurerepo
import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
)
type FeatureGroupWithValues struct {
FeatureName string
Values map[uint]string
}
type UIFeatureRepo interface {
GetFeaturesWithValues(featureIDs []uint, featureValueIDs []uint, idLang uint) (map[uint]FeatureGroupWithValues, error)
}
type FeatureRepo struct{}
func New() UIFeatureRepo {
return &FeatureRepo{}
}
func (r *FeatureRepo) GetFeaturesWithValues(featureIDs []uint, featureValueIDs []uint, idLang uint) (map[uint]FeatureGroupWithValues, error) {
result := make(map[uint]FeatureGroupWithValues)
if len(featureIDs) == 0 && len(featureValueIDs) == 0 {
return result, nil
}
if len(featureValueIDs) > 0 {
type valueResult struct {
IDFeatureValue int32
IDFeature int32
Value string
}
var values []valueResult
if err := db.Get().
Model(dbmodel.PsFeatureValueLang{}).
Select(`
ps_feature_value_lang.id_feature_value,
ps_feature_value.id_feature,
COALESCE(ps_feature_value_lang.value, '') as value
`).
Joins("LEFT JOIN ps_feature_value ON ps_feature_value.id_feature_value = ps_feature_value_lang.id_feature_value").
Where("ps_feature_value_lang.id_lang = ?", idLang).
Where("ps_feature_value_lang.id_feature_value IN ?", featureValueIDs).
Scan(&values).Error; err != nil {
return nil, err
}
for _, v := range values {
if _, exists := result[uint(v.IDFeature)]; !exists {
result[uint(v.IDFeature)] = FeatureGroupWithValues{
FeatureName: v.Value,
Values: make(map[uint]string),
}
}
result[uint(v.IDFeature)].Values[uint(v.IDFeatureValue)] = v.Value
}
}
if len(featureIDs) > 0 {
type featureResult struct {
IDFeature int32
FeatureName string
}
var features []featureResult
if err := db.Get().
Model(dbmodel.PsFeatureLang{}).
Select("ps_feature_lang.id_feature, COALESCE(ps_feature_lang.name, '') as feature_name").
Where("ps_feature_lang.id_feature IN ?", featureIDs).
Where("ps_feature_lang.id_lang = ?", idLang).
Scan(&features).Error; err != nil {
return nil, err
}
for _, f := range features {
if existing, ok := result[uint(f.IDFeature)]; ok {
if f.FeatureName != "" {
existing.FeatureName = f.FeatureName
result[uint(f.IDFeature)] = existing
}
} else {
result[uint(f.IDFeature)] = FeatureGroupWithValues{
FeatureName: f.FeatureName,
Values: make(map[uint]string),
}
}
}
}
return result, nil
}

View File

@@ -1,10 +1,13 @@
package listProductsRepo
import (
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
)
type UIListProductsRepo interface {
@@ -22,26 +25,32 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt
var total int64
query := db.Get().
Table("ps_product_shop AS ps").
Table("ps_product_shop ps").
Select(`
ps.id_product AS product_id,
pl.name AS name,
pl.link_rewrite AS link_rewrite,
CONCAT('https://www.naluconcept.com', '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
cl.name AS category_name,
p.reference AS reference,
COUNT(DISTINCT pas.id_product_attribute) AS variants_number,
sa.quantity AS quantity
`).
Joins("JOIN ps_product p ON p.id_product = ps.id_product").
Joins("JOIN ps_category_product cp ON ps.id_product = cp.id_product").
ps.id_product AS product_id,
pl.name AS name,
pl.link_rewrite AS link_rewrite,
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
cl.name AS category_name,
p.reference AS reference,
COALESCE(v.variants_number, 0) AS variants_number,
sa.quantity AS quantity
`, config.Get().Image.ImagePrefix).
Joins("JOIN "+dbmodel.PsProductCols.IDProduct.Tab()+" p ON p.id_product = ps.id_product").
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_lang = ?", id_lang).
Joins("JOIN ps_image_shop ims ON ims.id_product = ps.id_product AND ims.cover = 1").
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_lang = ?", id_lang).
Joins("LEFT JOIN ps_product_attribute_shop pas ON pas.id_product = cp.id_product").
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product").
Joins("JOIN ps_category_product cp ON cp.id_product = ps.id_product").
Joins("LEFT JOIN variants v ON v.id_product = ps.id_product").
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
Where("ps.active = ?", 1).
Group("cp.id_product")
Group("ps.id_product").
Clauses(exclause.With{CTEs: []exclause.CTE{
{
Name: "variants",
Subquery: exclause.Subquery{DB: db.Get().Model(&dbmodel.PsProductAttributeShop{}).Select("id_product", "COUNT(*) AS variants_number").Group("id_product")},
},
}})
// Apply all filters
if filt != nil {
@@ -58,7 +67,7 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt
Order("ps.id_product DESC").
Limit(p.Limit()).
Offset(p.Offset()).
Scan(&listing).Error
Find(&listing).Error
if err != nil {
return find.Found[model.ProductInList]{}, err
}

View File

@@ -16,22 +16,16 @@ func New() UILocaleSelectorRepo {
return &LocaleSelectorRepo{}
}
func (repo *LocaleSelectorRepo) GetLanguages() ([]model.Language, error) {
func (r *LocaleSelectorRepo) GetLanguages() ([]model.Language, error) {
var languages []model.Language
err := db.DB.Table("b2b_language").Scan(&languages).Error
err := db.Get().Find(&languages).Error
return languages, err
}
func (repo *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) {
func (r *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) {
var countries []model.Country
err := db.DB.
Table("b2b_countries").
Select("b2b_countries.id, b2b_countries.name, b2b_countries.flag, ps_currency.id_currency as id_currency, ps_currency.name as currency_name, ps_currency.iso_code as currency_iso_code").
Joins("LEFT JOIN ps_currency ON ps_currency.id_currency = b2b_countries.currency_id").
Scan(&countries).Error
err := db.Get().
Preload("PSCurrency").
Find(&countries).Error
return countries, err
}

View File

@@ -5,14 +5,16 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
)
type UIProductDescriptionRepo interface {
GetProductDescription(productID uint, productLangID uint) (*model.ProductDescription, error)
CreateIfDoesNotExist(productID uint, productLangID uint) error
UpdateFields(productID uint, productLangID uint, updates map[string]string) error
GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error)
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
CreateIfDoesNotExist(productID uint, productid_lang uint) error
UpdateFields(productID uint, productid_lang uint, updates map[string]string) error
GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error)
}
type ProductDescriptionRepo struct{}
@@ -22,12 +24,15 @@ func New() UIProductDescriptionRepo {
}
// We assume that any user has access to all product descriptions
func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productLangID uint) (*model.ProductDescription, error) {
func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error) {
var ProductDescription model.ProductDescription
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
err := db.Get().
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
First(&ProductDescription).Error
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
@@ -37,16 +42,19 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productLa
}
// If it doesn't exist, returns an error.
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productLangID uint) error {
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error {
record := model.ProductDescription{
ProductID: productID,
ShopID: constdata.SHOP_ID,
LangID: productLangID,
LangID: productid_lang,
}
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
err := db.Get().
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
FirstOrCreate(&record).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
@@ -55,7 +63,7 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productLan
return nil
}
func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint, updates map[string]string) error {
func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uint, updates map[string]string) error {
if len(updates) == 0 {
return nil
}
@@ -64,9 +72,13 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint
updatesIface[k] = v
}
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
err := db.Get().
Model(&dbmodel.PsProductLang{}).
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
Updates(updatesIface).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
@@ -75,100 +87,107 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint
return nil
}
// We assume that any user has access to all product descriptions
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
// GetMeiliProductsBatchedScanned returns a batch of products with LIMIT/OFFSET pagination
// The scanning is done inside the repo to keep the service layer cleaner
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error) {
var products []model.MeiliSearchProduct
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").
err := db.Get().
Table("ps_product_shop ps").
Select(`
ps.id_product AS id_product,
pl.name AS name,
pl.description AS description,
pl.description_short AS description_short,
pl.usage AS used_for,
p.ean13 AS ean13,
p.reference AS reference,
ps.id_category_default AS id_category,
cl.name AS category_name,
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").
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).
Joins("JOIN ps_image_shop AS pis ON pis.id_product = ps.id_product AND pis.cover = 1").
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
if err != nil {
return products, fmt.Errorf("database error: %w", err)
}
ps.id_product AS id_product,
pl.name AS name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
p.ean13,
p.reference,
ps.price,
ps.id_category_default AS id_category,
cl.name AS cat_name,
cl.link_rewrite AS l_rew,
COALESCE(vary.attributes, JSON_OBJECT()) AS attr,
COALESCE(feat.features, JSON_OBJECT()) AS feat,
img.id_image,
cat.category_ids,
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = ps.id_product AND pas2.id_shop = ?) AS variations
`, constdata.SHOP_ID).
Joins("JOIN ps_product p ON p.id_product = ps.id_product").
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
Joins("JOIN ps_category_lang 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 variations vary ON vary.id_product = ps.id_product").
Joins("LEFT JOIN features feat ON feat.id_product = ps.id_product").
Joins("LEFT JOIN images img ON img.id_product = ps.id_product").
Joins("LEFT JOIN categories cat ON cat.id_product = ps.id_product").
Joins("JOIN products_page pp ON pp.id_product = ps.id_product").
Where("ps.active = ?", 1).
Order("ps.id_product").
Clauses(exclause.With{CTEs: []exclause.CTE{
{
Name: "products_page",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsProductShop{}).
Select("id_product, price").
Where("id_shop = ? AND active = 1", constdata.SHOP_ID).
Order("id_product").
Limit(limit).
Offset(offset),
},
},
{
Name: "variation_attributes",
Subquery: exclause.Subquery{
DB: db.Get().
Table("ps_product_attribute_shop pas"). // <- explicit alias here
Select(`
pas.id_product,
pag.id_attribute_group AS attribute_name,
JSON_ARRAYAGG(DISTINCT pa.id_attribute) AS attribute_values
`).
Joins("JOIN ps_product_attribute_combination ppac ON ppac.id_product_attribute = pas.id_product_attribute").
Joins("JOIN ps_attribute pa ON pa.id_attribute = ppac.id_attribute").
Joins("JOIN ps_attribute_group pag ON pag.id_attribute_group = pa.id_attribute_group").
Where("pas.id_shop = ?", constdata.SHOP_ID).
Group("pas.id_product, pag.id_attribute_group"),
},
},
{
Name: "variations",
Subquery: exclause.Subquery{
DB: db.Get().
Table("variation_attributes").
Select("id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes").
Group("id_product"),
},
},
{
Name: "features",
Subquery: exclause.Subquery{
DB: db.Get().
Table("ps_feature_product pfp"). // <- explicit alias
Select("pfp.id_product, JSON_OBJECTAGG(pfp.id_feature, pfp.id_feature_value) AS features").
Group("pfp.id_product"),
},
},
{
Name: "images",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsImageShop{}).
Select("id_product, id_image").
Where("id_shop = ? AND cover = 1", constdata.SHOP_ID),
},
},
{
Name: "categories",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsCategoryProduct{}).
Select("id_product, JSON_ARRAYAGG(id_category) AS category_ids").
Group("id_product"),
},
},
}}).Find(&products).Error
// 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, err
}

View File

@@ -3,6 +3,7 @@ package routesrepo
import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
)
type UIRoutesRepo interface {
@@ -18,7 +19,7 @@ func New() UIRoutesRepo {
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
routes := []model.Route{}
err := db.DB.Find(&routes).Error
err := db.DB.Find(&routes, model.Route{Active: nullable.GetNil(true)}).Error
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,82 @@
package searchrepo
import (
"bytes"
"fmt"
"io"
"net/http"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/model"
)
type SearchProxyResponse struct {
StatusCode int
Body []byte
}
type UISearchRepo interface {
Search(index string, body []byte) (*SearchProxyResponse, error)
GetIndexSettings(index string) (*SearchProxyResponse, error)
GetRoutes(langId uint) ([]model.Route, error)
}
type SearchRepo struct {
cfg *config.Config
}
func New() UISearchRepo {
return &SearchRepo{
cfg: config.Get(),
}
}
func (r *SearchRepo) Search(index string, body []byte) (*SearchProxyResponse, error) {
url := fmt.Sprintf("%s/indexes/%s/search", r.cfg.MailiSearch.ServerURL, index)
return r.doRequest(http.MethodPost, url, body)
}
func (r *SearchRepo) GetIndexSettings(index string) (*SearchProxyResponse, error) {
url := fmt.Sprintf("%s/indexes/%s/settings", r.cfg.MailiSearch.ServerURL, index)
return r.doRequest(http.MethodGet, url, nil)
}
func (r *SearchRepo) doRequest(method, url string, body []byte) (*SearchProxyResponse, error) {
var reqBody *bytes.Reader
if body != nil {
reqBody = bytes.NewReader(body)
} else {
reqBody = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if r.cfg.MailiSearch.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.cfg.MailiSearch.ApiKey))
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to reach meilisearch: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
return &SearchProxyResponse{
StatusCode: resp.StatusCode,
Body: respBody,
}, nil
}
func (r *SearchRepo) GetRoutes(langId uint) ([]model.Route, error) {
return nil, nil
}