added new endpoint to save product descriptions
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
***/node_modules/***
|
***/node_modules/***
|
||||||
tmp
|
tmp/
|
||||||
assets/public/dist
|
assets/public/dist
|
||||||
bin/
|
bin/
|
||||||
i18n/*.json
|
i18n/*.json
|
||||||
|
|||||||
@@ -5,6 +5,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/service/productDescriptionService"
|
"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"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
@@ -30,7 +31,7 @@ func ProductDescriptionHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewProductDescriptionHandler()
|
handler := NewProductDescriptionHandler()
|
||||||
|
|
||||||
r.Get("/get-product-description", handler.GetProductDescription)
|
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-quarters", handler.GetQuarters)
|
||||||
// r.Get("/get-issues", handler.GetIssues)
|
// r.Get("/get-issues", handler.GetIssues)
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ func (h *ProductDescriptionHandler) GetProductDescription(c fiber.Ctx) error {
|
|||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := c.Locals("userID").(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
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)
|
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
|
package model
|
||||||
|
|
||||||
// User represents a user in the system
|
// ProductDescription contains all the information visible on webpage, in given language.
|
||||||
type ProductDescription struct {
|
type ProductDescription struct {
|
||||||
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id"`
|
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id" form:"product_id"`
|
||||||
ShopID uint `gorm:"column:id_shop;primaryKey" json:"shop_id"`
|
ShopID uint `gorm:"column:id_shop;primaryKey" json:"shop_id" form:"shop_id"`
|
||||||
LangID uint `gorm:"column:id_lang;primaryKey" json:"lang_id"`
|
LangID uint `gorm:"column:id_lang;primaryKey" json:"lang_id" form:"lang_id"`
|
||||||
Description string `gorm:"column:description;type:text" json:"description"`
|
Description string `gorm:"column:description;type:text" json:"description" form:"description"`
|
||||||
DescriptionShort string `gorm:"column:description_short;type:text" json:"description_short"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
Name string `gorm:"column:name;type:varchar(128)" json:"name" form:"name"`
|
||||||
AvailableNow string `gorm:"column:available_now;type:varchar(255)" json:"available_now"`
|
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"`
|
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"`
|
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"`
|
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"`
|
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package productDescriptionService
|
package productDescriptionService
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
"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/utils/responseErrors"
|
||||||
"gorm.io/gorm"
|
"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
|
// 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) {
|
func (s *ProductDescriptionService) GetProductDescription(userID uint, productID uint, productShopID uint, productLangID uint) (*model.ProductDescription, error) {
|
||||||
var ProductDescription model.ProductDescription
|
var ProductDescription model.ProductDescription
|
||||||
|
|
||||||
fmt.Println(userID, productID, productShopID, productLangID)
|
|
||||||
|
|
||||||
err := s.db.
|
err := s.db.
|
||||||
Table("ps_product_lang").
|
Table("ps_product_lang").
|
||||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
|
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
|
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) {
|
// func (s *ProductDescriptionService) GetRepositoriesForUser(userID uint) ([]uint, error) {
|
||||||
// var repoIDs []uint
|
// var repoIDs []uint
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ var (
|
|||||||
|
|
||||||
// Typed errors for product description handler
|
// Typed errors for product description handler
|
||||||
ErrBadAttribute = errors.New("bad attribute")
|
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
|
// 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):
|
case errors.Is(err, ErrBadAttribute):
|
||||||
return i18n.T_(c, "error.err_bad_attribute")
|
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:
|
default:
|
||||||
return i18n.T_(c, "error.err_internal_server_error")
|
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, ErrInvalidVerificationToken),
|
||||||
errors.Is(err, ErrVerificationTokenExpired),
|
errors.Is(err, ErrVerificationTokenExpired),
|
||||||
errors.Is(err, ErrInvalidPassword),
|
errors.Is(err, ErrInvalidPassword),
|
||||||
errors.Is(err, ErrBadAttribute):
|
errors.Is(err, ErrBadAttribute),
|
||||||
|
errors.Is(err, ErrBadField),
|
||||||
|
errors.Is(err, ErrInvalidHTML):
|
||||||
return fiber.StatusBadRequest
|
return fiber.StatusBadRequest
|
||||||
case errors.Is(err, ErrEmailExists):
|
case errors.Is(err, ErrEmailExists):
|
||||||
return fiber.StatusConflict
|
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