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 }