577 lines
13 KiB
Go

package main
import (
"encoding/json"
"errors"
"time"
"github.com/larkox/mattermost-plugin-badges/badgesmodel"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
)
var errInvalidBadge = errors.New("invalid badge")
var errBadgeNotFound = errors.New("badge not found")
type Store interface {
// Interface
GetUserBadges(userID string) ([]*badgesmodel.UserBadge, error)
GetAllBadges() ([]*badgesmodel.AllBadgesBadge, error)
GetBadgeDetails(badgeID badgesmodel.BadgeID) (*badgesmodel.BadgeDetails, error)
// Autocomplete
GetRawBadges() ([]*badgesmodel.Badge, error)
GetRawTypes() (badgesmodel.BadgeTypeList, error)
// API
AddBadge(badge *badgesmodel.Badge) (*badgesmodel.Badge, error)
GrantBadge(badgeID badgesmodel.BadgeID, userID string, grantedBy string, reason string) (bool, error)
AddType(t *badgesmodel.BadgeTypeDefinition) (*badgesmodel.BadgeTypeDefinition, error)
GetType(tID badgesmodel.BadgeType) (*badgesmodel.BadgeTypeDefinition, error)
GetBadge(badgeID badgesmodel.BadgeID) (*badgesmodel.Badge, error)
UpdateType(t *badgesmodel.BadgeTypeDefinition) error
UpdateBadge(b *badgesmodel.Badge) error
DeleteType(tID badgesmodel.BadgeType) error
DeleteBadge(bID badgesmodel.BadgeID) error
AddSubscription(tID badgesmodel.BadgeType, cID string) error
RemoveSubscriptions(tID badgesmodel.BadgeType, cID string) error
GetTypeSubscriptions(tID badgesmodel.BadgeType) ([]string, error)
GetChannelSubscriptions(cID string) ([]*badgesmodel.BadgeTypeDefinition, error)
// Default type
EnsureDefaultType(botID string) error
// PAPI
EnsureBadges(badges []*badgesmodel.Badge, pluginID, botID string) ([]*badgesmodel.Badge, error)
}
type store struct {
api plugin.API
}
func NewStore(api plugin.API) Store {
return &store{
api: api,
}
}
func (s *store) EnsureBadges(badges []*badgesmodel.Badge, pluginID, botID string) ([]*badgesmodel.Badge, error) {
l, _, err := s.getAllTypes()
if err != nil {
return nil, err
}
var tDef *badgesmodel.BadgeTypeDefinition
for i, t := range l {
if t.CreatedBy == botID {
tDef = l[i]
break
}
}
if tDef == nil {
tDef, err = s.addType(&badgesmodel.BadgeTypeDefinition{
Name: "Plugin badges: " + pluginID,
CreatedBy: botID,
}, true)
if err != nil {
return nil, err
}
}
bb, _, err := s.getAllBadges()
if err != nil {
return nil, err
}
out := []*badgesmodel.Badge{}
for _, pb := range badges {
found := false
for _, b := range bb {
if b.CreatedBy == botID && b.Name == pb.Name {
found = true
out = append(out, b)
break
}
}
if !found {
pb.Type = tDef.ID
pb.CreatedBy = botID
newBadge, err := s.AddBadge(pb)
if err != nil {
return nil, err
}
out = append(out, newBadge)
}
}
return out, nil
}
func (s *store) AddBadge(b *badgesmodel.Badge) (*badgesmodel.Badge, error) {
if !b.IsValid() {
return nil, errInvalidBadge
}
badgeTypes, _, err := s.getAllTypes()
if err != nil {
return nil, err
}
t := badgeTypes.GetType(b.Type)
if t == nil {
return nil, errors.New("missing badge type")
}
b.ID = badgesmodel.BadgeID(model.NewId())
err = s.doAtomic(func() (bool, error) { return s.atomicAddBadge(b) })
if err != nil {
return nil, err
}
return b, nil
}
func (s *store) AddType(t *badgesmodel.BadgeTypeDefinition) (*badgesmodel.BadgeTypeDefinition, error) {
return s.addType(t, false)
}
func (s *store) addType(t *badgesmodel.BadgeTypeDefinition, isPlugin bool) (*badgesmodel.BadgeTypeDefinition, error) {
t.ID = badgesmodel.BadgeType(model.NewId())
err := s.doAtomic(func() (bool, error) { return s.atomicAddType(t) })
if err != nil {
return nil, err
}
return t, nil
}
func (s *store) EnsureDefaultType(botID string) error {
types, _, err := s.getAllTypes()
if err != nil {
return err
}
for _, t := range types {
if t.IsDefault {
return nil
}
}
_, err = s.addType(&badgesmodel.BadgeTypeDefinition{
Name: badgesmodel.DefaultTypeName,
IsDefault: true,
CreatedBy: botID,
CanCreate: badgesmodel.PermissionScheme{Everyone: true},
CanGrant: badgesmodel.PermissionScheme{Everyone: true},
}, false)
return err
}
func (s *store) GetAllBadges() ([]*badgesmodel.AllBadgesBadge, error) {
badges, _, err := s.getAllBadges()
if err != nil {
return nil, err
}
ownership, _, err := s.getOwnershipList()
if err != nil {
return nil, err
}
out := []*badgesmodel.AllBadgesBadge{}
for _, b := range badges {
badge := &badgesmodel.AllBadgesBadge{
Badge: *b,
}
grantedTo := map[string]bool{}
for _, o := range ownership {
if o.Badge != badge.ID {
continue
}
badge.GrantedTimes++
if !grantedTo[o.User] {
badge.Granted++
grantedTo[o.User] = true
}
}
badge.TypeName = "unknown"
t, err := s.GetType(badge.Type)
if err == nil {
badge.TypeName = t.Name
}
out = append(out, badge)
}
return out, nil
}
func (s *store) GetRawBadges() ([]*badgesmodel.Badge, error) {
bb, _, err := s.getAllBadges()
return bb, err
}
func (s *store) GetRawTypes() (badgesmodel.BadgeTypeList, error) {
tt, _, err := s.getAllTypes()
return tt, err
}
func (s *store) getAllTypes() (badgesmodel.BadgeTypeList, []byte, error) {
data, appErr := s.api.KVGet(KVKeyTypes)
if appErr != nil {
return nil, nil, appErr
}
typeList := []*badgesmodel.BadgeTypeDefinition{}
if data != nil {
err := json.Unmarshal(data, &typeList)
if err != nil {
return nil, nil, err
}
}
return typeList, data, nil
}
func (s *store) getAllBadges() ([]*badgesmodel.Badge, []byte, error) {
data, appErr := s.api.KVGet(KVKeyBadges)
if appErr != nil {
return nil, nil, appErr
}
badgeList := []*badgesmodel.Badge{}
if data != nil {
err := json.Unmarshal(data, &badgeList)
if err != nil {
return nil, nil, err
}
}
return badgeList, data, nil
}
func (s *store) getBadge(id badgesmodel.BadgeID) (*badgesmodel.Badge, error) {
badgeList, _, err := s.getAllBadges()
if err != nil {
return nil, err
}
return s.getBadgeFromList(id, badgeList)
}
func (s *store) GetBadgeDetails(id badgesmodel.BadgeID) (*badgesmodel.BadgeDetails, error) {
badge, err := s.getBadge(id)
if err != nil {
return nil, err
}
owners, err := s.getBadgeUsers(id)
if err != nil {
return nil, err
}
createdByName := "unknown"
u, appErr := s.api.GetUser(badge.CreatedBy)
if appErr == nil {
conf := s.api.GetConfig()
if conf != nil {
format := conf.TeamSettings.TeammateNameDisplay
if format != nil {
createdByName = u.GetDisplayName(*format)
}
}
}
typeName := "unknown"
t, err := s.GetType(badge.Type)
if err == nil {
typeName = t.Name
}
return &badgesmodel.BadgeDetails{
Badge: *badge,
Owners: owners,
CreatedByUsername: createdByName,
TypeName: typeName,
}, nil
}
func (s *store) getOwnershipList() (badgesmodel.OwnershipList, []byte, error) {
data, appErr := s.api.KVGet(KVKeyOwnership)
if appErr != nil {
return nil, nil, appErr
}
ownership := badgesmodel.OwnershipList{}
if data != nil {
err := json.Unmarshal(data, &ownership)
if err != nil {
return nil, nil, err
}
}
return ownership, data, nil
}
func (s *store) GrantBadge(id badgesmodel.BadgeID, userID string, grantedBy string, reason string) (bool, error) {
badge, err := s.getBadge(id)
if err != nil {
return false, err
}
types, _, err := s.getAllTypes()
if err != nil {
return false, err
}
badgeType := types.GetType(badge.Type)
if badgeType == nil {
return false, errors.New("badge type not found")
}
ownership := badgesmodel.Ownership{
User: userID,
Badge: badge.ID,
Time: time.Now(),
Reason: reason,
GrantedBy: grantedBy,
}
shouldNotify := false
err = s.doAtomic(func() (bool, error) {
var done bool
var err error
shouldNotify, done, err = s.atomicAddBadgeToOwnership(ownership, badge.Multiple)
return done, err
})
if err != nil {
return false, err
}
return shouldNotify, nil
}
func (s *store) GetUserBadges(userID string) ([]*badgesmodel.UserBadge, error) {
ownership, _, err := s.getOwnershipList()
if err != nil {
return nil, err
}
badges, _, err := s.getAllBadges()
if err != nil {
return nil, err
}
out := []*badgesmodel.UserBadge{}
for _, o := range ownership {
if o.User == userID {
badge, err := s.getBadgeFromList(o.Badge, badges)
if err != nil {
s.api.LogDebug("Badge not found while getting user badges", "badgeID", o.Badge, "userID", userID)
continue
}
grantedByName := "unknown"
u, appErr := s.api.GetUser(o.GrantedBy)
if appErr == nil {
conf := s.api.GetConfig()
if conf != nil {
format := conf.TeamSettings.TeammateNameDisplay
if format != nil {
grantedByName = u.GetDisplayName(*format)
}
}
}
typeName := "unknown"
t, err := s.GetType(badge.Type)
if err == nil {
typeName = t.Name
}
out = append([]*badgesmodel.UserBadge{{Badge: *badge, Ownership: o, GrantedByUsername: grantedByName, TypeName: typeName}}, out...)
}
}
return out, nil
}
func (s *store) GetType(tID badgesmodel.BadgeType) (*badgesmodel.BadgeTypeDefinition, error) {
tt, _, err := s.getAllTypes()
if err != nil {
return nil, err
}
for _, t := range tt {
if t.ID == tID {
return t, nil
}
}
return nil, errors.New("not found")
}
func (s *store) GetBadge(badgeID badgesmodel.BadgeID) (*badgesmodel.Badge, error) {
return s.getBadge(badgeID)
}
func (s *store) UpdateType(t *badgesmodel.BadgeTypeDefinition) error {
return s.doAtomic(func() (bool, error) { return s.atomicUpdateType(t) })
}
func (s *store) UpdateBadge(b *badgesmodel.Badge) error {
return s.doAtomic(func() (bool, error) { return s.atomicUpdateBadge(b) })
}
func (s *store) atomicDeleteType(tID badgesmodel.BadgeType) (bool, error) {
tt, data, err := s.getAllTypes()
if err != nil {
return false, err
}
for i, t := range tt {
if t.ID == tID {
tt = append(tt[:i], tt[i+1:]...)
break
}
}
return s.compareAndSet(KVKeyTypes, data, tt)
}
func (s *store) DeleteType(tID badgesmodel.BadgeType) error {
t, err := s.GetType(tID)
if err == nil && t.IsDefault {
return errors.New("cannot delete default type")
}
s.doAtomic(func() (bool, error) { return s.atomicDeleteType(tID) })
bb, _, err := s.getAllBadges()
if err != nil {
return err
}
for _, b := range bb {
if b.Type == tID {
s.api.LogDebug("Deleting badge", "name", b.Name)
err := s.DeleteBadge(b.ID)
if err != nil {
return err
}
}
}
return nil
}
func (s *store) DeleteBadge(bID badgesmodel.BadgeID) error {
err := s.doAtomic(func() (bool, error) { return s.atomicRemoveBadge(bID) })
if err != nil {
return err
}
err = s.doAtomic(func() (bool, error) { return s.atomicRemoveBadgeFromOwnership(bID) })
if err != nil {
return err
}
return nil
}
func (s *store) getAllSubscriptions() ([]badgesmodel.Subscription, []byte, error) {
data, appErr := s.api.KVGet(KVKeySubscriptions)
if appErr != nil {
return nil, nil, appErr
}
subs := []badgesmodel.Subscription{}
if data != nil {
err := json.Unmarshal(data, &subs)
if err != nil {
return nil, nil, err
}
}
return subs, data, nil
}
func (s *store) AddSubscription(tID badgesmodel.BadgeType, cID string) error {
toAdd := badgesmodel.Subscription{ChannelID: cID, TypeID: tID}
return s.doAtomic(func() (bool, error) { return s.atomicAddSubscription(toAdd) })
}
func (s *store) RemoveSubscriptions(tID badgesmodel.BadgeType, cID string) error {
toRemove := badgesmodel.Subscription{ChannelID: cID, TypeID: tID}
return s.doAtomic(func() (bool, error) { return s.atomicRemoveSubscription(toRemove) })
}
func (s *store) GetTypeSubscriptions(tID badgesmodel.BadgeType) ([]string, error) {
subs, _, err := s.getAllSubscriptions()
if err != nil {
return nil, err
}
out := []string{}
for _, sub := range subs {
if sub.TypeID == tID {
out = append(out, sub.ChannelID)
}
}
return out, nil
}
func (s *store) GetChannelSubscriptions(cID string) ([]*badgesmodel.BadgeTypeDefinition, error) {
subs, _, err := s.getAllSubscriptions()
if err != nil {
return nil, err
}
out := []*badgesmodel.BadgeTypeDefinition{}
for _, sub := range subs {
if sub.ChannelID == cID {
t, err := s.GetType(sub.TypeID)
if err != nil {
s.api.LogDebug("cannot get type", "err", err)
continue
}
out = append(out, t)
}
}
return out, nil
}
func (s *store) getBadgeFromList(badgeID badgesmodel.BadgeID, list []*badgesmodel.Badge) (*badgesmodel.Badge, error) {
for _, badge := range list {
if badgeID == badge.ID {
return badge, nil
}
}
return nil, errBadgeNotFound
}
func (s *store) getBadgeUsers(badgeID badgesmodel.BadgeID) (badgesmodel.OwnershipList, error) {
_, err := s.getBadge(badgeID)
if err != nil {
return nil, errBadgeNotFound
}
ownership, _, err := s.getOwnershipList()
if err != nil {
return nil, err
}
out := badgesmodel.OwnershipList{}
for _, o := range ownership {
if o.Badge == badgeID {
out = append(out, o)
}
}
return out, nil
}