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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"rijig/config"
|
"rijig/config"
|
||||||
|
"rijig/internal/worker"
|
||||||
"rijig/router"
|
"rijig/router"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
"github.com/robfig/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config.SetupConfig()
|
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 := fiber.New()
|
||||||
|
|
||||||
app.Use(cors.New(cors.Config{
|
app.Use(cors.New(cors.Config{
|
||||||
|
|
|
||||||
|
|
@ -5,65 +5,53 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValidationErrors struct {
|
type RequestCartItemDTO struct {
|
||||||
Errors map[string][]string
|
TrashID string `json:"trash_id"`
|
||||||
|
Amount float32 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ValidationErrors) Error() string {
|
type RequestCartDTO struct {
|
||||||
return "validation error"
|
CartItems []RequestCartItemDTO `json:"cart_items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartResponse struct {
|
type ResponseCartItemDTO struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"userid"`
|
TrashID string `json:"trash_id"`
|
||||||
CartItems []CartItemResponse `json:"cartitems"`
|
TrashName string `json:"trash_name"`
|
||||||
TotalAmount float32 `json:"totalamount"`
|
TrashIcon string `json:"trash_icon"`
|
||||||
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"`
|
|
||||||
Amount float32 `json:"amount"`
|
Amount float32 `json:"amount"`
|
||||||
EstimatedSubTotalPrice float32 `json:"estimated_subtotalprice"`
|
SubTotalEstimatedPrice float32 `json:"subtotal_estimated_price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestCartItems struct {
|
type ResponseCartDTO struct {
|
||||||
TrashCategoryID string `json:"trashid"`
|
ID string `json:"id"`
|
||||||
Amount float32 `json:"amount"`
|
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)
|
errors := make(map[string][]string)
|
||||||
|
|
||||||
if strings.TrimSpace(r.TrashCategoryID) == "" {
|
if len(r.CartItems) == 0 {
|
||||||
errors["trashid"] = append(errors["trashid"], "trashid is required")
|
errors["cart_items"] = append(errors["cart_items"], "minimal satu item harus dimasukkan")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
for i, item := range r.CartItems {
|
||||||
return errors, false
|
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")
|
||||||
|
}
|
||||||
return nil, true
|
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")
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
return errors, false
|
return errors, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, true
|
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/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/mdp/qrterminal/v3 v3.2.0
|
github.com/mdp/qrterminal/v3 v3.2.0
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
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/rs/zerolog v1.33.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.51.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/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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
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=
|
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
|
package services
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"log"
|
// "log"
|
||||||
"time"
|
// "time"
|
||||||
|
|
||||||
"rijig/dto"
|
// "rijig/dto"
|
||||||
"rijig/internal/repositories"
|
// "rijig/internal/repositories"
|
||||||
"rijig/model"
|
// "rijig/model"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
// "github.com/google/uuid"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type CartService struct {
|
// type CartService struct {
|
||||||
Repo repositories.CartRepository
|
// Repo repositories.CartRepository
|
||||||
}
|
// }
|
||||||
|
|
||||||
func NewCartService(repo repositories.CartRepository) *CartService {
|
// func NewCartService(repo repositories.CartRepository) *CartService {
|
||||||
return &CartService{Repo: repo}
|
// return &CartService{Repo: repo}
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *CartService) CommitCartToDatabase(userID string) error {
|
// func (s *CartService) CommitCartToDatabase(userID string) error {
|
||||||
items, err := GetCartItems(userID)
|
// items, err := GetCartItems(userID)
|
||||||
if err != nil || len(items) == 0 {
|
// if err != nil || len(items) == 0 {
|
||||||
log.Printf("No items to commit for user: %s", userID)
|
// log.Printf("No items to commit for user: %s", userID)
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var cartItems []model.CartItem
|
// var cartItems []model.CartItem
|
||||||
var totalAmount float32
|
// var totalAmount float32
|
||||||
var estimatedTotal float32
|
// var estimatedTotal float32
|
||||||
|
|
||||||
for _, item := range items {
|
// for _, item := range items {
|
||||||
trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
// trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Printf("Trash category not found for trashID: %s", item.TrashCategoryID)
|
// log.Printf("Trash category not found for trashID: %s", item.TrashCategoryID)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
subTotal := float32(trash.EstimatedPrice) * item.Amount
|
// subTotal := float32(trash.EstimatedPrice) * item.Amount
|
||||||
totalAmount += item.Amount
|
// totalAmount += item.Amount
|
||||||
estimatedTotal += subTotal
|
// estimatedTotal += subTotal
|
||||||
|
|
||||||
cartItems = append(cartItems, model.CartItem{
|
// cartItems = append(cartItems, model.CartItem{
|
||||||
ID: uuid.NewString(),
|
// ID: uuid.NewString(),
|
||||||
TrashCategoryID: item.TrashCategoryID,
|
// TrashCategoryID: item.TrashCategoryID,
|
||||||
Amount: item.Amount,
|
// Amount: item.Amount,
|
||||||
SubTotalEstimatedPrice: subTotal,
|
// SubTotalEstimatedPrice: subTotal,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
cart := &model.Cart{
|
// cart := &model.Cart{
|
||||||
ID: uuid.NewString(),
|
// ID: uuid.NewString(),
|
||||||
UserID: userID,
|
// UserID: userID,
|
||||||
CartItems: cartItems,
|
// CartItems: cartItems,
|
||||||
TotalAmount: totalAmount,
|
// TotalAmount: totalAmount,
|
||||||
EstimatedTotalPrice: estimatedTotal,
|
// EstimatedTotalPrice: estimatedTotal,
|
||||||
CreatedAt: time.Now(),
|
// CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
// UpdatedAt: time.Now(),
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := s.Repo.DeleteCartByUserID(userID); err != nil {
|
// if err := s.Repo.DeleteCartByUserID(userID); err != nil {
|
||||||
log.Printf("Failed to delete old cart: %v", err)
|
// log.Printf("Failed to delete old cart: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := s.Repo.CreateCart(cart); err != nil {
|
// if err := s.Repo.CreateCart(cart); err != nil {
|
||||||
log.Printf("Failed to create cart: %v", err)
|
// log.Printf("Failed to create cart: %v", err)
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := ClearCart(userID); err != nil {
|
// if err := ClearCart(userID); err != nil {
|
||||||
log.Printf("Failed to clear Redis cart: %v", err)
|
// log.Printf("Failed to clear Redis cart: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Printf("Cart committed successfully for user: %s", userID)
|
// log.Printf("Cart committed successfully for user: %s", userID)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *CartService) GetCartFromRedis(userID string) (*dto.CartResponse, error) {
|
// func (s *CartService) GetCartFromRedis(userID string) (*dto.CartResponse, error) {
|
||||||
items, err := GetCartItems(userID)
|
// items, err := GetCartItems(userID)
|
||||||
if err != nil || len(items) == 0 {
|
// if err != nil || len(items) == 0 {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var totalAmount float32
|
// var totalAmount float32
|
||||||
var estimatedTotal float32
|
// var estimatedTotal float32
|
||||||
var cartItemDTOs []dto.CartItemResponse
|
// var cartItemDTOs []dto.CartItemResponse
|
||||||
|
|
||||||
for _, item := range items {
|
// for _, item := range items {
|
||||||
trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
// trash, err := s.Repo.GetTrashCategoryByID(item.TrashCategoryID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
subtotal := float32(trash.EstimatedPrice) * item.Amount
|
// subtotal := float32(trash.EstimatedPrice) * item.Amount
|
||||||
totalAmount += item.Amount
|
// totalAmount += item.Amount
|
||||||
estimatedTotal += subtotal
|
// estimatedTotal += subtotal
|
||||||
|
|
||||||
cartItemDTOs = append(cartItemDTOs, dto.CartItemResponse{
|
// cartItemDTOs = append(cartItemDTOs, dto.CartItemResponse{
|
||||||
TrashId: trash.ID,
|
// TrashId: trash.ID,
|
||||||
TrashIcon: trash.Icon,
|
// TrashIcon: trash.Icon,
|
||||||
TrashName: trash.Name,
|
// TrashName: trash.Name,
|
||||||
Amount: item.Amount,
|
// Amount: item.Amount,
|
||||||
EstimatedSubTotalPrice: subtotal,
|
// EstimatedSubTotalPrice: subtotal,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
resp := &dto.CartResponse{
|
// resp := &dto.CartResponse{
|
||||||
ID: "N/A",
|
// ID: "N/A",
|
||||||
UserID: userID,
|
// UserID: userID,
|
||||||
TotalAmount: totalAmount,
|
// TotalAmount: totalAmount,
|
||||||
EstimatedTotalPrice: estimatedTotal,
|
// EstimatedTotalPrice: estimatedTotal,
|
||||||
CreatedAt: time.Now().Format(time.RFC3339),
|
// CreatedAt: time.Now().Format(time.RFC3339),
|
||||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
// UpdatedAt: time.Now().Format(time.RFC3339),
|
||||||
CartItems: cartItemDTOs,
|
// CartItems: cartItemDTOs,
|
||||||
}
|
// }
|
||||||
return resp, nil
|
// 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)
|
// cartRedis, err := s.GetCartFromRedis(userID)
|
||||||
if err == nil && len(cartRedis.CartItems) > 0 {
|
// if err == nil && len(cartRedis.CartItems) > 0 {
|
||||||
return cartRedis, nil
|
// return cartRedis, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
cartDB, err := s.Repo.GetCartByUserID(userID)
|
// cartDB, err := s.Repo.GetCartByUserID(userID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var items []dto.CartItemResponse
|
// var items []dto.CartItemResponse
|
||||||
for _, item := range cartDB.CartItems {
|
// for _, item := range cartDB.CartItems {
|
||||||
items = append(items, dto.CartItemResponse{
|
// items = append(items, dto.CartItemResponse{
|
||||||
ItemId: item.ID,
|
// ItemId: item.ID,
|
||||||
TrashId: item.TrashCategoryID,
|
// TrashId: item.TrashCategoryID,
|
||||||
TrashIcon: item.TrashCategory.Icon,
|
// TrashIcon: item.TrashCategory.Icon,
|
||||||
TrashName: item.TrashCategory.Name,
|
// TrashName: item.TrashCategory.Name,
|
||||||
Amount: item.Amount,
|
// Amount: item.Amount,
|
||||||
EstimatedSubTotalPrice: item.SubTotalEstimatedPrice,
|
// EstimatedSubTotalPrice: item.SubTotalEstimatedPrice,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
resp := &dto.CartResponse{
|
// resp := &dto.CartResponse{
|
||||||
ID: cartDB.ID,
|
// ID: cartDB.ID,
|
||||||
UserID: cartDB.UserID,
|
// UserID: cartDB.UserID,
|
||||||
TotalAmount: cartDB.TotalAmount,
|
// TotalAmount: cartDB.TotalAmount,
|
||||||
EstimatedTotalPrice: cartDB.EstimatedTotalPrice,
|
// EstimatedTotalPrice: cartDB.EstimatedTotalPrice,
|
||||||
CreatedAt: cartDB.CreatedAt.Format(time.RFC3339),
|
// CreatedAt: cartDB.CreatedAt.Format(time.RFC3339),
|
||||||
UpdatedAt: cartDB.UpdatedAt.Format(time.RFC3339),
|
// UpdatedAt: cartDB.UpdatedAt.Format(time.RFC3339),
|
||||||
CartItems: items,
|
// CartItems: items,
|
||||||
}
|
// }
|
||||||
return resp, nil
|
// return resp, nil
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,111 @@
|
||||||
package worker
|
package worker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"rijig/config"
|
"rijig/config"
|
||||||
"rijig/internal/services"
|
"rijig/dto"
|
||||||
|
"rijig/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func CommitExpiredCartsToDB() error {
|
||||||
lockPrefix = "lock:cart:"
|
ctx := context.Background()
|
||||||
lockExpiration = 30 * time.Second
|
|
||||||
commitThreshold = 20 * time.Second
|
|
||||||
scanPattern = "cart:*"
|
|
||||||
)
|
|
||||||
|
|
||||||
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() {
|
for _, key := range keys {
|
||||||
ticker := time.NewTicker(10 * time.Second)
|
ttl, err := config.RedisClient.TTL(ctx, key).Result()
|
||||||
defer ticker.Stop()
|
if err != nil || ttl > 30*time.Second {
|
||||||
|
|
||||||
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)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ttl > 0 && ttl < commitThreshold {
|
val, err := config.RedisClient.Get(ctx, key).Result()
|
||||||
userID := extractUserIDFromKey(key)
|
if err != nil {
|
||||||
if userID == "" {
|
continue
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
return nil
|
||||||
log.Printf("❌ Error iterating Redis keys: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractUserIDFromKey(key string) string {
|
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 ""
|
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 {
|
type Cart struct {
|
||||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
|
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:"-"`
|
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;" json:"-"`
|
||||||
CartItems []CartItem `gorm:"foreignKey:CartID;constraint:OnDelete:CASCADE;" json:"cartitems"`
|
CartItems []CartItem `gorm:"foreignKey:CartID;constraint:OnDelete:CASCADE;" json:"cart_items"`
|
||||||
TotalAmount float32 `json:"totalamount"`
|
TotalAmount float32 `json:"total_amount"`
|
||||||
EstimatedTotalPrice float32 `json:"estimated_totalprice"`
|
EstimatedTotalPrice float32 `json:"estimated_total_price"`
|
||||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartItem struct {
|
type CartItem struct {
|
||||||
|
|
@ -22,7 +22,7 @@ type CartItem struct {
|
||||||
TrashCategoryID string `gorm:"not null" json:"trash_id"`
|
TrashCategoryID string `gorm:"not null" json:"trash_id"`
|
||||||
TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"trash_category"`
|
TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"trash_category"`
|
||||||
Amount float32 `json:"amount"`
|
Amount float32 `json:"amount"`
|
||||||
SubTotalEstimatedPrice float32 `json:"subtotalestimatedprice"`
|
SubTotalEstimatedPrice float32 `json:"subtotal_estimated_price"`
|
||||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_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