Newer
Older
package sessionmanagermdl
import (
"errors"
"time"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/cachemdl"
)
type Entry struct {
Data gjson.Result `json:"data,omitempty"`
Expiration int64 `json:"expiration,omitempty"`
ExpiredAT int64 `json:"expiredAt,omitempty"`
}
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
return Entry{}, ErrInvalidDataType
}
}
// RetrieveAll returns all entries present in memory. **Not for production use. May add performance costs**
func RetrieveAll() map[string]interface{} {
return store.GetAll()
}
// 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()
entry.Expiration = extendBy
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.