favorites

This commit is contained in:
Daniel Goc
2026-04-09 14:49:50 +02:00
parent 9fb8e034fc
commit 393de36cb2
6 changed files with 65 additions and 20 deletions

View File

@@ -90,7 +90,13 @@ func (h *ProductsHandler) ListProducts(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
list, err := h.productService.Find(id_lang, paging, filters)
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
list, err := h.productService.Find(id_lang, userID, paging, filters)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))

View File

@@ -70,6 +70,7 @@ type ProductInList struct {
Reference string `gorm:"column:reference" json:"reference"`
VariantsNumber uint `gorm:"column:variants_number" json:"variants_number"`
Quantity int64 `gorm:"column:quantity" json:"quantity"`
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
}
type ProductFilters struct {

View File

@@ -16,7 +16,7 @@ import (
type UIProductsRepo interface {
GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error)
Find(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
Find(id_lang, userID uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
}
type ProductsRepo struct{}
@@ -37,7 +37,6 @@ func (repo *ProductsRepo) GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_custo
return nil, err
}
// Optional: validate it's valid JSON
if !json.Valid([]byte(productStr)) {
return nil, fmt.Errorf("invalid json returned from stored procedure")
}
@@ -46,7 +45,7 @@ func (repo *ProductsRepo) GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_custo
return &raw, nil
}
func (repo *ProductsRepo) Find(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
func (repo *ProductsRepo) Find(id_lang, userID uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
var list []model.ProductInList
var total int64
@@ -60,7 +59,8 @@ func (repo *ProductsRepo) Find(id_lang uint, p find.Paging, filt *filters.Filter
cl.name AS category_name,
p.reference AS reference,
COALESCE(v.variants_number, 0) AS variants_number,
sa.quantity AS quantity
sa.quantity AS quantity,
COALESCE(f.is_favorite, 0) AS is_favorite
`, 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).
@@ -68,15 +68,37 @@ func (repo *ProductsRepo) Find(id_lang uint, p find.Paging, filt *filters.Filter
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_lang = ?", id_lang).
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 favorites f ON f.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("ps.id_product").
Clauses(exclause.With{CTEs: []exclause.CTE{
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")},
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsProductAttributeShop{}).
Select("id_product", "COUNT(*) AS variants_number").
Group("id_product"),
},
}}).
},
{
Name: "favorites",
Subquery: exclause.Subquery{
DB: db.Get().
Table("b2b_favorites").
Select(`
product_id AS id_product,
COUNT(*) > 0 AS is_favorite
`).
Where("user_id = ?", userID).
Group("product_id"),
},
},
},
}).
Order("ps.id_product DESC")
// Apply all filters

View File

@@ -29,6 +29,6 @@ func (s *ProductService) GetJSON(p_id_product, p_id_lang, p_id_customer, b2b_id_
return products, nil
}
func (s *ProductService) Find(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
return s.productsRepo.Find(id_lang, p, filters)
func (s *ProductService) Find(id_lang, userID uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
return s.productsRepo.Find(id_lang, userID, p, filters)
}

View File

@@ -151,6 +151,16 @@ CREATE TABLE IF NOT EXISTS b2b_carts_products (
CREATE INDEX IF NOT EXISTS idx_carts_products_cart_id ON b2b_carts_products (cart_id);
-- favorites
CREATE TABLE IF NOT EXISTS b2b_favorites (
user_id BIGINT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
PRIMARY KEY (user_id, product_id),
CONSTRAINT fk_favorites_customer FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_favorites_product FOREIGN KEY (product_id) REFERENCES ps_product(id_product) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- refresh_tokens
CREATE TABLE IF NOT EXISTS b2b_refresh_tokens (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

View File

@@ -132,6 +132,12 @@ JSON_OBJECT(
m.name,
'category',
cl.name,
/* ================= FAVORITE ================= */
'is_favorite',
EXISTS(
SELECT 1 FROM b2b_favorites f
WHERE f.user_id = p_id_customer AND f.product_id = p_id_product
),
/* ================= IMAGE ================= */
'cover_image',
JSON_OBJECT(