Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into test
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
***/node_modules/***
|
||||
tmp
|
||||
tmp/
|
||||
assets/public/dist
|
||||
bin/
|
||||
i18n/*.json
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/service/productDescriptionService"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
@@ -30,7 +31,7 @@ func ProductDescriptionHandlerRoutes(r fiber.Router) fiber.Router {
|
||||
handler := NewProductDescriptionHandler()
|
||||
|
||||
r.Get("/get-product-description", handler.GetProductDescription)
|
||||
// r.Get("/get-years", handler.GetYears)
|
||||
r.Post("/save-product-description", handler.SaveProductDescription)
|
||||
// r.Get("/get-quarters", handler.GetQuarters)
|
||||
// r.Get("/get-issues", handler.GetIssues)
|
||||
|
||||
@@ -42,7 +43,7 @@ func (h *ProductDescriptionHandler) GetProductDescription(c fiber.Ctx) error {
|
||||
userID, ok := c.Locals("userID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody), // possibly could return a dfifferent error
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody), // possibly could return a different error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,3 +80,55 @@ func (h *ProductDescriptionHandler) GetProductDescription(c fiber.Ctx) error {
|
||||
|
||||
return c.JSON(response)
|
||||
}
|
||||
|
||||
// SaveProductDescription saves the description for a given product ID, in given shop and language
|
||||
func (h *ProductDescriptionHandler) SaveProductDescription(c fiber.Ctx) error {
|
||||
userID, ok := c.Locals("userID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody), // possibly could return a different error
|
||||
})
|
||||
}
|
||||
|
||||
productID_attribute := c.Query("productID")
|
||||
productID, err := strconv.Atoi(productID_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||
})
|
||||
}
|
||||
|
||||
productShopID_attribute := c.Query("productShopID")
|
||||
productShopID, err := strconv.Atoi(productShopID_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||
})
|
||||
}
|
||||
|
||||
productLangID_attribute := c.Query("productLangID")
|
||||
productLangID, err := strconv.Atoi(productLangID_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||
})
|
||||
}
|
||||
|
||||
updates := make(map[string]string)
|
||||
if err := c.Bind().Body(&updates); err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
|
||||
})
|
||||
}
|
||||
|
||||
err = h.productDescriptionService.SaveProductDescription(userID, uint(productID), uint(productShopID), uint(productLangID), updates)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
||||
"error": responseErrors.GetErrorCode(c, err),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"message": i18n.T_(c, "product_description.successfully_updated_fields"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package model
|
||||
|
||||
// User represents a user in the system
|
||||
// ProductDescription contains all the information visible on webpage, in given language.
|
||||
type ProductDescription struct {
|
||||
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id"`
|
||||
ShopID uint `gorm:"column:id_shop;primaryKey" json:"shop_id"`
|
||||
LangID uint `gorm:"column:id_lang;primaryKey" json:"lang_id"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
DescriptionShort string `gorm:"column:description_short;type:text" json:"description_short"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite;type:varchar(128)" json:"link_rewrite"`
|
||||
MetaDescription string `gorm:"column:meta_description;type:varchar(512)" json:"meta_description"`
|
||||
MetaKeywords string `gorm:"column:meta_keywords;type:varchar(255)" json:"meta_keywords"`
|
||||
MetaTitle string `gorm:"column:meta_title;type:varchar(128)" json:"meta_title"`
|
||||
Name string `gorm:"column:name;type:varchar(128)" json:"name"`
|
||||
AvailableNow string `gorm:"column:available_now;type:varchar(255)" json:"available_now"`
|
||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later"`
|
||||
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock"`
|
||||
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock"`
|
||||
Usage string `gorm:"column:usage;type:text" json:"usage"`
|
||||
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id" form:"product_id"`
|
||||
ShopID uint `gorm:"column:id_shop;primaryKey" json:"shop_id" form:"shop_id"`
|
||||
LangID uint `gorm:"column:id_lang;primaryKey" json:"lang_id" form:"lang_id"`
|
||||
Description string `gorm:"column:description;type:text" json:"description" form:"description"`
|
||||
DescriptionShort string `gorm:"column:description_short;type:text" json:"description_short" form:"description_short"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite;type:varchar(128)" json:"link_rewrite" form:"link_rewrite"`
|
||||
MetaDescription string `gorm:"column:meta_description;type:varchar(512)" json:"meta_description" form:"meta_description"`
|
||||
MetaKeywords string `gorm:"column:meta_keywords;type:varchar(255)" json:"meta_keywords" form:"meta_keywords"`
|
||||
MetaTitle string `gorm:"column:meta_title;type:varchar(128)" json:"meta_title" form:"meta_title"`
|
||||
Name string `gorm:"column:name;type:varchar(128)" json:"name" form:"name"`
|
||||
AvailableNow string `gorm:"column:available_now;type:varchar(255)" json:"available_now" form:"available_now"`
|
||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
||||
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
|
||||
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
|
||||
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package productDescriptionService
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"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/responseErrors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -18,12 +23,29 @@ func New() *ProductDescriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
func isValidXHTML(s string) bool {
|
||||
decoder := xml.NewDecoder(strings.NewReader(s))
|
||||
hasStartTag := false
|
||||
|
||||
for {
|
||||
tok, err := decoder.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return hasStartTag
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := tok.(xml.StartElement); ok {
|
||||
hasStartTag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We assume that any user has access to all product descriptions
|
||||
func (s *ProductDescriptionService) GetProductDescription(userID uint, productID uint, productShopID uint, productLangID uint) (*model.ProductDescription, error) {
|
||||
var ProductDescription model.ProductDescription
|
||||
|
||||
fmt.Println(userID, productID, productShopID, productLangID)
|
||||
|
||||
err := s.db.
|
||||
Table("ps_product_lang").
|
||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
|
||||
@@ -35,6 +57,59 @@ func (s *ProductDescriptionService) GetProductDescription(userID uint, productID
|
||||
return &ProductDescription, nil
|
||||
}
|
||||
|
||||
// Updates relevant fields with the "updates" map
|
||||
func (s *ProductDescriptionService) SaveProductDescription(userID uint, productID uint, productShopID uint, productLangID uint, updates map[string]string) error {
|
||||
// only some fields can be affected
|
||||
allowedFields := []string{"description", "description_short", "meta_description", "meta_title", "name", "available_now", "available_later", "usage"}
|
||||
for key := range updates {
|
||||
if !slices.Contains(allowedFields, key) {
|
||||
return responseErrors.ErrBadField
|
||||
}
|
||||
}
|
||||
|
||||
// check that fields description, description_short and usage, if they exist, have a valid html format
|
||||
mustBeHTML := []string{"description", "description_short", "usage"}
|
||||
for i := 0; i < len(mustBeHTML); i++ {
|
||||
if text, exists := updates[mustBeHTML[i]]; exists {
|
||||
if !isValidXHTML(text) {
|
||||
return responseErrors.ErrInvalidHTML
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record := model.ProductDescription{
|
||||
ProductID: productID,
|
||||
ShopID: productShopID,
|
||||
LangID: productLangID,
|
||||
}
|
||||
|
||||
err := s.db.
|
||||
Table("ps_product_lang").
|
||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
|
||||
FirstOrCreate(&record).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
updatesIface := make(map[string]interface{}, len(updates))
|
||||
for k, v := range updates {
|
||||
updatesIface[k] = v
|
||||
}
|
||||
|
||||
err = s.db.
|
||||
Table("ps_product_lang").
|
||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
|
||||
Updates(updatesIface).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func (s *ProductDescriptionService) GetRepositoriesForUser(userID uint) ([]uint, error) {
|
||||
// var repoIDs []uint
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ var (
|
||||
|
||||
// Typed errors for product description handler
|
||||
ErrBadAttribute = errors.New("bad attribute")
|
||||
ErrBadField = errors.New("this field can not be updated")
|
||||
ErrInvalidHTML = errors.New("text is not in html format")
|
||||
)
|
||||
|
||||
// Error represents an error with HTTP status code
|
||||
@@ -110,6 +112,10 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
||||
|
||||
case errors.Is(err, ErrBadAttribute):
|
||||
return i18n.T_(c, "error.err_bad_attribute")
|
||||
case errors.Is(err, ErrBadField):
|
||||
return i18n.T_(c, "error.err_bad_field")
|
||||
case errors.Is(err, ErrInvalidHTML):
|
||||
return i18n.T_(c, "error.err_invalid_html")
|
||||
|
||||
default:
|
||||
return i18n.T_(c, "error.err_internal_server_error")
|
||||
@@ -140,7 +146,9 @@ func GetErrorStatus(err error) int {
|
||||
errors.Is(err, ErrInvalidVerificationToken),
|
||||
errors.Is(err, ErrVerificationTokenExpired),
|
||||
errors.Is(err, ErrInvalidPassword),
|
||||
errors.Is(err, ErrBadAttribute):
|
||||
errors.Is(err, ErrBadAttribute),
|
||||
errors.Is(err, ErrBadField),
|
||||
errors.Is(err, ErrInvalidHTML):
|
||||
return fiber.StatusBadRequest
|
||||
case errors.Is(err, ErrEmailExists):
|
||||
return fiber.StatusConflict
|
||||
|
||||
@@ -1 +1 @@
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
Reference in New Issue
Block a user