2021-05-02 12:29:43 +00:00
|
|
|
package loyalty
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-05-02 17:39:46 +00:00
|
|
|
"errors"
|
2021-05-12 17:19:09 +00:00
|
|
|
"sync"
|
2021-05-02 12:29:43 +00:00
|
|
|
|
|
|
|
"github.com/dgraph-io/badger/v3"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
2021-05-07 16:36:23 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
2021-05-10 23:54:55 +00:00
|
|
|
kv "github.com/strimertul/kilovolt/v3"
|
2021-05-10 21:09:15 +00:00
|
|
|
"github.com/strimertul/strimertul/database"
|
2021-05-02 12:29:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Manager struct {
|
|
|
|
Config Config
|
|
|
|
Rewards RewardStorage
|
|
|
|
Goals GoalStorage
|
|
|
|
RedeemQueue RedeemQueueStorage
|
|
|
|
|
2021-05-12 17:19:09 +00:00
|
|
|
points PointStorage
|
|
|
|
pointsmu sync.Mutex
|
|
|
|
hub *kv.Hub
|
|
|
|
logger logrus.FieldLogger
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
|
2021-05-10 21:09:15 +00:00
|
|
|
func NewManager(db *database.DB, hub *kv.Hub, log logrus.FieldLogger) (*Manager, error) {
|
2021-05-02 19:33:37 +00:00
|
|
|
if log == nil {
|
|
|
|
log = logrus.New()
|
|
|
|
}
|
|
|
|
|
2021-05-02 12:29:43 +00:00
|
|
|
manager := &Manager{
|
2021-05-12 17:19:09 +00:00
|
|
|
logger: log,
|
|
|
|
hub: hub,
|
|
|
|
pointsmu: sync.Mutex{},
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
// Ger data from DB
|
2021-05-10 21:09:15 +00:00
|
|
|
if err := db.GetJSON(ConfigKey, &manager.Config); err != nil {
|
2021-05-02 12:29:43 +00:00
|
|
|
if err == badger.ErrKeyNotFound {
|
2021-05-02 19:33:37 +00:00
|
|
|
log.Warn("missing configuration for loyalty (but it's enabled). Please make sure to set it up properly!")
|
2021-05-02 12:29:43 +00:00
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2021-05-12 17:19:09 +00:00
|
|
|
if err := db.GetJSON(PointsKey, &manager.points); err != nil {
|
2021-05-02 12:29:43 +00:00
|
|
|
if err == badger.ErrKeyNotFound {
|
2021-05-12 17:19:09 +00:00
|
|
|
manager.points = make(PointStorage)
|
2021-05-02 12:29:43 +00:00
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2021-05-10 21:09:15 +00:00
|
|
|
if err := db.GetJSON(RewardsKey, &manager.Rewards); err != nil {
|
2021-05-02 12:29:43 +00:00
|
|
|
if err != badger.ErrKeyNotFound {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2021-05-10 21:09:15 +00:00
|
|
|
if err := db.GetJSON(GoalsKey, &manager.Goals); err != nil {
|
2021-05-02 12:29:43 +00:00
|
|
|
if err != badger.ErrKeyNotFound {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2021-05-10 21:09:15 +00:00
|
|
|
if err := db.GetJSON(QueueKey, &manager.RedeemQueue); err != nil {
|
2021-05-02 12:29:43 +00:00
|
|
|
if err != badger.ErrKeyNotFound {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe for changes
|
|
|
|
go func() {
|
2021-05-10 21:09:15 +00:00
|
|
|
db.Subscribe(context.Background(), manager.update, "loyalty/")
|
2021-05-02 12:29:43 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
return manager, nil
|
|
|
|
}
|
|
|
|
|
2021-05-10 21:09:15 +00:00
|
|
|
func (m *Manager) update(kvs []database.ModifiedKV) error {
|
|
|
|
for _, kv := range kvs {
|
2021-05-02 12:29:43 +00:00
|
|
|
var err error
|
|
|
|
switch string(kv.Key) {
|
|
|
|
case ConfigKey:
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.Config)
|
2021-05-02 12:29:43 +00:00
|
|
|
case PointsKey:
|
2021-05-12 17:19:09 +00:00
|
|
|
m.pointsmu.Lock()
|
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.points)
|
|
|
|
m.pointsmu.Unlock()
|
2021-05-02 12:29:43 +00:00
|
|
|
case GoalsKey:
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.Goals)
|
2021-05-02 12:29:43 +00:00
|
|
|
case RewardsKey:
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.Rewards)
|
2021-05-02 12:29:43 +00:00
|
|
|
case QueueKey:
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.RedeemQueue)
|
2021-05-02 17:39:46 +00:00
|
|
|
case CreateRedeemRPC:
|
|
|
|
var redeem Redeem
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &redeem)
|
2021-05-02 17:39:46 +00:00
|
|
|
if err == nil {
|
|
|
|
err = m.AddRedeem(redeem)
|
|
|
|
}
|
|
|
|
case RemoveRedeemRPC:
|
|
|
|
var redeem Redeem
|
2021-05-10 21:09:15 +00:00
|
|
|
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &redeem)
|
2021-05-02 17:39:46 +00:00
|
|
|
if err == nil {
|
|
|
|
err = m.RemoveRedeem(redeem)
|
|
|
|
}
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2021-05-02 19:33:37 +00:00
|
|
|
m.logger.WithFields(logrus.Fields{
|
|
|
|
"key": string(kv.Key),
|
|
|
|
"error": err.Error(),
|
|
|
|
}).Error("subscribe error: invalid JSON received on key")
|
2021-05-02 12:29:43 +00:00
|
|
|
} else {
|
2021-05-02 19:33:37 +00:00
|
|
|
m.logger.WithField("key", string(kv.Key)).Debug("updated key")
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SavePoints() error {
|
2021-05-12 17:19:09 +00:00
|
|
|
m.pointsmu.Lock()
|
|
|
|
defer m.pointsmu.Unlock()
|
|
|
|
data, _ := jsoniter.ConfigFastest.Marshal(m.points)
|
2021-05-02 12:29:43 +00:00
|
|
|
return m.hub.WriteKey(PointsKey, string(data))
|
|
|
|
}
|
|
|
|
|
2021-05-12 17:19:09 +00:00
|
|
|
func (m *Manager) GetPoints(user string) int64 {
|
|
|
|
m.pointsmu.Lock()
|
|
|
|
defer m.pointsmu.Unlock()
|
|
|
|
points, ok := m.points[user]
|
|
|
|
if ok {
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SetPoints(user string, points int64) {
|
|
|
|
m.pointsmu.Lock()
|
|
|
|
defer m.pointsmu.Unlock()
|
|
|
|
m.points[user] = points
|
|
|
|
}
|
|
|
|
|
2021-05-02 12:29:43 +00:00
|
|
|
func (m *Manager) GivePoints(pointsToGive map[string]int64) error {
|
|
|
|
// Add points to each user
|
|
|
|
for user, points := range pointsToGive {
|
2021-05-12 17:19:09 +00:00
|
|
|
balance := m.GetPoints(user)
|
|
|
|
m.SetPoints(user, balance+points)
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save points
|
|
|
|
return m.SavePoints()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) TakePoints(pointsToTake map[string]int64) error {
|
|
|
|
// Add points to each user
|
|
|
|
for user, points := range pointsToTake {
|
2021-05-12 17:19:09 +00:00
|
|
|
balance := m.GetPoints(user)
|
|
|
|
m.SetPoints(user, balance-points)
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save points
|
|
|
|
return m.SavePoints()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SaveQueue() error {
|
|
|
|
data, _ := jsoniter.ConfigFastest.Marshal(m.RedeemQueue)
|
|
|
|
return m.hub.WriteKey(QueueKey, string(data))
|
|
|
|
}
|
|
|
|
|
2021-05-02 17:39:46 +00:00
|
|
|
func (m *Manager) AddRedeem(redeem Redeem) error {
|
|
|
|
// Add to local list
|
|
|
|
m.RedeemQueue = append(m.RedeemQueue, redeem)
|
|
|
|
|
|
|
|
// Send redeem event
|
|
|
|
data, _ := jsoniter.ConfigFastest.Marshal(redeem)
|
|
|
|
m.hub.WriteKey(RedeemEvent, string(data))
|
2021-05-02 12:29:43 +00:00
|
|
|
|
|
|
|
// Save points
|
|
|
|
return m.SaveQueue()
|
|
|
|
}
|
2021-05-02 17:39:46 +00:00
|
|
|
|
|
|
|
func (m *Manager) RemoveRedeem(redeem Redeem) error {
|
|
|
|
for index, queued := range m.RedeemQueue {
|
|
|
|
if queued.When == redeem.When && queued.Username == redeem.Username && queued.Reward.ID == redeem.Reward.ID {
|
|
|
|
// Remove redemption from list
|
|
|
|
m.RedeemQueue = append(m.RedeemQueue[:index], m.RedeemQueue[index+1:]...)
|
|
|
|
|
|
|
|
// Save points
|
|
|
|
return m.SaveQueue()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New("redeem not found")
|
|
|
|
}
|
2021-05-12 17:19:09 +00:00
|
|
|
|
|
|
|
func (m *Manager) SaveGoals() error {
|
|
|
|
data, _ := jsoniter.ConfigFastest.Marshal(m.Goals)
|
|
|
|
return m.hub.WriteKey(GoalsKey, string(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) ContributeGoal(goal *Goal, user string, points int64) error {
|
|
|
|
goal.Contributed += points
|
|
|
|
goal.Contributors[user] += points
|
|
|
|
return m.SaveGoals()
|
|
|
|
}
|