From 3d5e4e473b1e34adfc9b865849eb0fb940d2bbcf Mon Sep 17 00:00:00 2001 From: mzfarshad Date: Fri, 10 Apr 2026 18:52:30 +0330 Subject: [PATCH] implemented service layer --- shoppingbasketapp/service/entity.go | 20 ++++++ shoppingbasketapp/service/param.go | 29 ++++++++ shoppingbasketapp/service/service.go | 95 ++++++++++++++++++++++++++ shoppingbasketapp/service/validator.go | 91 ++++++++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 shoppingbasketapp/service/validator.go diff --git a/shoppingbasketapp/service/entity.go b/shoppingbasketapp/service/entity.go index 6d43c336..6f10c37f 100644 --- a/shoppingbasketapp/service/entity.go +++ b/shoppingbasketapp/service/entity.go @@ -1 +1,21 @@ package service + +import ( + "git.gocasts.ir/ebhomengo/niki/types" +) + +type CartItem struct { + ProductID types.ID + Quantity int + Price types.Price + Name string + AddedAt int64 +} + +type Cart struct { + UserID types.ID + Items []CartItem + TotalPrice types.Price + ExpireAt int64 + CreatedAt int64 +} diff --git a/shoppingbasketapp/service/param.go b/shoppingbasketapp/service/param.go index 6d43c336..264c053e 100644 --- a/shoppingbasketapp/service/param.go +++ b/shoppingbasketapp/service/param.go @@ -1 +1,30 @@ package service + +import "git.gocasts.ir/ebhomengo/niki/types" + +type AddToCartRequest struct { + UserID types.ID `json:"user_id"` + ProductID types.ID `json:"product_id"` + Quantity int `json:"quantity"` + Price types.Price `json:"price"` + Name string `json:"name"` +} + +type GetCartResponse struct { + UserID types.ID `json:"user_id"` + Items []CartItem `json:"items"` + TotalPrice types.Price `json:"total_price"` + CreatedAt int64 `json:"created_at"` + ExpireAt int64 `json:"expire_at"` +} + +type RemoveFromCartRequest struct { + UserID types.ID `json:"user_id"` + ProductID types.ID `json:"product_id"` +} + +type UpdateQuantityRequest struct { + UserID types.ID `json:"user_id"` + ProductID types.ID `json:"product_id"` + Quantity int `json:"quantity"` +} diff --git a/shoppingbasketapp/service/service.go b/shoppingbasketapp/service/service.go index 6d43c336..59d5a00b 100644 --- a/shoppingbasketapp/service/service.go +++ b/shoppingbasketapp/service/service.go @@ -1 +1,96 @@ package service + +import ( + "context" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + "git.gocasts.ir/ebhomengo/niki/types" + "time" +) + +type Repository interface { + AddItem(ctx context.Context, userID types.ID, cart CartItem) error + GetCart(ctx context.Context, userID types.ID) (Cart, error) + DeleteItem(ctx context.Context, userID, productID types.ID) error + UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error + DeleteCart(ctx context.Context, userID types.ID) error +} + +type Service struct { + validate Validate + repo Repository +} + +func New(val Validate, repo Repository) Service { + return Service{validate: val, repo: repo} +} + +func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error { + const op = "shoppingbasketapp.service.AddToBasket" + + if err := s.validate.ValidateAddToCart(req); err != nil { + return err + } + + return s.repo.AddItem(ctx, req.UserID, CartItem{ + ProductID: req.ProductID, + Quantity: req.Quantity, + Price: req.Price, + Name: req.Name, + AddedAt: time.Now().UnixNano(), + }) +} + +func (s Service) GetCart(ctx context.Context, userID types.ID) (GetCartResponse, error) { + const op = "shoppingbasketapp.service.GetCart" + if userID < 1 { + return GetCartResponse{}, richerror.New(op).WithKind(richerror.KindInvalid).WithMessage("invalid user id") + } + + res, err := s.repo.GetCart(ctx, userID) + if err != nil { + return GetCartResponse{}, richerror.New(op).WithErr(err) + } + + return GetCartResponse{ + UserID: res.UserID, + Items: res.Items, + TotalPrice: res.TotalPrice, + CreatedAt: res.CreatedAt, + ExpireAt: res.ExpireAt, + }, nil +} + +func (s Service) RemoveFromCart(ctx context.Context, req RemoveFromCartRequest) error { + const op = "shoppingbaskerapp.service.RemoveFromCart" + + if err := s.validate.ValidateRemoveFromCart(req); err != nil { + return err + } + + return s.repo.DeleteItem(ctx, req.UserID, req.ProductID) +} + +func (s Service) UpdateQuantity(ctx context.Context, req UpdateQuantityRequest) error { + const op = "shoppingbaskerapp.service.UpdateQuantity" + + if err := s.validate.ValidateUpdateQuantity(req); err != nil { + return err + } + + if req.Quantity == 0 { + return s.repo.DeleteItem(ctx, req.UserID, req.ProductID) + } + + return s.repo.UpdateQuantity(ctx, req.UserID, req.ProductID, req.Quantity) +} + +func (s Service) ClearCart(ctx context.Context, userID types.ID) error { + const op = "shoppingbaskerapp.service.ClearCart" + + if userID < 1 { + return richerror.New(op).WithKind(richerror.KindInvalid). + WithMessage("invalid user id") + } + + return s.repo.DeleteCart(ctx, userID) +} diff --git a/shoppingbasketapp/service/validator.go b/shoppingbasketapp/service/validator.go new file mode 100644 index 00000000..8ab07773 --- /dev/null +++ b/shoppingbasketapp/service/validator.go @@ -0,0 +1,91 @@ +package service + +import ( + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +const ( + ErrValidationPositive = "must be positive" + ErrValidationInvalidInput = "invalid input" +) + +type Validate struct{} + +func NewValidate() Validate { + return Validate{} +} + +func (v Validate) ValidateAddToCart(req AddToCartRequest) error { + const op = "shoppingbasketapp.service.AddToCart" + + if err := validation.ValidateStruct(&req, + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.ProductID, validation.Required), + validation.Field(&req.Price, validation.Required, validation.Min(int64(1)).Error(ErrValidationPositive)), + validation.Field(&req.Quantity, validation.Min(int(1)).Error(ErrValidationPositive)), + validation.Field(&req.Name, validation.Required)); err != nil { + + fieldErr := make(map[string]interface{}) + vErr, ok := err.(validation.Errors) + if ok { + for key, value := range vErr { + if value != nil { + fieldErr[key] = value.Error() + } + } + } + + return richerror.New(op).WithMessage(ErrValidationInvalidInput). + WithMeta(fieldErr).WithErr(err).WithKind(richerror.KindInvalid) + } + + return nil +} + +func (v Validate) ValidateRemoveFromCart(req RemoveFromCartRequest) error { + const op = "shoppingbasketapp.service.ValidateRemoveFromCart" + + if err := validation.ValidateStruct(&req, + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.ProductID, validation.Required)); err != nil { + + fieldErrs := make(map[string]interface{}) + vErr, ok := err.(validation.Errors) + if ok { + for key, value := range vErr { + if value != nil { + fieldErrs[key] = value.Error() + } + } + } + return richerror.New(op).WithMessage(ErrValidationInvalidInput). + WithKind(richerror.KindInvalid).WithMeta(fieldErrs).WithErr(err) + } + + return nil +} + +func (v Validate) ValidateUpdateQuantity(req UpdateQuantityRequest) error { + const op = "shoppingbasketapp.service.ValidateUpdateQuantity" + + if err := validation.ValidateStruct(&req, + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.ProductID, validation.Required), + validation.Field(&req.Quantity, validation.Required, validation.Min(int(1)))); err != nil { + + fieldErrs := make(map[string]interface{}) + vErr, ok := err.(validation.Errors) + if ok { + for key, value := range vErr { + if value != nil { + fieldErrs[key] = value.Error() + } + } + } + return richerror.New(op).WithMessage(ErrValidationInvalidInput). + WithKind(richerror.KindInvalid).WithMeta(fieldErrs).WithErr(err) + } + + return nil +}