carts #25
99
app/delivery/web/api/restricted/carts.go
Normal file
99
app/delivery/web/api/restricted/carts.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/cartsService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CartsHandler handles endpoints that modify carts.
|
||||||
|
type CartsHandler struct {
|
||||||
|
cartsService *cartsService.CartsService
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartsHandler creates a new CartsHandler instance
|
||||||
|
func NewCartsHandler() *CartsHandler {
|
||||||
|
cartsService := cartsService.New()
|
||||||
|
return &CartsHandler{
|
||||||
|
cartsService: cartsService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CartsHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewCartsHandler()
|
||||||
|
|
||||||
|
r.Get("/add-new-cart", handler.AddNewCart)
|
||||||
|
r.Get("/change-cart-name", handler.ChangeCartName)
|
||||||
|
r.Get("/retrieve-cart", handler.RetrieveCart)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cart, err := h.cartsService.CreateNewCart(userID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
new_name := c.Query("new_name")
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.UpdateCartName(userID, uint(cart_id), new_name)
|
||||||
|
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 *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart, err := h.cartsService.RetrieveCart(userID, uint(cart_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -110,6 +110,10 @@ func (s *Server) Setup() error {
|
|||||||
meiliSearch := s.restricted.Group("/meili-search")
|
meiliSearch := s.restricted.Group("/meili-search")
|
||||||
restricted.MeiliSearchHandlerRoutes(meiliSearch)
|
restricted.MeiliSearchHandlerRoutes(meiliSearch)
|
||||||
|
|
||||||
|
// carts (restricted)
|
||||||
|
carts := s.restricted.Group("/carts")
|
||||||
|
restricted.CartsHandlerRoutes(carts)
|
||||||
|
|
||||||
// // Restricted routes example
|
// // Restricted routes example
|
||||||
// restricted := s.api.Group("/restricted")
|
// restricted := s.api.Group("/restricted")
|
||||||
// restricted.Use(middleware.AuthMiddleware())
|
// restricted.Use(middleware.AuthMiddleware())
|
||||||
|
|||||||
24
app/model/cart.go
Normal file
24
app/model/cart.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type CustomerCart struct {
|
||||||
|
CartID uint64 `gorm:"column:cart_id;primaryKey;autoIncrement" json:"cart_id"`
|
||||||
|
UserID uint64 `gorm:"column:user_id;not null;index" json:"user_id"`
|
||||||
|
Name *string `gorm:"column:name;size:255" json:"name,omitempty"`
|
||||||
|
Products []CartProduct `gorm:"foreignKey:CartID;references:CartID" json:"products,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CustomerCart) TableName() string {
|
||||||
|
return "b2b_customer_carts"
|
||||||
|
}
|
||||||
|
|
||||||
|
type CartProduct struct {
|
||||||
|
ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||||
|
CartID uint64 `gorm:"column:cart_id;not null;index" json:"cart_id"`
|
||||||
|
ProductID uint `gorm:"column:product_id;not null" json:"product_id"`
|
||||||
|
ProductAttributeID *uint64 `gorm:"column:product_attribute_id" json:"product_attribute_id,omitempty"`
|
||||||
|
Amount int `gorm:"column:amount;not null" json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CartProduct) TableName() string {
|
||||||
|
return "b2b_carts_products"
|
||||||
|
}
|
||||||
82
app/repos/cartsRepo/cartsRepo.go
Normal file
82
app/repos/cartsRepo/cartsRepo.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package cartsRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UICartsRepo interface {
|
||||||
|
CartsAmount(user_id uint) (uint, error)
|
||||||
|
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
||||||
|
UserHasCart(user_id uint, cart_id uint) (uint, error)
|
||||||
|
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
||||||
|
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CartsRepo struct{}
|
||||||
|
|
||||||
|
func New() UICartsRepo {
|
||||||
|
return &CartsRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) CartsAmount(user_id uint) (uint, error) {
|
||||||
|
var amt uint
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_customer_carts").
|
||||||
|
Select("COUNT(*) AS amt").
|
||||||
|
Where("user_id = ?", user_id).
|
||||||
|
Scan(&amt).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return amt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
||||||
|
var name string
|
||||||
|
name = constdata.DEFAULT_NEW_CART_NAME
|
||||||
|
|
||||||
|
cart := model.CustomerCart{
|
||||||
|
UserID: uint64(user_id),
|
||||||
|
Name: &name,
|
||||||
|
}
|
||||||
|
err := db.DB.Create(&cart).Error
|
||||||
|
|
||||||
|
return cart, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
|
||||||
|
var amt uint
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_customer_carts").
|
||||||
|
Select("COUNT(*) AS amt").
|
||||||
|
Where("user_id = ? AND cart_id = ?", user_id, cart_id).
|
||||||
|
Scan(&amt).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return amt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_customer_carts").
|
||||||
|
Where("user_id = ? AND cart_id = ?", user_id, cart_id).
|
||||||
|
Update("name", new_name).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
||||||
|
var cart model.CustomerCart
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Preload("b2b_carts_products").
|
||||||
|
Where("user_id = ? AND cart_id = ?", user_id, cart_id).
|
||||||
|
First(&cart).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return &cart, err
|
||||||
|
}
|
||||||
59
app/service/cartsService/cartsService.go
Normal file
59
app/service/cartsService/cartsService.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package cartsService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CartsService struct {
|
||||||
|
repo cartsRepo.UICartsRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *CartsService {
|
||||||
|
return &CartsService{
|
||||||
|
repo: cartsRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
||||||
|
var cart model.CustomerCart
|
||||||
|
|
||||||
|
customers_carts_amount, err := s.repo.CartsAmount(user_id)
|
||||||
|
if err != nil {
|
||||||
|
return cart, err
|
||||||
|
}
|
||||||
|
if customers_carts_amount >= constdata.MAX_AMOUNT_OF_CARTS_PER_USER {
|
||||||
|
return cart, responseErrors.ErrMaxAmtOfCartsReached
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new cart for customer
|
||||||
|
cart, err = s.repo.CreateNewCart(user_id)
|
||||||
|
|
||||||
|
return cart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
|
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if amt != 1 {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.UpdateCartName(user_id, cart_id, new_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
||||||
|
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if amt != 1 {
|
||||||
|
return nil, responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.RetrieveCart(user_id, cart_id)
|
||||||
|
}
|
||||||
@@ -3,3 +3,5 @@ package constdata
|
|||||||
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
||||||
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
||||||
const SHOP_ID = 1
|
const SHOP_ID = 1
|
||||||
|
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
||||||
|
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ var (
|
|||||||
|
|
||||||
// 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")
|
||||||
|
|
||||||
|
// Typed errors for carts handler
|
||||||
|
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
||||||
|
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents an error with HTTP status code
|
// Error represents an error with HTTP status code
|
||||||
@@ -141,6 +145,11 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
case errors.Is(err, ErrNoRootFound):
|
case errors.Is(err, ErrNoRootFound):
|
||||||
return i18n.T_(c, "error.no_root_found")
|
return i18n.T_(c, "error.no_root_found")
|
||||||
|
|
||||||
|
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
||||||
|
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
||||||
|
case errors.Is(err, ErrUserHasNoSuchCart):
|
||||||
|
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return i18n.T_(c, "error.err_internal_server_error")
|
return i18n.T_(c, "error.err_internal_server_error")
|
||||||
}
|
}
|
||||||
@@ -176,7 +185,9 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrBadField),
|
errors.Is(err, ErrBadField),
|
||||||
errors.Is(err, ErrInvalidXHTML),
|
errors.Is(err, ErrInvalidXHTML),
|
||||||
errors.Is(err, ErrBadPaging),
|
errors.Is(err, ErrBadPaging),
|
||||||
errors.Is(err, ErrNoRootFound):
|
errors.Is(err, ErrNoRootFound),
|
||||||
|
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||||
|
errors.Is(err, ErrUserHasNoSuchCart):
|
||||||
return fiber.StatusBadRequest
|
return fiber.StatusBadRequest
|
||||||
case errors.Is(err, ErrEmailExists):
|
case errors.Is(err, ErrEmailExists):
|
||||||
return fiber.StatusConflict
|
return fiber.StatusConflict
|
||||||
|
|||||||
@@ -86,18 +86,27 @@ CREATE INDEX IF NOT EXISTS idx_customers_deleted_at
|
|||||||
ON b2b_customers (deleted_at);
|
ON b2b_customers (deleted_at);
|
||||||
|
|
||||||
|
|
||||||
-- customer_repo_accesses
|
-- customer_carts
|
||||||
-- CREATE TABLE IF NOT EXISTS b2b_customer_repo_accesses (
|
CREATE TABLE IF NOT EXISTS b2b_customer_carts (
|
||||||
-- user_id BIGINT NOT NULL,
|
cart_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
-- repo_id BIGINT NOT NULL,
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
-- PRIMARY KEY (user_id, repo_id),
|
name VARCHAR(255) NULL,
|
||||||
-- CONSTRAINT fk_customer_repo_user
|
CONSTRAINT fk_customer_carts_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
-- FOREIGN KEY (user_id) REFERENCES b2b_customers(id)
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
-- ON DELETE CASCADE,
|
CREATE INDEX IF NOT EXISTS idx_customer_carts_user_id ON b2b_customer_carts (user_id);
|
||||||
-- CONSTRAINT fk_customer_repo_repo
|
|
||||||
-- FOREIGN KEY (repo_id) REFERENCES repository(id)
|
|
||||||
-- ON DELETE CASCADE
|
-- carts_products
|
||||||
-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
CREATE TABLE IF NOT EXISTS b2b_carts_products (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
cart_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
product_id INT UNSIGNED NOT NULL,
|
||||||
|
product_attribute_id BIGINT NULL,
|
||||||
|
amount INT NOT NULL,
|
||||||
|
CONSTRAINT fk_carts_products_customer_carts FOREIGN KEY (cart_id) REFERENCES b2b_customer_carts (cart_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_carts_products_product FOREIGN KEY (product_id) REFERENCES ps_product (id_product) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_carts_products_cart_id ON b2b_carts_products (cart_id);
|
||||||
|
|
||||||
|
|
||||||
-- refresh_tokens
|
-- refresh_tokens
|
||||||
|
|||||||
Reference in New Issue
Block a user