forked from ebhomengo/niki
added adapter to repository
This commit is contained in:
parent
6ca9fd1645
commit
d400991b13
|
|
@ -0,0 +1,123 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity"
|
||||
"git.gocasts.ir/ebhomengo/niki/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CacheKartKeyPrefix string `koanf:"cache_kart_key_prefix"`
|
||||
CacheTTL time.Duration `koanf:"cache_ttl"`
|
||||
|
||||
MysqlTTL time.Duration `koanf:"mysql_ttl"`
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
db DB
|
||||
cache Cache
|
||||
}
|
||||
|
||||
func New(db DB, cache Cache) Repo {
|
||||
return Repo{db: db, cache: cache}
|
||||
}
|
||||
|
||||
func op(s string) string {
|
||||
return fmt.Sprintf("shoppingbasketapp-repository-", s)
|
||||
}
|
||||
|
||||
func (r Repo) AddItem(ctx context.Context, userID types.ID, item entity.Item) error {
|
||||
if err := r.db.addToBasket(ctx, userID, item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cart, err := r.db.getCart(ctx, nil, FindCartByUserIDQuery, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.cache.upsertCart(ctx, cart)
|
||||
}
|
||||
|
||||
func (r Repo) GetCart(ctx context.Context, userID types.ID) (entity.Cart, error) {
|
||||
cart, err := r.cache.getCart(ctx, userID)
|
||||
if err != nil {
|
||||
return entity.Cart{}, err
|
||||
}
|
||||
|
||||
if cart.ID < 1 || len(cart.Items) < 1 {
|
||||
c, err := r.db.getCart(ctx, nil, FindCartByUserIDQuery, userID)
|
||||
if err != nil {
|
||||
return entity.Cart{}, err
|
||||
}
|
||||
|
||||
if c.ID < 1 {
|
||||
return entity.Cart{}, nil
|
||||
}
|
||||
|
||||
if err := r.cache.upsertCart(ctx, c); err != nil {
|
||||
return entity.Cart{}, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return cart, nil
|
||||
}
|
||||
|
||||
func (r Repo) UpdateQuantity(ctx context.Context, cartID, itemID types.ID, quantity int) error {
|
||||
|
||||
cart, err := r.db.getCart(ctx, nil, FindCartByIDQuery, cartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, item := range cart.Items {
|
||||
if item.ID == itemID {
|
||||
cart.Items[i].Quantity = quantity
|
||||
item.Quantity = quantity
|
||||
if err := r.db.updateQuantity(ctx, nil, item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r.cache.upsertCart(ctx, cart)
|
||||
}
|
||||
|
||||
func (r Repo) UpdateStatus(ctx context.Context, cartID types.ID, status entity.CartStatus) error {
|
||||
if err := r.db.updateCartStatus(ctx, nil, cartID, status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cart, err := r.db.getCart(ctx, nil, FindCartByIDQuery, cartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.cache.upsertCart(ctx, cart)
|
||||
}
|
||||
|
||||
func (r Repo) DeleteItem(ctx context.Context, cartID, itemID types.ID) error {
|
||||
if err := r.db.deleteItem(ctx, nil, cartID, itemID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := r.db.getCart(ctx, nil, FindCartByIDQuery, cartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.cache.upsertCart(ctx, c)
|
||||
}
|
||||
|
||||
func (r Repo) DeleteCart(ctx context.Context, cartID, userID types.ID) error {
|
||||
|
||||
if err := r.db.deleteCart(ctx, cartID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.cache.deleteCart(ctx, userID)
|
||||
}
|
||||
|
|
@ -207,3 +207,20 @@ func parseCartFromRedis(result map[string]string) (entity.Cart, error) {
|
|||
|
||||
return cart, nil
|
||||
}
|
||||
|
||||
func (c Cache) deleteCart(ctx context.Context, userID types.ID) error {
|
||||
cartKey := c.cartKey(userID)
|
||||
cartIDsKey := c.kartIDsKey(userID)
|
||||
pipe := c.client.Client().Pipeline()
|
||||
|
||||
pipe.Del(ctx, cartKey)
|
||||
pipe.Del(ctx, cartIDsKey)
|
||||
|
||||
_, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return richerror.New(richerror.Op(op("cache-deleteCart"))).
|
||||
WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,262 +0,0 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
"git.gocasts.ir/ebhomengo/niki/types"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CacheKartKeyPrefix string `koanf:"cache_kart_key_prefix"`
|
||||
CacheTTL time.Duration `koanf:"cache_ttl"`
|
||||
|
||||
MysqlTTL time.Duration `koanf:"mysql_ttl"`
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
client *redis.Client
|
||||
config Config
|
||||
}
|
||||
|
||||
func New(client *redis.Client, cfg Config) Repo {
|
||||
return Repo{client: client, config: cfg}
|
||||
}
|
||||
|
||||
func op(s string) string {
|
||||
return fmt.Sprintf("shoppingbasketapp-repository-", s)
|
||||
}
|
||||
|
||||
func (r Repo) AddItem(ctx context.Context, userID types.ID, item entity.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.CacheTTL.Nanoseconds(),
|
||||
})
|
||||
} else {
|
||||
existsItem, _ := r.client.HGet(ctx, cartKey, itemKey).Result()
|
||||
if existsItem != "" {
|
||||
var i entity.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.CacheTTL.Nanoseconds())
|
||||
if err := r.updateTotalPrice(ctx, cartKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.client.Expire(ctx, cartKey, r.config.CacheTTL)
|
||||
|
||||
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) (entity.Cart, error) {
|
||||
const op = "shoppingbasketapp.repository.GetCart"
|
||||
cartKey := r.cartKey(userID)
|
||||
|
||||
exists, err := r.client.Exists(ctx, cartKey).Result()
|
||||
if err != nil {
|
||||
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
if exists == 0 {
|
||||
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket")
|
||||
}
|
||||
|
||||
allCart, err := r.client.HGetAll(ctx, cartKey).Result()
|
||||
if err != nil {
|
||||
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
c := entity.Cart{Items: []entity.Item{}}
|
||||
|
||||
for field, value := range allCart {
|
||||
if strings.HasPrefix(field, "item:") {
|
||||
var i entity.Item
|
||||
if err := json.Unmarshal([]byte(value), &i); err != nil {
|
||||
return entity.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 entity.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 entity.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.existsItem"
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ import "errors"
|
|||
const (
|
||||
FindCartByUserIDQuery = `SELECT id, user_id, total_price, status, expire_at, created_at
|
||||
FROM carts
|
||||
WHERE user_id = ? AND status = 'active' AND deleted_at IS NULL`
|
||||
WHERE user_id = ? AND status = 'active' AND deleted_at IS NULL
|
||||
ORDER BY created_at DESC`
|
||||
|
||||
FindCartByIDQuery = `SELECT id, user_id, total_price, status, expire_at, created_at
|
||||
FROM carts
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ func (d DB) updateTotalPrice(ctx context.Context, tx *sql.Tx, cartID types.ID) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d DB) deleteItem(ctx context.Context, tx *sql.Tx, cartID, productID types.ID) error {
|
||||
func (d DB) deleteItem(ctx context.Context, tx *sql.Tx, cartID, itemID types.ID) error {
|
||||
t, ok, err := d.getLocalTX(ctx, tx, "mysql-deleteItem")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -317,9 +317,9 @@ func (d DB) deleteItem(ctx context.Context, tx *sql.Tx, cartID, productID types.
|
|||
query := `UPDATE items
|
||||
SET
|
||||
deleted_at = ?
|
||||
WHERE cart_id = ? AND product_id = ? AND deleted_at IS NULL`
|
||||
WHERE cart_id = ? AND id = ? AND deleted_at IS NULL`
|
||||
deleteTime := time.Now().UTC()
|
||||
_, err = t.ExecContext(ctx, query, deleteTime, cartID, productID)
|
||||
_, err = t.ExecContext(ctx, query, deleteTime, cartID, itemID)
|
||||
if err != nil {
|
||||
return richerror.New(richerror.Op(op("mysql-deleteItem"))).
|
||||
WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
|
|
@ -340,12 +340,27 @@ func (d DB) deleteItem(ctx context.Context, tx *sql.Tx, cartID, productID types.
|
|||
}
|
||||
|
||||
func (d DB) deleteCart(ctx context.Context, cartID, userID types.ID) error {
|
||||
query := `UPDATE items
|
||||
itemQuery := `UPDATE items
|
||||
SET
|
||||
deleted_at = ?
|
||||
WHERE cart_id = ? AND user_id = ? AND deleted_at IS NULL`
|
||||
deleteTime := time.Now().UTC()
|
||||
_, err := d.db.Conn().ExecContext(ctx, query, deleteTime, cartID, userID)
|
||||
|
||||
deleteItemTime := time.Now().UTC()
|
||||
|
||||
_, err := d.db.Conn().ExecContext(ctx, itemQuery, deleteItemTime, cartID, userID)
|
||||
if err != nil {
|
||||
return richerror.New(richerror.Op(op("mysql-deleteCart"))).
|
||||
WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
cartQuery := `UPDATE carts
|
||||
SET
|
||||
deleted_at = ?
|
||||
WHERE id = ? AND user_id = ? AND deleted_at IS NULL`
|
||||
|
||||
deleteCartTime := time.Now().UTC()
|
||||
|
||||
_, err = d.db.Conn().ExecContext(ctx, cartQuery, deleteCartTime, cartID, userID)
|
||||
if err != nil {
|
||||
return richerror.New(richerror.Op(op("mysql-deleteCart"))).
|
||||
WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
|
|
@ -370,7 +385,7 @@ func (d DB) updateCartStatus(ctx context.Context, tx *sql.Tx, cartID types.ID, s
|
|||
query := `UPDATE carts
|
||||
SET
|
||||
status = ?
|
||||
WHERE cart_id = ? AND deleted_at IS NULL`
|
||||
WHERE id = ? AND status = active AND deleted_at IS NULL`
|
||||
|
||||
_, err = t.ExecContext(ctx, query, status, cartID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ type Repository interface {
|
|||
AddItem(ctx context.Context, userID types.ID, item entity.Item) error
|
||||
GetCart(ctx context.Context, userID types.ID) (entity.Cart, error)
|
||||
DeleteItem(ctx context.Context, cartID, itemID types.ID) error
|
||||
UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error
|
||||
DeleteCart(ctx context.Context, cartID types.ID) error
|
||||
UpdateQuantity(ctx context.Context, cartID, itemID types.ID, quantity int) error
|
||||
DeleteCart(ctx context.Context, cartID, userID types.ID) error
|
||||
UpdateStatus(ctx context.Context, cartID types.ID, status entity.CartStatus) error
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue