package sessionmanagermdl import ( "errors" "time" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/cachemdl" ) // Entry is a data to be stored against a key. type Entry struct { Data gjson.Result `json:"data,omitempty"` Expiration int64 `json:"expiration,omitempty"` ExpiredAT int64 `json:"expiredAt,omitempty"` } const ( // keys for the entry object KEY_DATA = "data" KEY_EXPIREDAT = "expiredAt" KEY_EXPIRATION = "expiration" ) var store cachemdl.Cacher var ErrSessionNotFound = errors.New("SESSION_NOT_FOUND") var ErrInvalidDataType = errors.New("INVALID_DATA_Type") // Init initializes session manager with provided cache. Subsequent calls will not have any effect after first initialization. func Init(cache cachemdl.Cacher) { if store != nil { return } store = cache } // NewEntry prepares the object required to store data in session. // // The `exp` field interprets time in seconds. Ex. For 5 seconds, set `5` func NewEntry(val gjson.Result, exp int64) Entry { duration := time.Duration(exp) * time.Second deadLine := time.Now().Add(duration).Unix() return Entry{ Data: val, Expiration: exp, ExpiredAT: deadLine, } } // NewRedisEntry prepares the entry for redis cache. This is required because redis accepts a byte array. func NewRedisEntry(entry Entry) string { var data string // the error can be ignored here as we have valid keys and data values data, _ = sjson.Set(data, KEY_DATA, entry.Data.Value()) data, _ = sjson.Set(data, KEY_EXPIRATION, entry.Expiration) data, _ = sjson.Set(data, KEY_EXPIREDAT, entry.ExpiredAT) return data } // ToObject returns an cache entry as an object. It is better than sjson.Set() as we need to perform gjson.Parse(). func ToObject(entry Entry) map[string]interface{} { return map[string]interface{}{ KEY_DATA: entry.Data.Value(), KEY_EXPIRATION: entry.Expiration, KEY_EXPIREDAT: entry.ExpiredAT, } } // Store adds/ updates the entry against the provided key. func Store(key string, entry Entry) { duration := time.Duration(entry.Expiration) * time.Second // if session manager uses redis cache, the data field (gjson.Result) is saved as is. // This adds irrelevant fields in redis cache and we get them on retrieve operation. // The following operation needs to be performed so that the data is marshaled correctly. Redis only accepts []byte{}. if store.Type() == cachemdl.TypeRedisCache { store.SetWithExpiration(key, NewRedisEntry(entry), duration) return } store.SetWithExpiration(key, entry, duration) } // Retrieve returns the entry present against the provided key. If a key is not available or data stored is not of type gjson.Result, a non nil error will be returned func Retrieve(key string) (Entry, error) { data, ok := store.Get(key) if !ok { return Entry{}, ErrSessionNotFound } switch v := data.(type) { case string: // for result from redis cache res := gjson.Parse(v) return Entry{ Data: res.Get(KEY_DATA), Expiration: res.Get(KEY_EXPIRATION).Int(), ExpiredAT: res.Get(KEY_EXPIREDAT).Int(), }, nil case Entry: // for result from fastcache return v, nil default: return Entry{}, ErrInvalidDataType } } // RetrieveAndExtend returns the entry and extends the entry expiration by provided `SECONDS`, only if remaining time < extendBy. // If extendBy < 0, it is same as Retrieve function. func RetrieveAndExtend(key string, extendBy int64) (Entry, error) { entry, err := Retrieve(key) if err != nil { return Entry{}, err } if extendBy > 0 { timeRemaining := entry.ExpiredAT - time.Now().Unix() if timeRemaining < extendBy { // update with new expiratin entry.ExpiredAT = time.Now().Add(time.Second * time.Duration(extendBy)).Unix() Store(key, entry) } } return entry, nil } // RetrieveAndDelete deletes the entry after first retrieval func RetrieveAndDelete(key string) (Entry, error) { entry, err := Retrieve(key) if err != nil { return Entry{}, err } store.Delete(key) return entry, nil } // Delete removes the entry from session manager. If the key is not present, error `ErrSessionNotFound` will be thrown. Caller can ignore error if this is acceptable. func Delete(key string) error { _, ok := store.Get(key) if !ok { return ErrSessionNotFound } store.Delete(key) return nil }