added repository layer

This commit is contained in:
mzfarshad 2026-04-12 21:32:23 +03:30
parent 3d5e4e473b
commit c1ed70cf66
6 changed files with 291 additions and 9 deletions

View File

@ -1 +1,11 @@
package shoppingbasketapp
import (
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
"git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository"
)
type Config struct {
Redis redis.Config `koanf:"redis" json:"redis"`
Repo repository.Repo `koanf:"repo" json:"repo"`
}

View File

@ -0,0 +1,272 @@
package repository
import (
"context"
"encoding/json"
"fmt"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart"
"git.gocasts.ir/ebhomengo/niki/types"
"github.com/redis/go-redis/v9"
"strconv"
"strings"
"time"
)
const (
FieldNumber = 5
UserIDField = "user_id"
CreatedAtField = "created_at"
ExpireAtField = "expire_at"
TotalPriceField = "total_price"
)
type Config struct {
KartKeyPrefix string `koanf:"kart_key_prefix"`
TTL time.Duration `koanf:"ttl"`
}
type Repo struct {
client *redis.Client
config Config
}
func New(client *redis.Client, cfg Config) Repo {
return Repo{client: client, config: cfg}
}
func (r Repo) cartKey(userID types.ID) string {
return r.config.KartKeyPrefix + fmt.Sprintf("%d", userID)
}
func (r Repo) itemKey(productID types.ID) string {
return fmt.Sprintf("item:%d", productID)
}
func (r Repo) AddItem(ctx context.Context, userID types.ID, item cart.Item) error {
const op = "shoppingbasketapp.repository.AddItem"
cartKey := r.cartKey(userID)
itemKey := r.itemKey(item.ProductID)
now := time.Now().UnixNano()
itemJson, _ := json.Marshal(item)
exists, _ := r.client.Exists(ctx, cartKey).Result()
if exists == 0 {
r.client.HSet(ctx, cartKey, map[string]interface{}{
UserIDField: userID,
itemKey: string(itemJson),
TotalPriceField: item.Price * types.Price(item.Quantity),
CreatedAtField: now,
ExpireAtField: now + r.config.TTL.Nanoseconds(),
})
} else {
existsItem, _ := r.client.HGet(ctx, cartKey, itemKey).Result()
if existsItem != "" {
var i cart.Item
if err := json.Unmarshal([]byte(existsItem), &i); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
item.Quantity += i.Quantity
itemJson, _ = json.Marshal(item)
}
r.client.HSet(ctx, cartKey, itemKey, string(itemJson))
r.client.HSet(ctx, cartKey, ExpireAtField, now+r.config.TTL.Nanoseconds())
if err := r.updateTotalPrice(ctx, cartKey); err != nil {
return err
}
}
r.client.Expire(ctx, cartKey, r.config.TTL)
return nil
}
func parsInt(s string) int64 {
i, _ := strconv.ParseInt(s, 10, 64)
return i
}
func (r Repo) GetCart(ctx context.Context, userID types.ID) (cart.Cart, error) {
const op = "shoppingbasketapp.repository.GetCart"
cartKey := r.cartKey(userID)
exists, err := r.client.Exists(ctx, cartKey).Result()
if err != nil {
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
if exists == 0 {
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket")
}
allCart, err := r.client.HGetAll(ctx, cartKey).Result()
if err != nil {
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
c := cart.Cart{Items: []cart.Item{}}
for field, value := range allCart {
if strings.HasPrefix(field, "item:") {
var i cart.Item
if err := json.Unmarshal([]byte(value), &i); err != nil {
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
c.Items = append(c.Items, i)
continue
}
switch field {
case UserIDField:
c.UserID = types.ID(parsInt(value))
case TotalPriceField:
c.TotalPrice = types.Price(parsInt(value))
case CreatedAtField:
c.CreatedAt = parsInt(value)
case ExpireAtField:
c.ExpireAt = parsInt(value)
}
}
return c, nil
}
func (r Repo) DeleteItem(ctx context.Context, userID, productID types.ID) error {
const op = "shoppingbasketapp.repository.DeleteItem"
cartKey := r.cartKey(userID)
itemKey := r.itemKey(productID)
if err := r.existsCart(ctx, cartKey); err != nil {
return err
}
if err := r.existsItem(ctx, cartKey, itemKey); err != nil {
return err
}
if err := r.client.HDel(ctx, cartKey, itemKey).Err(); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
num, err := r.client.HLen(ctx, cartKey).Result()
if err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
if num < FieldNumber {
return r.DeleteCart(ctx, userID)
}
return r.updateTotalPrice(ctx, cartKey)
}
func (r Repo) UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error {
const op = "shoppingbasketapp.repository.UpdateQuantity"
cartKey := r.cartKey(userID)
itemKey := r.itemKey(productID)
if err := r.existsCart(ctx, cartKey); err != nil {
return err
}
if err := r.existsItem(ctx, cartKey, itemKey); err != nil {
return err
}
data, err := r.client.HGet(ctx, cartKey, itemKey).Result()
if err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
var item cart.Item
if err := json.Unmarshal([]byte(data), &item); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
item.Quantity = quantity
j, _ := json.Marshal(item)
if err := r.client.HSet(ctx, cartKey, itemKey, string(j)).Err(); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
return r.updateTotalPrice(ctx, cartKey)
}
func (r Repo) DeleteCart(ctx context.Context, userID types.ID) error {
const op = "shoppingbasketapp.repository.DeleteCart"
cartKey := r.cartKey(userID)
if err := r.existsCart(ctx, cartKey); err != nil {
return err
}
if err := r.client.Del(ctx, cartKey).Err(); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
return nil
}
func (r Repo) updateTotalPrice(ctx context.Context, cartKey string) error {
const op = "shoppingbasketapp.repository.updateTotalPrice"
allFields, err := r.client.HGetAll(ctx, cartKey).Result()
if err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
var total types.Price
for field, value := range allFields {
if strings.HasPrefix(field, "item:") {
var item cart.Item
if err := json.Unmarshal([]byte(value), &item); err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
total += item.Price * types.Price(item.Quantity)
}
}
return r.client.HSet(ctx, cartKey, TotalPriceField, int64(total)).Err()
}
func (r Repo) existsCart(ctx context.Context, cartKey string) error {
const op = "shoppingbasketapp.repository.existsCart"
exists, err := r.client.Exists(ctx, cartKey).Result()
if err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
if exists == 0 {
return richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket")
}
return nil
}
func (r Repo) existsItem(ctx context.Context, cartKey, itemKey string) error {
const op = "shoppingbasketapp.repository.existsCart"
exists, err := r.client.HExists(ctx, cartKey, itemKey).Result()
if err != nil {
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
}
if !exists {
return richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found product form shopping basket")
}
return nil
}

View File

@ -1,10 +1,10 @@
package service
package cart
import (
"git.gocasts.ir/ebhomengo/niki/types"
)
type CartItem struct {
type Item struct {
ProductID types.ID
Quantity int
Price types.Price
@ -14,7 +14,7 @@ type CartItem struct {
type Cart struct {
UserID types.ID
Items []CartItem
Items []Item
TotalPrice types.Price
ExpireAt int64
CreatedAt int64

View File

@ -1,4 +1,4 @@
package service
package cart
import "git.gocasts.ir/ebhomengo/niki/types"
@ -12,7 +12,7 @@ type AddToCartRequest struct {
type GetCartResponse struct {
UserID types.ID `json:"user_id"`
Items []CartItem `json:"items"`
Items []Item `json:"items"`
TotalPrice types.Price `json:"total_price"`
CreatedAt int64 `json:"created_at"`
ExpireAt int64 `json:"expire_at"`

View File

@ -1,4 +1,4 @@
package service
package cart
import (
"context"
@ -8,7 +8,7 @@ import (
)
type Repository interface {
AddItem(ctx context.Context, userID types.ID, cart CartItem) error
AddItem(ctx context.Context, userID types.ID, item Item) 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
@ -31,7 +31,7 @@ func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error {
return err
}
return s.repo.AddItem(ctx, req.UserID, CartItem{
return s.repo.AddItem(ctx, req.UserID, Item{
ProductID: req.ProductID,
Quantity: req.Quantity,
Price: req.Price,

View File

@ -1,4 +1,4 @@
package service
package cart
import (
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"