forked from ebhomengo/niki
227 lines
5.4 KiB
Go
227 lines
5.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
adapter "git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
|
"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"
|
|
"time"
|
|
)
|
|
|
|
type Cache struct {
|
|
client *adapter.Adapter
|
|
config Config
|
|
}
|
|
|
|
func NewCache(client *adapter.Adapter, cfg Config) Cache {
|
|
return Cache{client: client, config: cfg}
|
|
}
|
|
|
|
func (c Cache) cartKey(userID types.ID) string {
|
|
return fmt.Sprintf("%s:%d", c.config.CacheKartKeyPrefix, userID)
|
|
}
|
|
|
|
func (c Cache) itemKey(itemID types.ID) string {
|
|
return fmt.Sprintf("item:%d", itemID)
|
|
}
|
|
|
|
func (c Cache) kartIDsKey(userID types.ID) string {
|
|
return fmt.Sprintf("cart:%d:items", userID)
|
|
}
|
|
|
|
func (c Cache) upsertCart(ctx context.Context, cart entity.Cart) error {
|
|
cartKey := c.cartKey(cart.UserID)
|
|
cartIDsKey := c.kartIDsKey(cart.UserID)
|
|
|
|
pipe := c.client.Client().Pipeline()
|
|
pipe.HSet(ctx, cartKey, map[string]interface{}{
|
|
IDField: cart.ID,
|
|
UserIDField: cart.UserID,
|
|
StatusField: cart.Status,
|
|
TotalPriceField: cart.TotalPrice,
|
|
ExpireAtField: cart.ExpireAt,
|
|
CreatedAtField: cart.CreatedAt,
|
|
UpdatedAtField: cart.UpdatedAt,
|
|
})
|
|
|
|
pipe.Expire(ctx, cartKey, c.config.CacheTTL)
|
|
|
|
pipe.Del(ctx, cartIDsKey)
|
|
|
|
for _, i := range cart.Items {
|
|
|
|
pipe.SAdd(ctx, cartIDsKey, i.ID)
|
|
|
|
itemKey := c.itemKey(i.ID)
|
|
jsonItem, err := json.Marshal(i)
|
|
if err != nil {
|
|
return richerror.New(richerror.Op(op("cache.upsertCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
pipe.Set(ctx, itemKey, string(jsonItem), c.config.CacheTTL)
|
|
}
|
|
|
|
pipe.Expire(ctx, cartIDsKey, c.config.CacheTTL)
|
|
|
|
_, err := pipe.Exec(ctx)
|
|
if err != nil {
|
|
return richerror.New(richerror.Op(op("cache.upsertCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c Cache) getCart(ctx context.Context, userID types.ID) (entity.Cart, error) {
|
|
cartKey := c.cartKey(userID)
|
|
cartIDsKey := c.kartIDsKey(userID)
|
|
|
|
pipe := c.client.Client().Pipeline()
|
|
|
|
resCart := pipe.HGetAll(ctx, cartKey)
|
|
resIDs := pipe.SMembers(ctx, cartIDsKey)
|
|
|
|
_, err := pipe.Exec(ctx)
|
|
if err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
result := resCart.Val()
|
|
|
|
if len(result) < 1 {
|
|
return entity.Cart{}, nil
|
|
}
|
|
|
|
cart, err := parseCartFromRedis(result)
|
|
if err != nil {
|
|
return entity.Cart{}, err
|
|
}
|
|
|
|
if cart.ID < 1 {
|
|
return entity.Cart{}, nil
|
|
}
|
|
|
|
ids := resIDs.Val()
|
|
|
|
if len(ids) < 1 {
|
|
return cart, nil
|
|
}
|
|
|
|
itemPipe := c.client.Client().Pipeline()
|
|
|
|
itemResults := make([]*redis.StringCmd, len(ids))
|
|
|
|
for i, idStr := range ids {
|
|
id, _ := strconv.Atoi(idStr)
|
|
itemKey := c.itemKey(types.ID(id))
|
|
cmd := itemPipe.Get(ctx, itemKey)
|
|
itemResults[i] = cmd
|
|
}
|
|
|
|
if _, err := itemPipe.Exec(ctx); err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
for _, cmd := range itemResults {
|
|
if err := cmd.Err(); err != nil {
|
|
if err == redis.Nil {
|
|
|
|
continue
|
|
}
|
|
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
var item entity.Item
|
|
|
|
if err := json.Unmarshal([]byte(cmd.Val()), &item); err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
|
|
cart.Items = append(cart.Items, item)
|
|
}
|
|
|
|
return cart, nil
|
|
}
|
|
|
|
func parseCartFromRedis(result map[string]string) (entity.Cart, error) {
|
|
if len(result) == 0 {
|
|
return entity.Cart{}, nil
|
|
}
|
|
|
|
var cart entity.Cart
|
|
|
|
cart.Items = []entity.Item{}
|
|
|
|
for key, val := range result {
|
|
switch key {
|
|
case IDField:
|
|
id, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
cart.ID = types.ID(id)
|
|
case UserIDField:
|
|
uid, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
cart.UserID = types.ID(uid)
|
|
case StatusField:
|
|
cart.Status = entity.CartStatus(val)
|
|
case TotalPriceField:
|
|
price, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
cart.TotalPrice = price
|
|
case ExpireAtField, CreatedAtField, UpdatedAtField:
|
|
t, err := time.Parse(time.RFC3339, val)
|
|
if err != nil {
|
|
return entity.Cart{}, richerror.New(richerror.Op(op("cache.getCart"))).
|
|
WithKind(richerror.KindUnexpected).WithErr(err)
|
|
}
|
|
switch key {
|
|
case ExpireAtField:
|
|
cart.ExpireAt = t
|
|
case CreatedAtField:
|
|
cart.CreatedAt = t
|
|
case UpdatedAtField:
|
|
cart.UpdatedAt = t
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|