From 0a5ce5d9c2e32a1c3f0962d7428d9dee01c2d4a4 Mon Sep 17 00:00:00 2001 From: Daniel Goc Date: Fri, 10 Apr 2026 09:13:13 +0200 Subject: [PATCH] ... --- app/delivery/web/api/restricted/product.go | 50 +++++++++++++++++++ app/model/product.go | 9 ++++ app/repos/productsRepo/productsRepo.go | 16 ++++++ app/service/productService/productService.go | 8 +++ app/utils/responseErrors/responseErrors.go | 16 +++++- bruno/api_v1/product/Add To Favorites.yml | 15 ++++++ .../api_v1/product/Remove Form Favorites.yml | 15 ++++++ go.mod | 6 +-- go.sum | 6 --- 9 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 bruno/api_v1/product/Add To Favorites.yml create mode 100644 bruno/api_v1/product/Remove Form Favorites.yml diff --git a/app/delivery/web/api/restricted/product.go b/app/delivery/web/api/restricted/product.go index 5ab5b36..096d5ec 100644 --- a/app/delivery/web/api/restricted/product.go +++ b/app/delivery/web/api/restricted/product.go @@ -34,6 +34,8 @@ func ProductsHandlerRoutes(r fiber.Router) fiber.Router { r.Get("/:id/:country_id/:quantity", handler.GetProductJson) r.Get("/list", handler.ListProducts) + r.Post("/favorite/:product_id", handler.AddToFavorites) + r.Delete("/favorite/:product_id", handler.RemoveFromFavorites) return r } @@ -114,3 +116,51 @@ var columnMappingListProducts map[string]string = map[string]string{ "quantity": "sa.quantity", "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))) +} diff --git a/app/model/product.go b/app/model/product.go index 06b599e..e862259 100644 --- a/app/model/product.go +++ b/app/model/product.go @@ -86,3 +86,12 @@ type ProductFilters struct { } 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" +} diff --git a/app/repos/productsRepo/productsRepo.go b/app/repos/productsRepo/productsRepo.go index 9e0ab1c..7699c10 100644 --- a/app/repos/productsRepo/productsRepo.go +++ b/app/repos/productsRepo/productsRepo.go @@ -17,6 +17,8 @@ import ( 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) 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{} @@ -125,3 +127,17 @@ func (repo *ProductsRepo) Find(id_lang, userID uint, p find.Paging, filt *filter Count: uint(total), }, 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 +} diff --git a/app/service/productService/productService.go b/app/service/productService/productService.go index f5d7ca1..03a7132 100644 --- a/app/service/productService/productService.go +++ b/app/service/productService/productService.go @@ -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) { 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) +} diff --git a/app/utils/responseErrors/responseErrors.go b/app/utils/responseErrors/responseErrors.go index 28802e1..6b3c548 100644 --- a/app/utils/responseErrors/responseErrors.go +++ b/app/utils/responseErrors/responseErrors.go @@ -49,8 +49,11 @@ var ( ErrAIResponseFail = errors.New("AI responded with failure") ErrAIBadOutput = errors.New("AI response does not obey the format") - // Typed errors for product list handler - ErrBadPaging = errors.New("bad or missing paging attribute value in header") + // Typed errors for product handler + 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 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): 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): 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, ErrInvalidXHTML), errors.Is(err, ErrBadPaging), + errors.Is(err, ErrProductNotFound), + errors.Is(err, ErrAlreadyInFavorites), + errors.Is(err, ErrNotInFavorites), errors.Is(err, ErrNoRootFound), errors.Is(err, ErrCircularDependency), errors.Is(err, ErrStartCategoryNotFound), diff --git a/bruno/api_v1/product/Add To Favorites.yml b/bruno/api_v1/product/Add To Favorites.yml new file mode 100644 index 0000000..71b3d9a --- /dev/null +++ b/bruno/api_v1/product/Add To Favorites.yml @@ -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 diff --git a/bruno/api_v1/product/Remove Form Favorites.yml b/bruno/api_v1/product/Remove Form Favorites.yml new file mode 100644 index 0000000..a76feb6 --- /dev/null +++ b/bruno/api_v1/product/Remove Form Favorites.yml @@ -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 diff --git a/go.mod b/go.mod index 1c184da..6141322 100644 --- a/go.mod +++ b/go.mod @@ -36,8 +36,6 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.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/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect @@ -100,10 +98,10 @@ require ( github.com/valyala/fasthttp v1.69.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // 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/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 gorm.io/driver/mysql v1.6.0 ) diff --git a/go.sum b/go.sum index 81fe849..d208fb1 100644 --- a/go.sum +++ b/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-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/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/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU= 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-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-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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 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/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=