MIF_E31222379_BE/internal/services/trashcart_service.go

241 lines
6.3 KiB
Go

package services
import (
"encoding/json"
"errors"
"fmt"
"log"
"time"
"rijig/dto"
"rijig/internal/repositories"
"rijig/model"
"rijig/utils"
)
type CartService interface {
CreateCartFromDTO(userID string, items []dto.RequestCartItems) error
GetCartByUserID(userID string) (*dto.CartResponse, error)
CommitCartFromRedis(userID string) error
DeleteCart(cartID string) error
}
type cartService struct {
repo repositories.CartRepository
repoTrash repositories.TrashRepository
}
func NewCartService(repo repositories.CartRepository, repoTrash repositories.TrashRepository) CartService {
return &cartService{repo: repo, repoTrash: repoTrash}
}
func redisCartKey(userID string) string {
return fmt.Sprintf("cart:user:%s", userID)
}
func (s *cartService) CreateCartFromDTO(userID string, items []dto.RequestCartItems) error {
// Validasi semua item
for _, item := range items {
if errMap, valid := item.ValidateRequestCartItem(); !valid {
return dto.ValidationErrors{Errors: errMap}
}
}
// Ambil cart yang sudah ada dari Redis (jika ada)
var existingCart dto.CartResponse
val, err := utils.GetData(redisCartKey(userID))
if err == nil && val != "" {
if err := json.Unmarshal([]byte(val), &existingCart); err != nil {
log.Printf("Failed to unmarshal existing cart: %v", err)
}
}
// Buat map dari existing items untuk mempermudah update
itemMap := make(map[string]dto.CartItemResponse)
for _, item := range existingCart.CartItems {
itemMap[item.TrashName] = item
}
// Proses input baru
for _, input := range items {
trash, err := s.repoTrash.GetCategoryByID(input.TrashID)
if err != nil {
return fmt.Errorf("failed to retrieve trash category for id %s: %v", input.TrashID, err)
}
if input.Amount == 0 {
delete(itemMap, trash.Name) // hapus item
continue
}
subtotal := float32(trash.EstimatedPrice) * input.Amount
itemMap[trash.Name] = dto.CartItemResponse{
TrashIcon: trash.Icon,
TrashName: trash.Name,
Amount: input.Amount,
EstimatedSubTotalPrice: subtotal,
}
}
// Rekonstruksi cart
var finalItems []dto.CartItemResponse
var totalAmount float32
var totalPrice float32
for _, item := range itemMap {
finalItems = append(finalItems, item)
totalAmount += item.Amount
totalPrice += item.EstimatedSubTotalPrice
}
cart := dto.CartResponse{
ID: existingCart.ID,
UserID: userID,
TotalAmount: totalAmount,
EstimatedTotalPrice: totalPrice,
CartItems: finalItems,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Simpan ulang ke Redis dengan TTL 10 menit
return utils.SetData(redisCartKey(userID), cart, 1*time.Minute)
}
func (s *cartService) GetCartByUserID(userID string) (*dto.CartResponse, error) {
val, err := utils.GetData(redisCartKey(userID))
if err != nil {
log.Printf("Redis get error: %v", err)
}
if val != "" {
var cached dto.CartResponse
if err := json.Unmarshal([]byte(val), &cached); err == nil {
return &cached, nil
}
}
cart, err := s.repo.GetByUserID(userID)
if err != nil {
return nil, err
}
if cart == nil {
return nil, nil
}
var items []dto.CartItemResponse
for _, item := range cart.CartItems {
items = append(items, dto.CartItemResponse{
TrashIcon: item.TrashCategory.Icon,
TrashName: item.TrashCategory.Name,
Amount: item.Amount,
EstimatedSubTotalPrice: item.SubTotalEstimatedPrice,
})
}
response := &dto.CartResponse{
ID: cart.ID,
UserID: cart.UserID,
TotalAmount: cart.TotalAmount,
EstimatedTotalPrice: cart.EstimatedTotalPrice,
CartItems: items,
CreatedAt: cart.CreatedAt,
UpdatedAt: cart.UpdatedAt,
}
return response, nil
}
func (s *cartService) CommitCartFromRedis(userID string) error {
val, err := utils.GetData(redisCartKey(userID))
if err != nil || val == "" {
return errors.New("no cart found in redis")
}
var cartDTO dto.CartResponse
if err := json.Unmarshal([]byte(val), &cartDTO); err != nil {
return errors.New("invalid cart data in Redis")
}
existingCart, err := s.repo.GetByUserID(userID)
if err != nil {
return fmt.Errorf("failed to get cart from db: %v", err)
}
if existingCart == nil {
// buat cart baru jika belum ada
var items []model.CartItem
for _, item := range cartDTO.CartItems {
trash, err := s.repoTrash.GetTrashCategoryByName(item.TrashName)
if err != nil {
continue
}
items = append(items, model.CartItem{
TrashID: trash.ID,
Amount: item.Amount,
SubTotalEstimatedPrice: item.EstimatedSubTotalPrice,
})
}
newCart := model.Cart{
UserID: userID,
TotalAmount: cartDTO.TotalAmount,
EstimatedTotalPrice: cartDTO.EstimatedTotalPrice,
CartItems: items,
}
return s.repo.Create(&newCart)
}
// buat map item lama (by trash_name)
existingItemMap := make(map[string]*model.CartItem)
for i := range existingCart.CartItems {
trashName := existingCart.CartItems[i].TrashCategory.Name
existingItemMap[trashName] = &existingCart.CartItems[i]
}
// proses update/hapus/tambah
for _, newItem := range cartDTO.CartItems {
if newItem.Amount == 0 {
if existing, ok := existingItemMap[newItem.TrashName]; ok {
_ = s.repo.DeleteCartItemByID(existing.ID)
}
continue
}
trash, err := s.repoTrash.GetTrashCategoryByName(newItem.TrashName)
if err != nil {
continue
}
if existing, ok := existingItemMap[newItem.TrashName]; ok {
existing.Amount = newItem.Amount
existing.SubTotalEstimatedPrice = newItem.EstimatedSubTotalPrice
_ = s.repo.UpdateCartItem(existing)
} else {
newModelItem := model.CartItem{
CartID: existingCart.ID,
TrashID: trash.ID,
Amount: newItem.Amount,
SubTotalEstimatedPrice: newItem.EstimatedSubTotalPrice,
}
_ = s.repo.InsertCartItem(&newModelItem)
}
}
// update cart total amount & price
existingCart.TotalAmount = cartDTO.TotalAmount
existingCart.EstimatedTotalPrice = cartDTO.EstimatedTotalPrice
if err := s.repo.Update(existingCart); err != nil {
return err
}
return utils.DeleteData(redisCartKey(userID))
}
func (s *cartService) DeleteCart(cartID string) error {
return s.repo.Delete(cartID)
}