update_categories #62
@@ -10,7 +10,6 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -115,26 +114,6 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequireAdmin creates admin-only middleware
|
|
||||||
func RequireAdmin() fiber.Handler {
|
|
||||||
return func(c fiber.Ctx) error {
|
|
||||||
originalUserRole, ok := localeExtractor.GetOriginalUserRole(c)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
|
||||||
"error": "not authenticated",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.CustomerRole(originalUserRole.Name) != model.RoleAdmin {
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
|
||||||
"error": "admin access required",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webdav
|
// Webdav
|
||||||
func Webdav() fiber.Handler {
|
func Webdav() fiber.Handler {
|
||||||
authService := authService.NewAuthService()
|
authService := authService.NewAuthService()
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ package perms
|
|||||||
type Permission string
|
type Permission string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserReadAny Permission = "user.read.any"
|
UserReadAny Permission = "user.read.any"
|
||||||
UserWriteAny Permission = "user.write.any"
|
UserWriteAny Permission = "user.write.any"
|
||||||
UserDeleteAny Permission = "user.delete.any"
|
UserDeleteAny Permission = "user.delete.any"
|
||||||
CurrencyWrite Permission = "currency.write"
|
CurrencyWrite Permission = "currency.write"
|
||||||
SpecificPriceManage Permission = "specific_price.manage"
|
SpecificPriceManage Permission = "specific_price.manage"
|
||||||
|
CreateWebdavToken Permission = "webdav.create_token"
|
||||||
|
ProductTranslationSave Permission = "product_translation.save"
|
||||||
|
ProductTranslationTranslate Permission = "product_translation.translate"
|
||||||
|
SearchCreateIndex Permission = "search.create_index"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ var columnMappingListProducts map[string]string = map[string]string{
|
|||||||
"category_id": "cp.id_category",
|
"category_id": "cp.id_category",
|
||||||
"quantity": "sa.quantity",
|
"quantity": "sa.quantity",
|
||||||
"is_favorite": "ps.is_favorite",
|
"is_favorite": "ps.is_favorite",
|
||||||
|
"is_new": "is_new",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"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/delivery/middleware"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
@@ -35,8 +35,8 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewProductTranslationHandler()
|
handler := NewProductTranslationHandler()
|
||||||
|
|
||||||
r.Get("/get-product-description", handler.GetProductDescription)
|
r.Get("/get-product-description", handler.GetProductDescription)
|
||||||
r.Post("/save-product-description", handler.SaveProductDescription)
|
r.Post("/save-product-description", middleware.Require("product_translation.save"), handler.SaveProductDescription)
|
||||||
|
|
|||||||
r.Get("/translate-product-description", handler.TranslateProductDescription)
|
r.Get("/translate-product-description", middleware.Require("product_translation.translate"), handler.TranslateProductDescription)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -80,12 +80,6 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
|
||||||
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
|
||||||
}
|
|
||||||
|
|
||||||
productID_attribute := c.Query("productID")
|
productID_attribute := c.Query("productID")
|
||||||
productID, err := strconv.Atoi(productID_attribute)
|
productID, err := strconv.Atoi(productID_attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,12 +117,6 @@ func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) err
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
|
||||||
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
|
||||||
}
|
|
||||||
|
|
||||||
productID_attribute := c.Query("productID")
|
productID_attribute := c.Query("productID")
|
||||||
productID, err := strconv.Atoi(productID_attribute)
|
productID, err := strconv.Atoi(productID_attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
|
||||||
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
|
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
@@ -30,7 +30,7 @@ func NewMeiliSearchHandler() *MeiliSearchHandler {
|
|||||||
func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
handler := NewMeiliSearchHandler()
|
handler := NewMeiliSearchHandler()
|
||||||
|
|
||||||
r.Get("/create-index", handler.CreateIndex)
|
r.Get("/create-index", middleware.Require("search.create_index"), handler.CreateIndex)
|
||||||
r.Post("/search", handler.Search)
|
r.Post("/search", handler.Search)
|
||||||
r.Post("/settings", handler.GetSettings)
|
r.Post("/settings", handler.GetSettings)
|
||||||
|
|
||||||
@@ -44,12 +44,6 @@ func (h *MeiliSearchHandler) CreateIndex(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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
|
||||||
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.meiliService.CreateIndex(id_lang)
|
err := h.meiliService.CreateIndex(id_lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("CreateIndex error: %v\n", err)
|
fmt.Printf("CreateIndex error: %v\n", err)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"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/delivery/middleware"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/storageService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/storageService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
@@ -34,7 +34,7 @@ func StorageHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
r.Get("/download-file/*", handler.DownloadFile)
|
r.Get("/download-file/*", handler.DownloadFile)
|
||||||
|
|
||||||
// for admins only
|
// for admins only
|
||||||
r.Get("/create-new-webdav-token", handler.CreateNewWebdavToken)
|
r.Get("/create-new-webdav-token", middleware.Require("webdav.create_token"), handler.CreateNewWebdavToken)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -84,12 +84,6 @@ func (h *StorageHandler) CreateNewWebdavToken(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
|
||||||
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
|
||||||
}
|
|
||||||
|
|
||||||
new_token, err := h.storageService.NewWebdavToken(userID)
|
new_token, err := h.storageService.NewWebdavToken(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
|||||||
@@ -161,16 +161,6 @@ func (s *Server) Setup() error {
|
|||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// // Admin routes example
|
|
||||||
// admin := s.api.Group("/admin")
|
|
||||||
// admin.Use(middleware.AuthMiddleware())
|
|
||||||
// admin.Use(middleware.RequireAdmin())
|
|
||||||
// admin.Get("/users", func(c fiber.Ctx) error {
|
|
||||||
// return c.JSON(fiber.Map{
|
|
||||||
// "message": "Admin area - user management",
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// keep this at the end because its wilderange
|
// keep this at the end because its wilderange
|
||||||
general.InitBo(s.App())
|
general.InitBo(s.App())
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type ProductInList struct {
|
|||||||
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
||||||
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
||||||
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
||||||
|
IsNew uint `gorm:"column:is_new" json:"is_new"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductFilters struct {
|
type ProductFilters struct {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"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/model/dbmodel"
|
||||||
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/WinterYukky/gorm-extra-clause-plugin/exclause"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +16,6 @@ type UIProductDescriptionRepo interface {
|
|||||||
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
|
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
|
||||||
CreateIfDoesNotExist(productID uint, productid_lang uint) error
|
CreateIfDoesNotExist(productID uint, productid_lang uint) error
|
||||||
UpdateFields(productID uint, productid_lang uint, updates map[string]string) 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{}
|
type ProductDescriptionRepo struct{}
|
||||||
@@ -118,108 +116,3 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uin
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.Get().
|
|
||||||
Table("ps_product_shop ps").
|
|
||||||
Select(`
|
|
||||||
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
|
|
||||||
|
|
||||||
return products, err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -116,7 +116,19 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
|
|||||||
p.reference AS reference,
|
p.reference AS reference,
|
||||||
COALESCE(v.variants_number, 0) AS variants_number,
|
COALESCE(v.variants_number, 0) AS variants_number,
|
||||||
sa.quantity AS quantity,
|
sa.quantity AS quantity,
|
||||||
COALESCE(f.is_favorite, 0) AS is_favorite
|
COALESCE(f.is_favorite, 0) AS is_favorite,
|
||||||
|
CASE
|
||||||
|
WHEN ps.date_add >= DATE_SUB(
|
||||||
|
NOW(),
|
||||||
|
INTERVAL (
|
||||||
|
SELECT value
|
||||||
|
FROM ps_configuration
|
||||||
|
WHERE name = 'PS_NB_DAYS_NEW_PRODUCT'
|
||||||
|
) DAY
|
||||||
|
) AND ps.active = 1
|
||||||
|
THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END AS is_new
|
||||||
`, config.Get().Image.ImagePrefix).
|
`, config.Get().Image.ImagePrefix).
|
||||||
Joins("JOIN "+dbmodel.PsProductCols.IDProduct.Tab()+" p ON p.id_product = ps.id_product").
|
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 = ?", langID).
|
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_lang = ?", langID).
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"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"
|
||||||
|
"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 SearchProxyResponse struct {
|
type SearchProxyResponse struct {
|
||||||
@@ -17,6 +21,7 @@ type SearchProxyResponse struct {
|
|||||||
|
|
||||||
type UISearchRepo interface {
|
type UISearchRepo interface {
|
||||||
Search(index string, body []byte) (*SearchProxyResponse, error)
|
Search(index string, body []byte) (*SearchProxyResponse, error)
|
||||||
|
GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error)
|
||||||
GetIndexSettings(index string) (*SearchProxyResponse, error)
|
GetIndexSettings(index string) (*SearchProxyResponse, error)
|
||||||
GetRoutes(langId uint) ([]model.Route, error)
|
GetRoutes(langId uint) ([]model.Route, error)
|
||||||
}
|
}
|
||||||
@@ -80,3 +85,108 @@ func (r *SearchRepo) doRequest(method, url string, body []byte) (*SearchProxyRes
|
|||||||
func (r *SearchRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
func (r *SearchRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMeiliProductsProducts returns a batch of products with LIMIT/OFFSET pagination
|
||||||
|
// The scanning is done inside the repo to keep the service layer cleaner
|
||||||
|
func (r *SearchRepo) GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error) {
|
||||||
|
|
||||||
|
var products []model.MeiliSearchProduct
|
||||||
|
|
||||||
|
err := db.Get().
|
||||||
|
Table("ps_product_shop ps").
|
||||||
|
Select(`
|
||||||
|
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
|
||||||
|
|
||||||
|
return products, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"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/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
searchrepo "git.ma-al.com/goc_daniel/b2b/app/repos/searchRepo"
|
||||||
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,8 +20,8 @@ type MeiliIndexSettings struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MeiliService struct {
|
type MeiliService struct {
|
||||||
productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo
|
searchRepo searchrepo.UISearchRepo
|
||||||
meiliClient meilisearch.ServiceManager
|
meiliClient meilisearch.ServiceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *MeiliService {
|
func New() *MeiliService {
|
||||||
@@ -32,8 +32,8 @@ func New() *MeiliService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return &MeiliService{
|
return &MeiliService{
|
||||||
meiliClient: client,
|
meiliClient: client,
|
||||||
productDescriptionRepo: productDescriptionRepo.New(),
|
searchRepo: searchrepo.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
// Get batch of products from repo (includes scanning)
|
// Get batch of products from repo (includes scanning)
|
||||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
products, err := s.searchRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,6 @@ func GetUserID(c fiber.Ctx) (uint, bool) {
|
|||||||
return user_locale.User.ID, true
|
return user_locale.User.ID, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOriginalUserRole(c fiber.Ctx) (model.Role, bool) {
|
|
||||||
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
|
||||||
if !ok || user_locale.OriginalUser == nil || user_locale.OriginalUser.Role == nil {
|
|
||||||
return model.Role{}, false
|
|
||||||
}
|
|
||||||
return *user_locale.OriginalUser.Role, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCustomer(c fiber.Ctx) (*model.Customer, bool) {
|
func GetCustomer(c fiber.Ctx) (*model.Customer, bool) {
|
||||||
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
||||||
if !ok || user_locale.User == nil {
|
if !ok || user_locale.User == nil {
|
||||||
|
|||||||
@@ -35,15 +35,27 @@ INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('2', 'user.write.any');
|
|||||||
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('3', 'user.delete.any');
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('3', 'user.delete.any');
|
||||||
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('4', 'currency.write');
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('4', 'currency.write');
|
||||||
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('5', 'specific_price.manage');
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('5', 'specific_price.manage');
|
||||||
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('6', 'webdav.create_token');
|
||||||
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('7', 'product_translation.save');
|
||||||
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('8', 'product_translation.translate');
|
||||||
|
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('9', 'search.create_index');
|
||||||
|
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '1');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '1');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '2');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '2');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '3');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '3');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '4');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '4');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '5');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '5');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '6');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '7');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '8');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '9');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '1');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '1');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '2');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '2');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '3');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '3');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '4');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '4');
|
||||||
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '5');
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '5');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '6');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '7');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '8');
|
||||||
|
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '9');
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
Reference in New Issue
Block a user
I just remembered that the purpose of defining the permissions in permissions.go is to minimize the usage of hardcoded strings in handler and instead use perms.Permission const.
in this instance it would be perms.ProductTranslationSave