flamingo.me/flamingo-commerce/v3@v3.11.0/cart/application/cartCache.go (about)

     1  package application
     2  
     3  import (
     4  	"context"
     5  	"encoding/gob"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"go.opencensus.io/trace"
    11  
    12  	"flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    13  	"flamingo.me/flamingo/v3/core/auth"
    14  	"flamingo.me/flamingo/v3/framework/flamingo"
    15  	"flamingo.me/flamingo/v3/framework/web"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  //go:generate go run github.com/vektra/mockery/v2@v2.42.3 --name CartCache --case snake
    20  
    21  type (
    22  	// CartCache describes a cart caches methods
    23  	CartCache interface {
    24  		GetCart(context.Context, *web.Session, CartCacheIdentifier) (*cart.Cart, error)
    25  		CacheCart(context.Context, *web.Session, CartCacheIdentifier, *cart.Cart) error
    26  		Invalidate(context.Context, *web.Session, CartCacheIdentifier) error
    27  		Delete(context.Context, *web.Session, CartCacheIdentifier) error
    28  		DeleteAll(context.Context, *web.Session) error
    29  		BuildIdentifier(context.Context, *web.Session) (CartCacheIdentifier, error)
    30  	}
    31  
    32  	// CartCacheIdentifier identifies Cart Caches
    33  	CartCacheIdentifier struct {
    34  		GuestCartID    string
    35  		IsCustomerCart bool
    36  		CustomerID     string
    37  	}
    38  
    39  	// CartSessionCache defines a Cart Cache
    40  	CartSessionCache struct {
    41  		logger             flamingo.Logger
    42  		webIdentityService *auth.WebIdentityService
    43  		lifetimeSeconds    float64
    44  	}
    45  
    46  	// CachedCartEntry defines a single Cart Cache Entry
    47  	CachedCartEntry struct {
    48  		IsInvalid bool
    49  		Entry     cart.Cart
    50  		ExpiresOn time.Time
    51  	}
    52  )
    53  
    54  const (
    55  	// CartSessionCacheCacheKeyPrefix is a string prefix for Cart Cache Keys
    56  	CartSessionCacheCacheKeyPrefix = "cart.sessioncache."
    57  )
    58  
    59  var (
    60  	_ CartCache = (*CartSessionCache)(nil)
    61  	// ErrCacheIsInvalid sets generalized invalid Cache Error
    62  	ErrCacheIsInvalid = errors.New("cache is invalid")
    63  	// ErrNoCacheEntry - used if cache is not found
    64  	ErrNoCacheEntry = errors.New("cache entry not found")
    65  )
    66  
    67  func init() {
    68  	gob.Register(CachedCartEntry{})
    69  }
    70  
    71  // CacheKey creates a Cache Key Identifier string
    72  func (ci *CartCacheIdentifier) CacheKey() string {
    73  	return fmt.Sprintf(
    74  		"cart_%v_%v",
    75  		ci.CustomerID,
    76  		ci.GuestCartID,
    77  	)
    78  }
    79  
    80  // BuildIdentifierFromCart creates a Cache Identifier from Cart Data
    81  // Deprecated: use BuildIdentifier function of concrete implementation
    82  func BuildIdentifierFromCart(cart *cart.Cart) (*CartCacheIdentifier, error) {
    83  	if cart == nil {
    84  		return nil, errors.New("no cart")
    85  	}
    86  
    87  	if cart.BelongsToAuthenticatedUser {
    88  		return &CartCacheIdentifier{
    89  			CustomerID:     cart.AuthenticatedUserID,
    90  			IsCustomerCart: true,
    91  		}, nil
    92  	}
    93  
    94  	return &CartCacheIdentifier{
    95  		GuestCartID:    cart.ID,
    96  		CustomerID:     cart.AuthenticatedUserID,
    97  		IsCustomerCart: false,
    98  	}, nil
    99  }
   100  
   101  // Inject the dependencies
   102  func (cs *CartSessionCache) Inject(
   103  	logger flamingo.Logger,
   104  	webIdentityService *auth.WebIdentityService,
   105  	config *struct {
   106  		LifetimeSeconds float64 `inject:"config:commerce.cart.cacheLifetime"` // in seconds
   107  	},
   108  ) {
   109  	cs.webIdentityService = webIdentityService
   110  	cs.logger = logger.WithField(flamingo.LogKeyCategory, "CartSessionCache").WithField(flamingo.LogKeyModule, "cart")
   111  
   112  	if config != nil {
   113  		cs.lifetimeSeconds = config.LifetimeSeconds
   114  	}
   115  }
   116  
   117  // BuildIdentifier creates a CartCacheIdentifier based on the login state
   118  func (cs *CartSessionCache) BuildIdentifier(ctx context.Context, session *web.Session) (CartCacheIdentifier, error) {
   119  	ctx, span := trace.StartSpan(ctx, "cart/CartSessionCache/BuildIdentifier")
   120  	defer span.End()
   121  
   122  	identity := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
   123  	if identity != nil {
   124  		return CartCacheIdentifier{
   125  			CustomerID:     identity.Subject(),
   126  			IsCustomerCart: true,
   127  		}, nil
   128  	}
   129  
   130  	guestCartID, ok := session.Load(GuestCartSessionKey)
   131  	if !ok {
   132  		return CartCacheIdentifier{}, errors.New("Fatal - ShouldHaveGuestCart returned true but got no GuestCartSessionKey?")
   133  	}
   134  
   135  	guestCartIDString, ok := guestCartID.(string)
   136  	if !ok {
   137  		return CartCacheIdentifier{}, errors.New("Fatal - ShouldHaveGuestCart returned true but got no GuestCartSessionKey string")
   138  	}
   139  
   140  	return CartCacheIdentifier{
   141  		GuestCartID: guestCartIDString,
   142  	}, nil
   143  }
   144  
   145  // GetCart fetches a Cart from the Cache
   146  func (cs *CartSessionCache) GetCart(ctx context.Context, session *web.Session, id CartCacheIdentifier) (*cart.Cart, error) {
   147  	ctx, span := trace.StartSpan(ctx, "cart/CartSessionCache/GetCart")
   148  	defer span.End()
   149  
   150  	if cache, ok := session.Load(CartSessionCacheCacheKeyPrefix + id.CacheKey()); ok {
   151  		if cachedCartsEntry, ok := cache.(CachedCartEntry); ok {
   152  			cs.logger.WithContext(ctx).Debugf("Found cached cart: %v  InValid: %v", id.CacheKey(), cachedCartsEntry.IsInvalid)
   153  			if cachedCartsEntry.IsInvalid {
   154  				return &cachedCartsEntry.Entry, ErrCacheIsInvalid
   155  			}
   156  
   157  			if time.Now().After(cachedCartsEntry.ExpiresOn) {
   158  				err := cs.Invalidate(ctx, session, id)
   159  				if err != nil {
   160  					return nil, err
   161  				}
   162  
   163  				return nil, ErrCacheIsInvalid
   164  			}
   165  
   166  			return &cachedCartsEntry.Entry, nil
   167  		}
   168  		cs.logger.WithContext(ctx).Error("Cannot Cast Cache Entry %v", id.CacheKey())
   169  
   170  		return nil, errors.New("cart cache contains invalid data at cache key")
   171  	}
   172  	cs.logger.WithContext(ctx).Debug("Did not Found cached cart %v", id.CacheKey())
   173  
   174  	return nil, ErrNoCacheEntry
   175  }
   176  
   177  // CacheCart adds a Cart to the Cache
   178  func (cs *CartSessionCache) CacheCart(ctx context.Context, session *web.Session, id CartCacheIdentifier, cartForCache *cart.Cart) error {
   179  	ctx, span := trace.StartSpan(ctx, "cart/CartSessionCache/CacheCart")
   180  	defer span.End()
   181  
   182  	if cartForCache == nil {
   183  		return errors.New("no cart given to cache")
   184  	}
   185  	entry := CachedCartEntry{
   186  		Entry:     *cartForCache,
   187  		ExpiresOn: time.Now().Add(time.Duration(cs.lifetimeSeconds * float64(time.Second))),
   188  	}
   189  
   190  	cs.logger.WithContext(ctx).Debug("Caching cart %v", id.CacheKey())
   191  	session.Store(CartSessionCacheCacheKeyPrefix+id.CacheKey(), entry)
   192  	return nil
   193  }
   194  
   195  // Invalidate a Cache Entry
   196  func (cs *CartSessionCache) Invalidate(ctx context.Context, session *web.Session, id CartCacheIdentifier) error {
   197  	_, span := trace.StartSpan(ctx, "cart/CartSessionCache/Invalidate")
   198  	defer span.End()
   199  
   200  	if cache, ok := session.Load(CartSessionCacheCacheKeyPrefix + id.CacheKey()); ok {
   201  		if cachedCartsEntry, ok := cache.(CachedCartEntry); ok {
   202  			cachedCartsEntry.IsInvalid = true
   203  			session.Store(CartSessionCacheCacheKeyPrefix+id.CacheKey(), cachedCartsEntry)
   204  
   205  			return nil
   206  		}
   207  	}
   208  
   209  	return ErrNoCacheEntry
   210  }
   211  
   212  // Delete a Cache entry
   213  func (cs *CartSessionCache) Delete(ctx context.Context, session *web.Session, id CartCacheIdentifier) error {
   214  	_, span := trace.StartSpan(ctx, "cart/CartSessionCache/Delete")
   215  	defer span.End()
   216  
   217  	if _, ok := session.Load(CartSessionCacheCacheKeyPrefix + id.CacheKey()); ok {
   218  		session.Delete(CartSessionCacheCacheKeyPrefix + id.CacheKey())
   219  
   220  		// ok deleted something
   221  		return nil
   222  	}
   223  
   224  	return ErrNoCacheEntry
   225  }
   226  
   227  // DeleteAll empties the Cache
   228  func (cs *CartSessionCache) DeleteAll(ctx context.Context, session *web.Session) error {
   229  	_, span := trace.StartSpan(ctx, "cart/CartSessionCache/DeleteAll")
   230  	defer span.End()
   231  
   232  	deleted := false
   233  	for _, k := range session.Keys() {
   234  		if stringKey, ok := k.(string); ok {
   235  			if strings.Contains(stringKey, CartSessionCacheCacheKeyPrefix) {
   236  				session.Delete(k)
   237  				deleted = true
   238  			}
   239  		}
   240  	}
   241  
   242  	if deleted {
   243  		// successfully deleted something
   244  		return nil
   245  	}
   246  
   247  	return ErrNoCacheEntry
   248  }