fix: fixing cart feature behavior and auto commit optimizing
This commit is contained in:
parent
226d188ece
commit
b7a1d10898
14
cmd/main.go
14
cmd/main.go
|
|
@ -1,15 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"rijig/config"
|
||||
"rijig/internal/worker"
|
||||
"rijig/router"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.SetupConfig()
|
||||
logFile, _ := os.OpenFile("logs/cart_commit.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
log.SetOutput(logFile)
|
||||
|
||||
go func() {
|
||||
c := cron.New()
|
||||
c.AddFunc("@every 1m", func() {
|
||||
_ = worker.CommitExpiredCartsToDB()
|
||||
})
|
||||
c.Start()
|
||||
}()
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(cors.New(cors.Config{
|
||||
|
|
|
|||
|
|
@ -5,65 +5,53 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ValidationErrors struct {
|
||||
Errors map[string][]string
|
||||
type RequestCartItemDTO struct {
|
||||
TrashID string `json:"trash_id"`
|
||||
Amount float32 `json:"amount"`
|
||||
}
|
||||
|
||||
func (v ValidationErrors) Error() string {
|
||||
return "validation error"
|
||||
type RequestCartDTO struct {
|
||||
CartItems []RequestCartItemDTO `json:"cart_items"`
|
||||
}
|
||||
|
||||
type CartResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"userid"`
|
||||
CartItems []CartItemResponse `json:"cartitems"`
|
||||
TotalAmount float32 `json:"totalamount"`
|
||||
EstimatedTotalPrice float32 `json:"estimated_totalprice"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CartItemResponse struct {
|
||||
ItemId string `json:"item_id"`
|
||||
TrashId string `json:"trashid"`
|
||||
TrashIcon string `json:"trashicon"`
|
||||
TrashName string `json:"trashname"`
|
||||
type ResponseCartItemDTO struct {
|
||||
ID string `json:"id"`
|
||||
TrashID string `json:"trash_id"`
|
||||
TrashName string `json:"trash_name"`
|
||||
TrashIcon string `json:"trash_icon"`
|
||||
Amount float32 `json:"amount"`
|
||||
EstimatedSubTotalPrice float32 `json:"estimated_subtotalprice"`
|
||||
SubTotalEstimatedPrice float32 `json:"subtotal_estimated_price"`
|
||||
}
|
||||
|
||||
type RequestCartItems struct {
|
||||
TrashCategoryID string `json:"trashid"`
|
||||
Amount float32 `json:"amount"`
|
||||
type ResponseCartDTO struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
TotalAmount float32 `json:"total_amount"`
|
||||
EstimatedTotalPrice float32 `json:"estimated_total_price"`
|
||||
CartItems []ResponseCartItemDTO `json:"cart_items"`
|
||||
}
|
||||
|
||||
func (r *RequestCartItems) ValidateRequestCartItem() (map[string][]string, bool) {
|
||||
// ==== VALIDATION ====
|
||||
|
||||
func (r *RequestCartDTO) ValidateRequestCartDTO() (map[string][]string, bool) {
|
||||
errors := make(map[string][]string)
|
||||
|
||||
if strings.TrimSpace(r.TrashCategoryID) == "" {
|
||||
errors["trashid"] = append(errors["trashid"], "trashid is required")
|
||||
if len(r.CartItems) == 0 {
|
||||
errors["cart_items"] = append(errors["cart_items"], "minimal satu item harus dimasukkan")
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors, false
|
||||
}
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
||||
type BulkRequestCartItems struct {
|
||||
Items []RequestCartItems `json:"items"`
|
||||
}
|
||||
|
||||
func (b *BulkRequestCartItems) Validate() (map[string][]string, bool) {
|
||||
errors := make(map[string][]string)
|
||||
for i, item := range b.Items {
|
||||
if strings.TrimSpace(item.TrashCategoryID) == "" {
|
||||
errors[fmt.Sprintf("items[%d].trashid", i)] = append(errors[fmt.Sprintf("items[%d].trashid", i)], "trashid is required")
|
||||
for i, item := range r.CartItems {
|
||||
if strings.TrimSpace(item.TrashID) == "" {
|
||||
errors[fmt.Sprintf("cart_items[%d].trash_id", i)] = append(errors[fmt.Sprintf("cart_items[%d].trash_id", i)], "trash_id tidak boleh kosong")
|
||||
}
|
||||
if item.Amount <= 0 {
|
||||
errors[fmt.Sprintf("cart_items[%d].amount", i)] = append(errors[fmt.Sprintf("cart_items[%d].amount", i)], "amount harus lebih dari 0")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors, false
|
||||
}
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -37,6 +37,7 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robfig/cron v1.2.0
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -64,6 +64,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/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/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"rijig/dto"
|
||||
"rijig/internal/services"
|
||||
"rijig/utils"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CartHandler interface {
|
||||
GetCart(c *fiber.Ctx) error
|
||||
AddOrUpdateCartItem(c *fiber.Ctx) error
|
||||
AddMultipleCartItems(c *fiber.Ctx) error
|
||||
DeleteCartItem(c *fiber.Ctx) error
|
||||
ClearCart(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
type cartHandler struct {
|
||||
service services.CartService
|
||||
}
|
||||
|
||||
func NewCartHandler(service services.CartService) CartHandler {
|
||||
return &cartHandler{service: service}
|
||||
}
|
||||
|
||||
// GET /cart
|
||||
func (h *cartHandler) GetCart(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
cart, err := h.service.GetCart(context.Background(), userID)
|
||||
if err != nil {
|
||||
return utils.ErrorResponse(c, "Cart belum dibuat atau sudah kadaluarsa")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, cart, "Data cart berhasil diambil")
|
||||
}
|
||||
|
||||
// POST /cart/item
|
||||
func (h *cartHandler) AddOrUpdateCartItem(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
var item dto.RequestCartItemDTO
|
||||
if err := c.BodyParser(&item); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"format tidak valid"}})
|
||||
}
|
||||
|
||||
if item.TrashID == "" || item.Amount <= 0 {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{
|
||||
"trash_id": {"harus diisi"},
|
||||
"amount": {"harus lebih dari 0"},
|
||||
})
|
||||
}
|
||||
|
||||
if err := h.service.AddOrUpdateItem(context.Background(), userID, item); err != nil {
|
||||
return utils.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Item berhasil ditambahkan/diupdate di cart")
|
||||
}
|
||||
|
||||
// POST /cart/items
|
||||
func (h *cartHandler) AddMultipleCartItems(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
var payload dto.RequestCartDTO
|
||||
if err := c.BodyParser(&payload); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{
|
||||
"body": {"format tidak valid"},
|
||||
})
|
||||
}
|
||||
|
||||
if errs, ok := payload.ValidateRequestCartDTO(); !ok {
|
||||
return utils.ValidationErrorResponse(c, errs)
|
||||
}
|
||||
|
||||
for _, item := range payload.CartItems {
|
||||
if err := h.service.AddOrUpdateItem(context.Background(), userID, item); err != nil {
|
||||
return utils.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Semua item berhasil ditambahkan/diupdate ke cart")
|
||||
}
|
||||
|
||||
|
||||
// DELETE /cart/item/:trashID
|
||||
func (h *cartHandler) DeleteCartItem(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
trashID := c.Params("trashID")
|
||||
|
||||
if trashID == "" {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"trash_id": {"tidak boleh kosong"}})
|
||||
}
|
||||
|
||||
err := h.service.DeleteItem(context.Background(), userID, trashID)
|
||||
if err != nil {
|
||||
return utils.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Item berhasil dihapus dari cart")
|
||||
}
|
||||
|
||||
// DELETE /cart
|
||||
func (h *cartHandler) ClearCart(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
if err := h.service.ClearCart(context.Background(), userID); err != nil {
|
||||
return utils.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Seluruh cart berhasil dihapus")
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"rijig/dto"
|
||||
"rijig/internal/services"
|
||||
"rijig/utils"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CartHandler struct {
|
||||
Service *services.CartService
|
||||
}
|
||||
|
||||
func NewCartHandler(service *services.CartService) *CartHandler {
|
||||
return &CartHandler{Service: service}
|
||||
}
|
||||
|
||||
func (h *CartHandler) AddOrUpdateCartItem(c *fiber.Ctx) error {
|
||||
var body dto.BulkRequestCartItems
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{
|
||||
"body": {"Invalid JSON body"},
|
||||
})
|
||||
}
|
||||
|
||||
if errors, ok := body.Validate(); !ok {
|
||||
return utils.ValidationErrorResponse(c, errors)
|
||||
}
|
||||
|
||||
userID := c.Locals("userID").(string)
|
||||
for _, item := range body.Items {
|
||||
if err := services.AddOrUpdateCartItem(userID, item); err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Failed to update one or more items")
|
||||
}
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Cart updated successfully")
|
||||
}
|
||||
|
||||
func (h *CartHandler) DeleteCartItem(c *fiber.Ctx) error {
|
||||
trashID := c.Params("trashid")
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
err := services.DeleteCartItem(userID, trashID)
|
||||
if err != nil {
|
||||
if err.Error() == "no cart found" || err.Error() == "trashid not found" {
|
||||
return utils.GenericResponse(c, fiber.StatusNotFound, "Trash item not found in cart")
|
||||
}
|
||||
return utils.InternalServerErrorResponse(c, "Failed to delete item")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Item deleted")
|
||||
}
|
||||
|
||||
func (h *CartHandler) ClearCart(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
if err := services.ClearCart(userID); err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Failed to clear cart")
|
||||
}
|
||||
return utils.SuccessResponse(c, nil, "Cart cleared")
|
||||
}
|
||||
|
||||
func (h *CartHandler) GetCart(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
cart, err := h.Service.GetCart(userID)
|
||||
if err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Failed to fetch cart")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, cart, "User cart data successfully fetched")
|
||||
}
|
||||
|
||||
func (h *CartHandler) CommitCart(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
err := h.Service.CommitCartToDatabase(userID)
|
||||
if err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Failed to commit cart to database")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Cart committed to database")
|
||||
}
|
||||
|
||||
// PUT /cart/refresh → refresh TTL Redis
|
||||
func (h *CartHandler) RefreshCartTTL(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userID").(string)
|
||||
|
||||
err := services.RefreshCartTTL(userID)
|
||||
if err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Failed to refresh cart TTL")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, nil, "Cart TTL refreshed")
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"rijig/config"
|
||||
"rijig/dto"
|
||||
)
|
||||
|
||||
var cartTTL = 30 * time.Minute
|
||||
|
||||
func getCartKey(userID string) string {
|
||||
return fmt.Sprintf("cart:user:%s", userID)
|
||||
}
|
||||
|
||||
func SetCartToRedis(ctx context.Context, userID string, cart dto.RequestCartDTO) error {
|
||||
key := getCartKey(userID)
|
||||
|
||||
data, err := json.Marshal(cart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal cart: %w", err)
|
||||
}
|
||||
|
||||
err = config.RedisClient.Set(ctx, key, data, cartTTL).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save cart to redis: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCartFromRedis(ctx context.Context, userID string) (*dto.RequestCartDTO, error) {
|
||||
key := getCartKey(userID)
|
||||
val, err := config.RedisClient.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cart dto.RequestCartDTO
|
||||
if err := json.Unmarshal([]byte(val), &cart); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal cart data: %w", err)
|
||||
}
|
||||
|
||||
return &cart, nil
|
||||
}
|
||||
|
||||
func DeleteCartFromRedis(ctx context.Context, userID string) error {
|
||||
key := getCartKey(userID)
|
||||
return config.RedisClient.Del(ctx, key).Err()
|
||||
}
|
||||
|
||||
func GetCartTTL(ctx context.Context, userID string) (time.Duration, error) {
|
||||
key := getCartKey(userID)
|
||||
return config.RedisClient.TTL(ctx, key).Result()
|
||||
}
|
||||
|
||||
func UpdateOrAddCartItemToRedis(ctx context.Context, userID string, item dto.RequestCartItemDTO) error {
|
||||
cart, err := GetCartFromRedis(ctx, userID)
|
||||
if err != nil {
|
||||
|
||||
cart = &dto.RequestCartDTO{
|
||||
CartItems: []dto.RequestCartItemDTO{item},
|
||||
}
|
||||
return SetCartToRedis(ctx, userID, *cart)
|
||||
}
|
||||
|
||||
updated := false
|
||||
for i, ci := range cart.CartItems {
|
||||
if ci.TrashID == item.TrashID {
|
||||
cart.CartItems[i].Amount = item.Amount
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
cart.CartItems = append(cart.CartItems, item)
|
||||
}
|
||||
|
||||
return SetCartToRedis(ctx, userID, *cart)
|
||||
}
|
||||
|
||||
func RemoveCartItemFromRedis(ctx context.Context, userID, trashID string) error {
|
||||
cart, err := GetCartFromRedis(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedItems := make([]dto.RequestCartItemDTO, 0)
|
||||
for _, ci := range cart.CartItems {
|
||||
if ci.TrashID != trashID {
|
||||
updatedItems = append(updatedItems, ci)
|
||||
}
|
||||
}
|
||||
|
||||
if len(updatedItems) == 0 {
|
||||
return DeleteCartFromRedis(ctx, userID)
|
||||
}
|
||||
|
||||
cart.CartItems = updatedItems
|
||||
return SetCartToRedis(ctx, userID, *cart)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"rijig/dto"
|
||||
"context"
|
||||
)
|
||||
|
||||
type CartService interface {
|
||||
GetCart(ctx context.Context, userID string) (*dto.RequestCartDTO, error)
|
||||
AddOrUpdateItem(ctx context.Context, userID string, item dto.RequestCartItemDTO) error
|
||||
DeleteItem(ctx context.Context, userID string, trashID string) error
|
||||
ClearCart(ctx context.Context, userID string) error
|
||||
}
|
||||
|
||||
type cartService struct{}
|
||||
|
||||
func NewCartService() CartService {
|
||||
return &cartService{}
|
||||
}
|
||||
|
||||
func (s *cartService) GetCart(ctx context.Context, userID string) (*dto.RequestCartDTO, error) {
|
||||
return GetCartFromRedis(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *cartService) AddOrUpdateItem(ctx context.Context, userID string, item dto.RequestCartItemDTO) error {
|
||||
return UpdateOrAddCartItemToRedis(ctx, userID, item)
|
||||
}
|
||||
|
||||
func (s *cartService) DeleteItem(ctx context.Context, userID string, trashID string) error {
|
||||
return RemoveCartItemFromRedis(ctx, userID, trashID)
|
||||
}
|
||||
|
||||
func (s *cartService) ClearCart(ctx context.Context, userID string) error {
|
||||
return DeleteCartFromRedis(ctx, userID)
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"rijig/config"
|
||||
"rijig/dto"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
const cartTTL = 1 * time.Minute
|
||||
|
||||
func getCartKey(userID string) string {
|
||||
return fmt.Sprintf("cart:%s", userID)
|
||||
}
|
||||
|
||||
func GetCartItems(userID string) ([]dto.RequestCartItems, error) {
|
||||
key := getCartKey(userID)
|
||||
val, err := config.RedisClient.Get(config.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []dto.RequestCartItems
|
||||
err = json.Unmarshal([]byte(val), &items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func AddOrUpdateCartItem(userID string, newItem dto.RequestCartItems) error {
|
||||
key := getCartKey(userID)
|
||||
var cartItems []dto.RequestCartItems
|
||||
|
||||
val, err := config.RedisClient.Get(config.Ctx, key).Result()
|
||||
if err == nil && val != "" {
|
||||
json.Unmarshal([]byte(val), &cartItems)
|
||||
}
|
||||
|
||||
updated := false
|
||||
for i, item := range cartItems {
|
||||
if item.TrashCategoryID == newItem.TrashCategoryID {
|
||||
if newItem.Amount == 0 {
|
||||
cartItems = append(cartItems[:i], cartItems[i+1:]...)
|
||||
} else {
|
||||
cartItems[i].Amount = newItem.Amount
|
||||
}
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !updated && newItem.Amount > 0 {
|
||||
cartItems = append(cartItems, newItem)
|
||||
}
|
||||
|
||||
return setCartItems(key, cartItems)
|
||||
}
|
||||
|
||||
func DeleteCartItem(userID, trashID string) error {
|
||||
key := fmt.Sprintf("cart:%s", userID)
|
||||
items, err := GetCartItems(userID)
|
||||
|
||||
if err == redis.Nil {
|
||||
|
||||
log.Printf("No cart found in Redis for user: %s", userID)
|
||||
return fmt.Errorf("no cart found")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Redis error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
index := -1
|
||||
for i, item := range items {
|
||||
if item.TrashCategoryID == trashID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index == -1 {
|
||||
log.Printf("TrashCategoryID %s not found in cart for user %s", trashID, userID)
|
||||
return fmt.Errorf("trashid not found")
|
||||
}
|
||||
|
||||
items = append(items[:index], items[index+1:]...)
|
||||
|
||||
if len(items) == 0 {
|
||||
return config.RedisClient.Del(config.Ctx, key).Err()
|
||||
}
|
||||
|
||||
return setCartItems(key, items)
|
||||
}
|
||||
|
||||
func ClearCart(userID string) error {
|
||||
key := getCartKey(userID)
|
||||
return config.RedisClient.Del(config.Ctx, key).Err()
|
||||
}
|
||||
|
||||
func RefreshCartTTL(userID string) error {
|
||||
key := getCartKey(userID)
|
||||
return config.RedisClient.Expire(config.Ctx, key, cartTTL).Err()
|
||||
}
|
||||
|
||||
func setCartItems(key string, items []dto.RequestCartItems) error {
|
||||
data, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.RedisClient.Set(config.Ctx, key, data, cartTTL).Err()
|
||||
if err != nil {
|
||||
log.Printf("Redis SetCart error: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,154 +1,154 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
// import (
|
||||
// "log"
|
||||
// "time"
|
||||
|
||||
"rijig/dto"
|
||||
"rijig/internal/repositories"
|
||||
"rijig/model"
|
||||
// "rijig/dto"
|
||||
// "rijig/internal/repositories"
|
||||
// "rijig/model"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
// "github.com/google/uuid"
|
||||
// )
|
||||
|
||||
type CartService struct {
|
||||
Repo repositories.CartRepository
|
||||
}
|
||||
// type CartService struct {
|
||||
// Repo repositories.CartRepository
|
||||
// }
|
||||
|
||||
func NewCartService(repo repositories.CartRepository) *CartService {
|
||||
return &CartService{Repo: repo}
|
||||
}
|
||||
// func NewCartService(repo repositories.CartRepository) *CartService {
|
||||
// return &CartService{Repo: repo}
|
||||
// }
|
||||
|
||||
func (s *CartService) CommitCartToDatabase(userID string) error {
|
||||
items, err := GetCartItems(userID)
|
||||
if err != nil || len(items) == 0 {
|
||||
log.Printf("No items to commit for user: %s", userID)
|
||||
return err
|
||||
}
|
||||
// func (s *CartService) CommitCartToDatabase(userID string) error {
|
||||
// items, err := GetCartItems(userID)
|
||||
// if err != nil || len(items) == 0 {
|
||||
// log.Printf("No items to commit for user: %s", userID)
|
||||
// return err
|
||||
// }
|
||||
|
||||
var cartItems []model.CartItem
|
||||
var totalAmount float32
|
||||
var estimatedTotal float32
|
||||
// var cartItems []model.CartItem
|
||||
// var totalAmount float32
|
||||
// var estimatedTotal float32
|
||||
|
||||
for _, item := range items {
|
||||
trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||
if err != nil {
|
||||
log.Printf("Trash category not found for trashID: %s", item.TrashCategoryID)
|
||||
continue
|
||||
}
|
||||
// for _, item := range items {
|
||||
// trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||
// if err != nil {
|
||||
// log.Printf("Trash category not found for trashID: %s", item.TrashCategoryID)
|
||||
// continue
|
||||
// }
|
||||
|
||||
subTotal := float32(trash.EstimatedPrice) * item.Amount
|
||||
totalAmount += item.Amount
|
||||
estimatedTotal += subTotal
|
||||
// subTotal := float32(trash.EstimatedPrice) * item.Amount
|
||||
// totalAmount += item.Amount
|
||||
// estimatedTotal += subTotal
|
||||
|
||||
cartItems = append(cartItems, model.CartItem{
|
||||
ID: uuid.NewString(),
|
||||
TrashCategoryID: item.TrashCategoryID,
|
||||
Amount: item.Amount,
|
||||
SubTotalEstimatedPrice: subTotal,
|
||||
})
|
||||
}
|
||||
// cartItems = append(cartItems, model.CartItem{
|
||||
// ID: uuid.NewString(),
|
||||
// TrashCategoryID: item.TrashCategoryID,
|
||||
// Amount: item.Amount,
|
||||
// SubTotalEstimatedPrice: subTotal,
|
||||
// })
|
||||
// }
|
||||
|
||||
cart := &model.Cart{
|
||||
ID: uuid.NewString(),
|
||||
UserID: userID,
|
||||
CartItems: cartItems,
|
||||
TotalAmount: totalAmount,
|
||||
EstimatedTotalPrice: estimatedTotal,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
// cart := &model.Cart{
|
||||
// ID: uuid.NewString(),
|
||||
// UserID: userID,
|
||||
// CartItems: cartItems,
|
||||
// TotalAmount: totalAmount,
|
||||
// EstimatedTotalPrice: estimatedTotal,
|
||||
// CreatedAt: time.Now(),
|
||||
// UpdatedAt: time.Now(),
|
||||
// }
|
||||
|
||||
if err := s.Repo.DeleteCartByUserID(userID); err != nil {
|
||||
log.Printf("Failed to delete old cart: %v", err)
|
||||
}
|
||||
// if err := s.Repo.DeleteCartByUserID(userID); err != nil {
|
||||
// log.Printf("Failed to delete old cart: %v", err)
|
||||
// }
|
||||
|
||||
if err := s.Repo.CreateCart(cart); err != nil {
|
||||
log.Printf("Failed to create cart: %v", err)
|
||||
return err
|
||||
}
|
||||
// if err := s.Repo.CreateCart(cart); err != nil {
|
||||
// log.Printf("Failed to create cart: %v", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := ClearCart(userID); err != nil {
|
||||
log.Printf("Failed to clear Redis cart: %v", err)
|
||||
}
|
||||
// if err := ClearCart(userID); err != nil {
|
||||
// log.Printf("Failed to clear Redis cart: %v", err)
|
||||
// }
|
||||
|
||||
log.Printf("Cart committed successfully for user: %s", userID)
|
||||
return nil
|
||||
}
|
||||
// log.Printf("Cart committed successfully for user: %s", userID)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *CartService) GetCartFromRedis(userID string) (*dto.CartResponse, error) {
|
||||
items, err := GetCartItems(userID)
|
||||
if err != nil || len(items) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
// func (s *CartService) GetCartFromRedis(userID string) (*dto.CartResponse, error) {
|
||||
// items, err := GetCartItems(userID)
|
||||
// if err != nil || len(items) == 0 {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
var totalAmount float32
|
||||
var estimatedTotal float32
|
||||
var cartItemDTOs []dto.CartItemResponse
|
||||
// var totalAmount float32
|
||||
// var estimatedTotal float32
|
||||
// var cartItemDTOs []dto.CartItemResponse
|
||||
|
||||
for _, item := range items {
|
||||
trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// for _, item := range items {
|
||||
// trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
|
||||
subtotal := float32(trash.EstimatedPrice) * item.Amount
|
||||
totalAmount += item.Amount
|
||||
estimatedTotal += subtotal
|
||||
// subtotal := float32(trash.EstimatedPrice) * item.Amount
|
||||
// totalAmount += item.Amount
|
||||
// estimatedTotal += subtotal
|
||||
|
||||
cartItemDTOs = append(cartItemDTOs, dto.CartItemResponse{
|
||||
TrashId: trash.ID,
|
||||
TrashIcon: trash.Icon,
|
||||
TrashName: trash.Name,
|
||||
Amount: item.Amount,
|
||||
EstimatedSubTotalPrice: subtotal,
|
||||
})
|
||||
}
|
||||
// cartItemDTOs = append(cartItemDTOs, dto.CartItemResponse{
|
||||
// TrashId: trash.ID,
|
||||
// TrashIcon: trash.Icon,
|
||||
// TrashName: trash.Name,
|
||||
// Amount: item.Amount,
|
||||
// EstimatedSubTotalPrice: subtotal,
|
||||
// })
|
||||
// }
|
||||
|
||||
resp := &dto.CartResponse{
|
||||
ID: "N/A",
|
||||
UserID: userID,
|
||||
TotalAmount: totalAmount,
|
||||
EstimatedTotalPrice: estimatedTotal,
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
CartItems: cartItemDTOs,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
// resp := &dto.CartResponse{
|
||||
// ID: "N/A",
|
||||
// UserID: userID,
|
||||
// TotalAmount: totalAmount,
|
||||
// EstimatedTotalPrice: estimatedTotal,
|
||||
// CreatedAt: time.Now().Format(time.RFC3339),
|
||||
// UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
// CartItems: cartItemDTOs,
|
||||
// }
|
||||
// return resp, nil
|
||||
// }
|
||||
|
||||
func (s *CartService) GetCart(userID string) (*dto.CartResponse, error) {
|
||||
// func (s *CartService) GetCart(userID string) (*dto.CartResponse, error) {
|
||||
|
||||
cartRedis, err := s.GetCartFromRedis(userID)
|
||||
if err == nil && len(cartRedis.CartItems) > 0 {
|
||||
return cartRedis, nil
|
||||
}
|
||||
// cartRedis, err := s.GetCartFromRedis(userID)
|
||||
// if err == nil && len(cartRedis.CartItems) > 0 {
|
||||
// return cartRedis, nil
|
||||
// }
|
||||
|
||||
cartDB, err := s.Repo.GetCartByUserID(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// cartDB, err := s.Repo.GetCartByUserID(userID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
var items []dto.CartItemResponse
|
||||
for _, item := range cartDB.CartItems {
|
||||
items = append(items, dto.CartItemResponse{
|
||||
ItemId: item.ID,
|
||||
TrashId: item.TrashCategoryID,
|
||||
TrashIcon: item.TrashCategory.Icon,
|
||||
TrashName: item.TrashCategory.Name,
|
||||
Amount: item.Amount,
|
||||
EstimatedSubTotalPrice: item.SubTotalEstimatedPrice,
|
||||
})
|
||||
}
|
||||
// var items []dto.CartItemResponse
|
||||
// for _, item := range cartDB.CartItems {
|
||||
// items = append(items, dto.CartItemResponse{
|
||||
// ItemId: item.ID,
|
||||
// TrashId: item.TrashCategoryID,
|
||||
// TrashIcon: item.TrashCategory.Icon,
|
||||
// TrashName: item.TrashCategory.Name,
|
||||
// Amount: item.Amount,
|
||||
// EstimatedSubTotalPrice: item.SubTotalEstimatedPrice,
|
||||
// })
|
||||
// }
|
||||
|
||||
resp := &dto.CartResponse{
|
||||
ID: cartDB.ID,
|
||||
UserID: cartDB.UserID,
|
||||
TotalAmount: cartDB.TotalAmount,
|
||||
EstimatedTotalPrice: cartDB.EstimatedTotalPrice,
|
||||
CreatedAt: cartDB.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: cartDB.UpdatedAt.Format(time.RFC3339),
|
||||
CartItems: items,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
// resp := &dto.CartResponse{
|
||||
// ID: cartDB.ID,
|
||||
// UserID: cartDB.UserID,
|
||||
// TotalAmount: cartDB.TotalAmount,
|
||||
// EstimatedTotalPrice: cartDB.EstimatedTotalPrice,
|
||||
// CreatedAt: cartDB.CreatedAt.Format(time.RFC3339),
|
||||
// UpdatedAt: cartDB.UpdatedAt.Format(time.RFC3339),
|
||||
// CartItems: items,
|
||||
// }
|
||||
// return resp, nil
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,76 +1,111 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"log"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"rijig/config"
|
||||
"rijig/internal/services"
|
||||
"rijig/dto"
|
||||
"rijig/model"
|
||||
)
|
||||
|
||||
const (
|
||||
lockPrefix = "lock:cart:"
|
||||
lockExpiration = 30 * time.Second
|
||||
commitThreshold = 20 * time.Second
|
||||
scanPattern = "cart:*"
|
||||
)
|
||||
func CommitExpiredCartsToDB() error {
|
||||
ctx := context.Background()
|
||||
|
||||
func StartCartCommitWorker(service *services.CartService) {
|
||||
keys, err := config.RedisClient.Keys(ctx, "cart:user:*").Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching cart keys: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Println("🛠️ Cart Worker is running in background...")
|
||||
for range ticker.C {
|
||||
processCarts(service)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func processCarts(service *services.CartService) {
|
||||
iter := config.RedisClient.Scan(config.Ctx, 0, scanPattern, 0).Iterator()
|
||||
for iter.Next(config.Ctx) {
|
||||
key := iter.Val()
|
||||
|
||||
ttl, err := config.RedisClient.TTL(config.Ctx, key).Result()
|
||||
if err != nil {
|
||||
log.Printf("❌ Error getting TTL for %s: %v", key, err)
|
||||
for _, key := range keys {
|
||||
ttl, err := config.RedisClient.TTL(ctx, key).Result()
|
||||
if err != nil || ttl > 30*time.Second {
|
||||
continue
|
||||
}
|
||||
|
||||
if ttl > 0 && ttl < commitThreshold {
|
||||
userID := extractUserIDFromKey(key)
|
||||
if userID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
lockKey := lockPrefix + userID
|
||||
acquired, err := config.RedisClient.SetNX(config.Ctx, lockKey, "locked", lockExpiration).Result()
|
||||
if err != nil || !acquired {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("🔄 Auto-committing cart for user %s (TTL: %v)", userID, ttl)
|
||||
if err := service.CommitCartToDatabase(userID); err != nil {
|
||||
log.Printf("❌ Failed to commit cart for %s: %v", userID, err)
|
||||
} else {
|
||||
log.Printf("✅ Cart committed for user %s", userID)
|
||||
}
|
||||
|
||||
val, err := config.RedisClient.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var cart dto.RequestCartDTO
|
||||
if err := json.Unmarshal([]byte(val), &cart); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
userID := extractUserIDFromKey(key)
|
||||
|
||||
cartID := SaveCartToDB(ctx, userID, &cart)
|
||||
|
||||
_ = config.RedisClient.Del(ctx, key).Err()
|
||||
|
||||
fmt.Printf(
|
||||
"[AUTO-COMMIT] UserID: %s | CartID: %s | TotalItem: %d | EstimatedTotalPrice: %.2f | Committed at: %s\n",
|
||||
userID, cartID, len(cart.CartItems), calculateTotalEstimated(&cart), time.Now().Format(time.RFC3339),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if err := iter.Err(); err != nil {
|
||||
log.Printf("❌ Error iterating Redis keys: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractUserIDFromKey(key string) string {
|
||||
if strings.HasPrefix(key, "cart:") {
|
||||
return strings.TrimPrefix(key, "cart:")
|
||||
|
||||
parts := strings.Split(key, ":")
|
||||
if len(parts) == 3 {
|
||||
return parts[2]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func SaveCartToDB(ctx context.Context, userID string, cart *dto.RequestCartDTO) string {
|
||||
totalAmount := float32(0)
|
||||
totalPrice := float32(0)
|
||||
|
||||
var cartItems []model.CartItem
|
||||
for _, item := range cart.CartItems {
|
||||
|
||||
var trash model.TrashCategory
|
||||
if err := config.DB.First(&trash, "id = ?", item.TrashID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
subtotal := trash.EstimatedPrice * float64(item.Amount)
|
||||
totalAmount += item.Amount
|
||||
totalPrice += float32(subtotal)
|
||||
|
||||
cartItems = append(cartItems, model.CartItem{
|
||||
TrashCategoryID: item.TrashID,
|
||||
Amount: item.Amount,
|
||||
SubTotalEstimatedPrice: float32(subtotal),
|
||||
})
|
||||
}
|
||||
|
||||
newCart := model.Cart{
|
||||
UserID: userID,
|
||||
TotalAmount: totalAmount,
|
||||
EstimatedTotalPrice: totalPrice,
|
||||
CartItems: cartItems,
|
||||
}
|
||||
|
||||
if err := config.DB.WithContext(ctx).Create(&newCart).Error; err != nil {
|
||||
fmt.Printf("Error committing cart: %v\n", err)
|
||||
}
|
||||
|
||||
return newCart.ID
|
||||
}
|
||||
|
||||
func calculateTotalEstimated(cart *dto.RequestCartDTO) float32 {
|
||||
var total float32
|
||||
for _, item := range cart.CartItems {
|
||||
var trash model.TrashCategory
|
||||
if err := config.DB.First(&trash, "id = ?", item.TrashID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
total += item.Amount * float32(trash.EstimatedPrice)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import (
|
|||
|
||||
type Cart struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
|
||||
UserID string `gorm:"not null" json:"userid"`
|
||||
UserID string `gorm:"not null" json:"user_id"`
|
||||
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;" json:"-"`
|
||||
CartItems []CartItem `gorm:"foreignKey:CartID;constraint:OnDelete:CASCADE;" json:"cartitems"`
|
||||
TotalAmount float32 `json:"totalamount"`
|
||||
EstimatedTotalPrice float32 `json:"estimated_totalprice"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
|
||||
CartItems []CartItem `gorm:"foreignKey:CartID;constraint:OnDelete:CASCADE;" json:"cart_items"`
|
||||
TotalAmount float32 `json:"total_amount"`
|
||||
EstimatedTotalPrice float32 `json:"estimated_total_price"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
||||
type CartItem struct {
|
||||
|
|
@ -22,7 +22,7 @@ type CartItem struct {
|
|||
TrashCategoryID string `gorm:"not null" json:"trash_id"`
|
||||
TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"trash_category"`
|
||||
Amount float32 `json:"amount"`
|
||||
SubTotalEstimatedPrice float32 `json:"subtotalestimatedprice"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
|
||||
SubTotalEstimatedPrice float32 `json:"subtotal_estimated_price"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package presentation
|
||||
|
||||
import (
|
||||
"rijig/internal/handler"
|
||||
"rijig/internal/services"
|
||||
"rijig/middleware"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TrashCartRouter(api fiber.Router) {
|
||||
cartService := services.NewCartService()
|
||||
cartHandler := handler.NewCartHandler(cartService)
|
||||
|
||||
cart := api.Group("/cart")
|
||||
cart.Use(middleware.AuthMiddleware)
|
||||
|
||||
cart.Get("/", cartHandler.GetCart)
|
||||
cart.Post("/item", cartHandler.AddOrUpdateCartItem)
|
||||
cart.Post("/items", cartHandler.AddMultipleCartItems)
|
||||
cart.Delete("/item/:trashID", cartHandler.DeleteCartItem)
|
||||
cart.Delete("/", cartHandler.ClearCart)
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package presentation
|
||||
|
||||
import (
|
||||
"rijig/internal/handler"
|
||||
"rijig/internal/repositories"
|
||||
"rijig/internal/services"
|
||||
"rijig/internal/worker"
|
||||
"rijig/middleware"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TrashCartRouter(api fiber.Router) {
|
||||
cartRepo := repositories.NewCartRepository()
|
||||
cartService := services.NewCartService(cartRepo)
|
||||
cartHandler := handler.NewCartHandler(cartService)
|
||||
|
||||
worker.StartCartCommitWorker(cartService)
|
||||
|
||||
cart := api.Group("/cart", middleware.AuthMiddleware)
|
||||
cart.Put("/refresh", cartHandler.RefreshCartTTL)
|
||||
cart.Post("/", cartHandler.AddOrUpdateCartItem)
|
||||
cart.Get("/", cartHandler.GetCart)
|
||||
cart.Post("/commit", cartHandler.CommitCart)
|
||||
cart.Delete("/", cartHandler.ClearCart)
|
||||
cart.Delete("/:trashid", cartHandler.DeleteCartItem)
|
||||
}
|
||||
Loading…
Reference in New Issue