favorites #57
@@ -34,6 +34,8 @@ func ProductsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
|
|
||||||
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
|
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
|
||||||
r.Get("/list", handler.ListProducts)
|
r.Get("/list", handler.ListProducts)
|
||||||
|
r.Post("/favorite/:product_id", handler.AddToFavorites)
|
||||||
|
r.Delete("/favorite/:product_id", handler.RemoveFromFavorites)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -114,3 +116,51 @@ var columnMappingListProducts map[string]string = map[string]string{
|
|||||||
"quantity": "sa.quantity",
|
"quantity": "sa.quantity",
|
||||||
"is_favorite": "ps.is_favorite",
|
"is_favorite": "ps.is_favorite",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
||||||
|
productIDStr := c.Params("product_id")
|
||||||
|
|
||||||
|
productID, err := strconv.Atoi(productIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.productService.AddToFavorites(userID, uint(productID))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) RemoveFromFavorites(c fiber.Ctx) error {
|
||||||
|
productIDStr := c.Params("product_id")
|
||||||
|
|
||||||
|
productID, err := strconv.Atoi(productIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.productService.RemoveFromFavorites(userID, uint(productID))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -86,3 +86,12 @@ type ProductFilters struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FeatVal = map[uint][]uint
|
type FeatVal = map[uint][]uint
|
||||||
|
|
||||||
|
type B2bFavorite struct {
|
||||||
|
UserID uint `gorm:"column:user_id;not null;primaryKey" json:"user_id"`
|
||||||
|
ProductID uint `gorm:"column:product_id;not null;primaryKey" json:"product_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*B2bFavorite) TableName() string {
|
||||||
|
return "b2b_favorites"
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
type UIProductsRepo interface {
|
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)
|
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, userID 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)
|
||||||
|
AddToFavorites(userID uint, productID uint) error
|
||||||
|
RemoveFromFavorites(userID uint, productID uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductsRepo struct{}
|
type ProductsRepo struct{}
|
||||||
@@ -125,3 +127,17 @@ func (repo *ProductsRepo) Find(id_lang, userID uint, p find.Paging, filt *filter
|
|||||||
Count: uint(total),
|
Count: uint(total),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) AddToFavorites(userID uint, productID uint) error {
|
||||||
|
fav := model.B2bFavorite{
|
||||||
|
UserID: userID,
|
||||||
|
ProductID: productID,
|
||||||
|
}
|
||||||
|
return db.Get().Create(&fav).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) RemoveFromFavorites(userID uint, productID uint) error {
|
||||||
|
return db.Get().
|
||||||
|
Where("user_id = ? AND product_id = ?", userID, productID).
|
||||||
|
Delete(&model.B2bFavorite{}).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,3 +32,11 @@ func (s *ProductService) GetJSON(p_id_product, p_id_lang, p_id_customer, b2b_id_
|
|||||||
func (s *ProductService) Find(id_lang, userID uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
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)
|
return s.productsRepo.Find(id_lang, userID, p, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) AddToFavorites(userID uint, productID uint) error {
|
||||||
|
return s.productsRepo.AddToFavorites(userID, productID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) RemoveFromFavorites(userID uint, productID uint) error {
|
||||||
|
return s.productsRepo.RemoveFromFavorites(userID, productID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,8 +49,11 @@ var (
|
|||||||
ErrAIResponseFail = errors.New("AI responded with failure")
|
ErrAIResponseFail = errors.New("AI responded with failure")
|
||||||
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
||||||
|
|
||||||
// Typed errors for product list handler
|
// Typed errors for product handler
|
||||||
ErrBadPaging = errors.New("bad or missing paging attribute value in header")
|
ErrBadPaging = errors.New("bad or missing paging attribute value in header")
|
||||||
|
ErrProductNotFound = errors.New("product with provided id does not exist")
|
||||||
|
ErrAlreadyInFavorites = errors.New("the product already is in your favorites")
|
||||||
|
ErrNotInFavorites = errors.New("the product already is not in your favorites")
|
||||||
|
|
||||||
// Typed errors for menu handler
|
// Typed errors for menu handler
|
||||||
ErrNoRootFound = errors.New("no root found in categories table")
|
ErrNoRootFound = errors.New("no root found in categories table")
|
||||||
@@ -170,6 +173,12 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
|
|
||||||
case errors.Is(err, ErrBadPaging):
|
case errors.Is(err, ErrBadPaging):
|
||||||
return i18n.T_(c, "error.err_bad_paging")
|
return i18n.T_(c, "error.err_bad_paging")
|
||||||
|
case errors.Is(err, ErrProductNotFound):
|
||||||
|
return i18n.T_(c, "error.err_product_not_found")
|
||||||
|
case errors.Is(err, ErrAlreadyInFavorites):
|
||||||
|
return i18n.T_(c, "error.err_already_in_favorites")
|
||||||
|
case errors.Is(err, ErrNotInFavorites):
|
||||||
|
return i18n.T_(c, "error.err_already_not_in_favorites")
|
||||||
|
|
||||||
case errors.Is(err, ErrNoRootFound):
|
case errors.Is(err, ErrNoRootFound):
|
||||||
return i18n.T_(c, "error.err_no_root_found")
|
return i18n.T_(c, "error.err_no_root_found")
|
||||||
@@ -249,6 +258,9 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrInvalidURLSlug),
|
errors.Is(err, ErrInvalidURLSlug),
|
||||||
errors.Is(err, ErrInvalidXHTML),
|
errors.Is(err, ErrInvalidXHTML),
|
||||||
errors.Is(err, ErrBadPaging),
|
errors.Is(err, ErrBadPaging),
|
||||||
|
errors.Is(err, ErrProductNotFound),
|
||||||
|
errors.Is(err, ErrAlreadyInFavorites),
|
||||||
|
errors.Is(err, ErrNotInFavorites),
|
||||||
errors.Is(err, ErrNoRootFound),
|
errors.Is(err, ErrNoRootFound),
|
||||||
errors.Is(err, ErrCircularDependency),
|
errors.Is(err, ErrCircularDependency),
|
||||||
errors.Is(err, ErrStartCategoryNotFound),
|
errors.Is(err, ErrStartCategoryNotFound),
|
||||||
|
|||||||
15
bruno/api_v1/product/Add To Favorites.yml
Normal file
15
bruno/api_v1/product/Add To Favorites.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: Add To Favorites
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{bas_url}}/restricted/product/favorite/51"
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
15
bruno/api_v1/product/Remove Form Favorites.yml
Normal file
15
bruno/api_v1/product/Remove Form Favorites.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: Remove Form Favorites
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: DELETE
|
||||||
|
url: "{{bas_url}}/restricted/product/favorite/1"
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
6
go.mod
6
go.mod
@@ -36,8 +36,6 @@ require (
|
|||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
@@ -100,10 +98,10 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xyproto/randomstring v1.2.0 // indirect
|
github.com/xyproto/randomstring v1.2.0 // indirect
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.52.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gorm.io/driver/mysql v1.6.0
|
gorm.io/driver/mysql v1.6.0
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -72,8 +72,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw=
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
|
||||||
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
||||||
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
|
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
|
||||||
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
||||||
@@ -136,8 +134,6 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
@@ -158,8 +154,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|||||||
Reference in New Issue
Block a user