package productDescriptionRepo import ( "errors" "fmt" "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" "gorm.io/gorm" ) type UIProductDescriptionRepo interface { 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{} func New() UIProductDescriptionRepo { return &ProductDescriptionRepo{} } // We assume that any user has access to all product descriptions func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error) { var ProductDescription model.ProductDescription err := db.Get(). Model(dbmodel.PsProductLang{}). Where(&dbmodel.PsProductLang{ IDProduct: int32(productID), IDShop: int32(constdata.SHOP_ID), IDLang: int32(productid_lang), }). First(&ProductDescription).Error if errors.Is(err, gorm.ErrRecordNotFound) { // handle "not found" case only ProductDescription.ExistsInDatabase = false } else if err != nil { return nil, fmt.Errorf("database error: %w", err) } else { ProductDescription.ExistsInDatabase = true } return &ProductDescription, nil } // If it doesn't exist, returns an error. func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error { record := dbmodel.PsProductLang{ IDProduct: int32(productID), IDShop: int32(constdata.SHOP_ID), IDLang: int32(productid_lang), } err := db.Get(). Model(dbmodel.PsProductLang{}). 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) } return nil } func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uint, updates map[string]string) error { if len(updates) == 0 { return nil } updatesIface := make(map[string]interface{}, len(updates)) for k, v := range updates { updatesIface[k] = v } 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) } 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 }