2022-09-30 18:14:05 +03:00
|
|
|
package models
|
|
|
|
|
2022-10-04 00:52:22 +03:00
|
|
|
import (
|
|
|
|
"github.com/duke-git/lancet/v2/maputil"
|
|
|
|
"github.com/duke-git/lancet/v2/slice"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
2022-09-30 18:14:05 +03:00
|
|
|
|
|
|
|
type LeaderboardItem struct {
|
|
|
|
ID uint `json:"-" gorm:"primary_key; size:32"`
|
|
|
|
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
|
|
|
UserID string `json:"user_id" gorm:"not null; index:idx_leaderboard_user"`
|
|
|
|
Interval string `json:"interval" gorm:"not null; size:32; index:idx_leaderboard_combined"`
|
2022-10-02 01:01:39 +03:00
|
|
|
By *uint8 `json:"aggregated_by" gorm:"index:idx_leaderboard_combined"` // pointer because nullable
|
2022-09-30 18:14:05 +03:00
|
|
|
Total time.Duration `json:"total" gorm:"not null" swaggertype:"primitive,integer"`
|
2022-10-02 01:01:39 +03:00
|
|
|
Key *string `json:"key" gorm:"size:255"` // pointer because nullable
|
2022-09-30 18:14:05 +03:00
|
|
|
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
|
|
|
}
|
2022-10-04 00:52:22 +03:00
|
|
|
|
2022-10-20 09:33:12 +03:00
|
|
|
// https://github.com/go-gorm/gorm/issues/5789
|
|
|
|
// https://github.com/go-gorm/gorm/issues/5284#issuecomment-1107775806
|
|
|
|
type LeaderboardItemRanked struct {
|
|
|
|
LeaderboardItem
|
|
|
|
Rank uint
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l1 *LeaderboardItemRanked) Equals(l2 *LeaderboardItemRanked) bool {
|
2022-10-19 19:28:30 +03:00
|
|
|
return l1.ID == l2.ID
|
|
|
|
}
|
|
|
|
|
2022-10-20 09:33:12 +03:00
|
|
|
type Leaderboard []*LeaderboardItemRanked
|
2022-10-04 00:52:22 +03:00
|
|
|
|
2022-10-20 09:33:12 +03:00
|
|
|
func (l *Leaderboard) Add(item *LeaderboardItemRanked) {
|
|
|
|
if _, found := slice.Find[*LeaderboardItemRanked](*l, func(i int, item2 *LeaderboardItemRanked) bool {
|
2022-10-19 19:28:30 +03:00
|
|
|
return item.Equals(item2)
|
|
|
|
}); !found {
|
|
|
|
*l = append(*l, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-20 09:33:12 +03:00
|
|
|
func (l *Leaderboard) AddMany(items []*LeaderboardItemRanked) {
|
2022-10-19 19:28:30 +03:00
|
|
|
for _, item := range items {
|
|
|
|
l.Add(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 00:52:22 +03:00
|
|
|
func (l Leaderboard) UserIDs() []string {
|
2022-10-20 09:33:12 +03:00
|
|
|
return slice.Unique[string](slice.Map[*LeaderboardItemRanked, string](l, func(i int, item *LeaderboardItemRanked) string {
|
2022-10-04 00:52:22 +03:00
|
|
|
return item.UserID
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2022-10-19 19:28:30 +03:00
|
|
|
func (l Leaderboard) HasUser(userId string) bool {
|
|
|
|
return slice.Contain(l.UserIDs(), userId)
|
|
|
|
}
|
|
|
|
|
2022-10-04 00:52:22 +03:00
|
|
|
func (l Leaderboard) TopByKey(by uint8, key string) Leaderboard {
|
2022-10-20 09:33:12 +03:00
|
|
|
return slice.Filter[*LeaderboardItemRanked](l, func(i int, item *LeaderboardItemRanked) bool {
|
2022-10-04 00:52:22 +03:00
|
|
|
return item.By != nil && *item.By == by && item.Key != nil && strings.ToLower(*item.Key) == strings.ToLower(key)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l Leaderboard) TopKeys(by uint8) []string {
|
|
|
|
type keyTotal struct {
|
|
|
|
Key string
|
|
|
|
Total time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
totalsMapped := make(map[string]*keyTotal, len(l))
|
|
|
|
|
|
|
|
for _, item := range l {
|
|
|
|
if item.Key == nil || item.By == nil || *item.By != by {
|
|
|
|
continue
|
|
|
|
}
|
2022-10-07 09:58:51 +03:00
|
|
|
key := strings.ToLower(*item.Key)
|
|
|
|
if _, ok := totalsMapped[key]; !ok {
|
|
|
|
totalsMapped[key] = &keyTotal{Key: *item.Key, Total: 0}
|
2022-10-04 00:52:22 +03:00
|
|
|
}
|
2022-10-07 09:58:51 +03:00
|
|
|
totalsMapped[key].Total += item.Total
|
2022-10-04 00:52:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
totals := slice.Map[*keyTotal, keyTotal](maputil.Values[string, *keyTotal](totalsMapped), func(i int, item *keyTotal) keyTotal {
|
|
|
|
return *item
|
|
|
|
})
|
|
|
|
if err := slice.SortByField(totals, "Total", "desc"); err != nil {
|
|
|
|
return []string{} // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
return slice.Map[keyTotal, string](totals, func(i int, item keyTotal) string {
|
|
|
|
return item.Key
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l Leaderboard) TopKeysByUser(by uint8, userId string) []string {
|
2022-10-20 09:33:12 +03:00
|
|
|
return Leaderboard(slice.Filter[*LeaderboardItemRanked](l, func(i int, item *LeaderboardItemRanked) bool {
|
2022-10-04 00:52:22 +03:00
|
|
|
return item.UserID == userId
|
|
|
|
})).TopKeys(by)
|
|
|
|
}
|
2022-10-06 16:30:18 +03:00
|
|
|
|
|
|
|
func (l Leaderboard) LastUpdate() time.Time {
|
|
|
|
lastUpdate := time.Time{}
|
|
|
|
for _, item := range l {
|
|
|
|
if item.CreatedAt.T().After(lastUpdate) {
|
|
|
|
lastUpdate = item.CreatedAt.T()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lastUpdate
|
|
|
|
}
|