mirror of
				https://github.com/3ybactuk/marketplace-go-service-project.git
				synced 2025-10-30 22:13:44 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			212 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package service
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"slices"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/rs/zerolog/log"
 | |
| 
 | |
| 	"route256/cart/internal/domain/entity"
 | |
| 	"route256/cart/internal/domain/model"
 | |
| )
 | |
| 
 | |
| //go:generate minimock -i Repository -o ./mock -s _mock.go
 | |
| type Repository interface {
 | |
| 	AddItem(ctx context.Context, userID entity.UID, item *model.Item) error
 | |
| 	GetItemsByUserID(ctx context.Context, userID entity.UID) (entity.Cart, error)
 | |
| 	DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) error
 | |
| 	DeleteItemsByUserID(ctx context.Context, userID entity.UID) error
 | |
| }
 | |
| 
 | |
| type ProductService interface {
 | |
| 	GetProductBySku(ctx context.Context, sku entity.Sku) (*model.Product, error)
 | |
| }
 | |
| 
 | |
| type LomsService interface {
 | |
| 	OrderCreate(ctx context.Context, cart *model.Cart) (int64, error)
 | |
| 	StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error)
 | |
| }
 | |
| 
 | |
| type CartService struct {
 | |
| 	repository Repository
 | |
| 
 | |
| 	productService ProductService
 | |
| 	lomsService    LomsService
 | |
| }
 | |
| 
 | |
| func NewCartService(repository Repository, productService ProductService, lomsService LomsService) *CartService {
 | |
| 	return &CartService{
 | |
| 		repository:     repository,
 | |
| 		productService: productService,
 | |
| 		lomsService:    lomsService,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *CartService) AddItem(ctx context.Context, userID entity.UID, item *model.Item) error {
 | |
| 	if err := item.Validate(); err != nil {
 | |
| 		return fmt.Errorf("invalid requested values: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if userID <= 0 {
 | |
| 		return fmt.Errorf("invalid userID")
 | |
| 	}
 | |
| 
 | |
| 	_, err := s.productService.GetProductBySku(ctx, item.Product.Sku)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("productService.GetProductBySku: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	count, err := s.lomsService.StocksInfo(ctx, item.Product.Sku)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("lomsService.StocksInfo: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if count < item.Count {
 | |
| 		return model.ErrNotEnoughStocks
 | |
| 	}
 | |
| 
 | |
| 	if err := s.repository.AddItem(ctx, userID, item); err != nil {
 | |
| 		return fmt.Errorf("repository.AddItemToCart: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // GetUserCart gets all user cart's item ids, gets the item description from the product-service
 | |
| // and return a list of the collected items.
 | |
| // In case of failed request to product-service, return nothing and error.
 | |
| //
 | |
| // TODO: add worker group, BUT it's OK for now,
 | |
| // assuming user does not have hundreds of different items in his cart.
 | |
| func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) (*model.Cart, error) {
 | |
| 	if userID <= 0 {
 | |
| 		return nil, fmt.Errorf("userID invalid")
 | |
| 	}
 | |
| 
 | |
| 	cart, err := s.repository.GetItemsByUserID(ctx, userID)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("repository.AddItemToCart: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if len(cart.Items) == 0 {
 | |
| 		return nil, model.ErrCartNotFound
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(ctx)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	resultCart := &model.Cart{
 | |
| 		UserID:     userID,
 | |
| 		Items:      make([]*model.Item, len(cart.Items)),
 | |
| 		TotalPrice: 0,
 | |
| 	}
 | |
| 
 | |
| 	errCh := make(chan error, 1)
 | |
| 
 | |
| 	var (
 | |
| 		wg       sync.WaitGroup
 | |
| 		sumMutex sync.Mutex
 | |
| 	)
 | |
| 
 | |
| 	for idx, sku := range cart.Items {
 | |
| 		wg.Add(1)
 | |
| 
 | |
| 		go func(sku entity.Sku, count uint32, idx int) {
 | |
| 			defer wg.Done()
 | |
| 
 | |
| 			product, err := s.productService.GetProductBySku(ctx, sku)
 | |
| 			if err != nil {
 | |
| 				select {
 | |
| 				case errCh <- fmt.Errorf("productService.GetProductBySku: %w", err):
 | |
| 				case <-ctx.Done():
 | |
| 				}
 | |
| 
 | |
| 				log.Error().Err(err).Msg("productService.GetProductBySku")
 | |
| 
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			resultCart.Items[idx] = &model.Item{
 | |
| 				Product: product,
 | |
| 				Count:   count,
 | |
| 			}
 | |
| 
 | |
| 			sumMutex.Lock()
 | |
| 			resultCart.TotalPrice += uint32(product.Price) * count
 | |
| 			sumMutex.Unlock()
 | |
| 		}(sku, cart.ItemCount[sku], idx)
 | |
| 	}
 | |
| 
 | |
| 	doneCh := make(chan struct{})
 | |
| 	go func() {
 | |
| 		wg.Wait()
 | |
| 
 | |
| 		close(doneCh)
 | |
| 	}()
 | |
| 
 | |
| 	select {
 | |
| 	case err := <-errCh:
 | |
| 		cancel()
 | |
| 
 | |
| 		return nil, err
 | |
| 	case <-doneCh:
 | |
| 		slices.SortStableFunc(resultCart.Items, func(a, b *model.Item) int {
 | |
| 			return int(a.Product.Sku - b.Product.Sku)
 | |
| 		})
 | |
| 
 | |
| 		return resultCart, nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *CartService) DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) error {
 | |
| 	if userID <= 0 {
 | |
| 		return fmt.Errorf("userID invalid")
 | |
| 	}
 | |
| 
 | |
| 	if sku <= 0 {
 | |
| 		return fmt.Errorf("sku invalid")
 | |
| 	}
 | |
| 
 | |
| 	if err := s.repository.DeleteItem(ctx, userID, sku); err != nil {
 | |
| 		return fmt.Errorf("repository.DeleteItemFromUserCart: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *CartService) DeleteItemsByUserID(ctx context.Context, userID entity.UID) error {
 | |
| 	if userID <= 0 {
 | |
| 		return fmt.Errorf("userID invalid")
 | |
| 	}
 | |
| 
 | |
| 	if err := s.repository.DeleteItemsByUserID(ctx, userID); err != nil {
 | |
| 		return fmt.Errorf("repository.DeleteUserCart: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *CartService) CheckoutUserCart(ctx context.Context, userID entity.UID) (int64, error) {
 | |
| 	if userID <= 0 {
 | |
| 		return 0, fmt.Errorf("userID invalid")
 | |
| 	}
 | |
| 
 | |
| 	cart, err := s.GetItemsByUserID(ctx, entity.UID(userID))
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	orderID, err := s.lomsService.OrderCreate(ctx, cart)
 | |
| 	if err != nil {
 | |
| 		return 0, fmt.Errorf("lomsService.OrderCreate: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if err := s.DeleteItemsByUserID(ctx, userID); err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	return orderID, nil
 | |
| }
 | 
