sessionmanager.go 4.5 KiB
Newer Older
package sessionmanagermdl

import (
	"errors"
	"time"

	"github.com/tidwall/gjson"
	"github.com/tidwall/sjson"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/cachemdl"
)

Akshay Bharambe's avatar
Akshay Bharambe committed
// 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"`
}

Akshay Bharambe's avatar
Akshay Bharambe committed
	// 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")

Akshay Bharambe's avatar
Akshay Bharambe committed
// 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
}

Akshay Bharambe's avatar
Akshay Bharambe committed
// 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
Akshay Bharambe's avatar
Akshay Bharambe committed

Akshay Bharambe's avatar
Akshay Bharambe committed
	// 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.
Akshay Bharambe's avatar
Akshay Bharambe committed
	// 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()
			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
}

Akshay Bharambe's avatar
Akshay Bharambe committed
// 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
}