mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
chore: add maximum default leaderboard length
This commit is contained in:
parent
ffbcfc7467
commit
b1d7f87095
@ -19,14 +19,36 @@ type LeaderboardItem struct {
|
|||||||
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l1 *LeaderboardItem) Equals(l2 *LeaderboardItem) bool {
|
||||||
|
return l1.ID == l2.ID
|
||||||
|
}
|
||||||
|
|
||||||
type Leaderboard []*LeaderboardItem
|
type Leaderboard []*LeaderboardItem
|
||||||
|
|
||||||
|
func (l *Leaderboard) Add(item *LeaderboardItem) {
|
||||||
|
if _, found := slice.Find[*LeaderboardItem](*l, func(i int, item2 *LeaderboardItem) bool {
|
||||||
|
return item.Equals(item2)
|
||||||
|
}); !found {
|
||||||
|
*l = append(*l, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Leaderboard) AddMany(items []*LeaderboardItem) {
|
||||||
|
for _, item := range items {
|
||||||
|
l.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l Leaderboard) UserIDs() []string {
|
func (l Leaderboard) UserIDs() []string {
|
||||||
return slice.Unique[string](slice.Map[*LeaderboardItem, string](l, func(i int, item *LeaderboardItem) string {
|
return slice.Unique[string](slice.Map[*LeaderboardItem, string](l, func(i int, item *LeaderboardItem) string {
|
||||||
return item.UserID
|
return item.UserID
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l Leaderboard) HasUser(userId string) bool {
|
||||||
|
return slice.Contain(l.UserIDs(), userId)
|
||||||
|
}
|
||||||
|
|
||||||
func (l Leaderboard) TopByKey(by uint8, key string) Leaderboard {
|
func (l Leaderboard) TopByKey(by uint8, key string) Leaderboard {
|
||||||
return slice.Filter[*LeaderboardItem](l, func(i int, item *LeaderboardItem) bool {
|
return slice.Filter[*LeaderboardItem](l, func(i int, item *LeaderboardItem) bool {
|
||||||
return item.By != nil && *item.By == by && item.Key != nil && strings.ToLower(*item.Key) == strings.ToLower(key)
|
return item.By != nil && *item.By == by && item.Key != nil && strings.ToLower(*item.Key) == strings.ToLower(key)
|
||||||
|
@ -33,6 +33,15 @@ func (r *LeaderboardRepository) CountAllByUser(userId string) (int64, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LeaderboardRepository) CountUsers() (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.
|
||||||
|
Table("leaderboard_items").
|
||||||
|
Distinct("user_id").
|
||||||
|
Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LeaderboardRepository) GetAllAggregatedByInterval(key *models.IntervalKey, by *uint8, limit, skip int) ([]*models.LeaderboardItem, error) {
|
func (r *LeaderboardRepository) GetAllAggregatedByInterval(key *models.IntervalKey, by *uint8, limit, skip int) ([]*models.LeaderboardItem, error) {
|
||||||
// TODO: distinct by (user, key) to filter out potential duplicates ?
|
// TODO: distinct by (user, key) to filter out potential duplicates ?
|
||||||
var items []*models.LeaderboardItem
|
var items []*models.LeaderboardItem
|
||||||
@ -56,11 +65,10 @@ func (r *LeaderboardRepository) GetAggregatedByUserAndInterval(userId string, ke
|
|||||||
subq := r.db.
|
subq := r.db.
|
||||||
Table("leaderboard_items").
|
Table("leaderboard_items").
|
||||||
Select("*, rank() over (partition by \"key\" order by total desc) as \"rank\"").
|
Select("*, rank() over (partition by \"key\" order by total desc) as \"rank\"").
|
||||||
Where("user_id = ?", userId).
|
|
||||||
Where("\"interval\" in ?", *key)
|
Where("\"interval\" in ?", *key)
|
||||||
subq = utils.WhereNullable(subq, "\"by\"", by)
|
subq = utils.WhereNullable(subq, "\"by\"", by)
|
||||||
|
|
||||||
q := r.db.Table("(?) as ranked", subq)
|
q := r.db.Table("(?) as ranked", subq).Where("user_id = ?", userId)
|
||||||
q = r.withPaging(q, limit, skip)
|
q = r.withPaging(q, limit, skip)
|
||||||
|
|
||||||
if err := q.Find(&items).Error; err != nil {
|
if err := q.Find(&items).Error; err != nil {
|
||||||
|
@ -90,6 +90,7 @@ type IUserRepository interface {
|
|||||||
type ILeaderboardRepository interface {
|
type ILeaderboardRepository interface {
|
||||||
InsertBatch([]*models.LeaderboardItem) error
|
InsertBatch([]*models.LeaderboardItem) error
|
||||||
CountAllByUser(string) (int64, error)
|
CountAllByUser(string) (int64, error)
|
||||||
|
CountUsers() (int64, error)
|
||||||
DeleteByUser(string) error
|
DeleteByUser(string) error
|
||||||
DeleteByUserAndInterval(string, *models.IntervalKey) error
|
DeleteByUserAndInterval(string, *models.IntervalKey) error
|
||||||
GetAllAggregatedByInterval(*models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItem, error)
|
GetAllAggregatedByInterval(*models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItem, error)
|
||||||
|
@ -57,7 +57,10 @@ func (h *LeaderboardHandler) buildViewModel(r *http.Request) *view.LeaderboardVi
|
|||||||
user := middlewares.GetPrincipal(r)
|
user := middlewares.GetPrincipal(r)
|
||||||
byParam := strings.ToLower(r.URL.Query().Get("by"))
|
byParam := strings.ToLower(r.URL.Query().Get("by"))
|
||||||
keyParam := strings.ToLower(r.URL.Query().Get("key"))
|
keyParam := strings.ToLower(r.URL.Query().Get("key"))
|
||||||
pageParams := utils.ParsePageParams(r)
|
pageParams := utils.ParsePageParamsWithDefault(r, 1, 100)
|
||||||
|
// note: pagination is not fully implemented, yet
|
||||||
|
// count function to get total item / total pages is missing
|
||||||
|
// and according ui (+ optionally search bar) is missing, too
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var leaderboard models.Leaderboard
|
var leaderboard models.Leaderboard
|
||||||
@ -70,6 +73,16 @@ func (h *LeaderboardHandler) buildViewModel(r *http.Request) *view.LeaderboardVi
|
|||||||
conf.Log().Request(r).Error("error while fetching general leaderboard items - %v", err)
|
conf.Log().Request(r).Error("error while fetching general leaderboard items - %v", err)
|
||||||
return &view.LeaderboardViewModel{Error: criticalError}
|
return &view.LeaderboardViewModel{Error: criticalError}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regardless of page, always show own rank
|
||||||
|
if user != nil && !leaderboard.HasUser(user.ID) {
|
||||||
|
// but only if leaderboard spans multiple pages
|
||||||
|
if count, err := h.leaderboardService.CountUsers(); err == nil && count > int64(pageParams.PageSize) {
|
||||||
|
if l, err := h.leaderboardService.GetByIntervalAndUser(models.IntervalPast7Days, user.ID, true); err == nil && len(l) > 0 {
|
||||||
|
leaderboard = append(leaderboard, l[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if by, ok := allowedAggregations[byParam]; ok {
|
if by, ok := allowedAggregations[byParam]; ok {
|
||||||
leaderboard, err = h.leaderboardService.GetAggregatedByInterval(models.IntervalPast7Days, &by, pageParams, true)
|
leaderboard, err = h.leaderboardService.GetAggregatedByInterval(models.IntervalPast7Days, &by, pageParams, true)
|
||||||
@ -78,6 +91,18 @@ func (h *LeaderboardHandler) buildViewModel(r *http.Request) *view.LeaderboardVi
|
|||||||
return &view.LeaderboardViewModel{Error: criticalError}
|
return &view.LeaderboardViewModel{Error: criticalError}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regardless of page, always show own rank
|
||||||
|
if user != nil {
|
||||||
|
// but only if leaderboard could, in theory, span multiple pages
|
||||||
|
if count, err := h.leaderboardService.CountUsers(); err == nil && count > int64(pageParams.PageSize) {
|
||||||
|
if l, err := h.leaderboardService.GetAggregatedByIntervalAndUser(models.IntervalPast7Days, user.ID, &by, true); err == nil {
|
||||||
|
leaderboard.AddMany(l)
|
||||||
|
} else {
|
||||||
|
conf.Log().Request(r).Error("error while fetching own aggregated user leaderboard - %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userLeaderboards := slice.GroupWith[*models.LeaderboardItem, string](leaderboard, func(item *models.LeaderboardItem) string {
|
userLeaderboards := slice.GroupWith[*models.LeaderboardItem, string](leaderboard, func(item *models.LeaderboardItem) string {
|
||||||
return item.UserID
|
return item.UserID
|
||||||
})
|
})
|
||||||
|
@ -126,13 +126,31 @@ func (srv *LeaderboardService) ExistsAnyByUser(userId string) (bool, error) {
|
|||||||
return count > 0, err
|
return count > 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *LeaderboardService) CountUsers() (int64, error) {
|
||||||
|
// check cache
|
||||||
|
cacheKey := "count_total"
|
||||||
|
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
|
||||||
|
return cacheResult.(int64), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := srv.repository.CountUsers()
|
||||||
|
if err != nil {
|
||||||
|
srv.cache.SetDefault(cacheKey, count)
|
||||||
|
}
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) GetByInterval(interval *models.IntervalKey, pageParams *models.PageParams, resolveUsers bool) (models.Leaderboard, error) {
|
func (srv *LeaderboardService) GetByInterval(interval *models.IntervalKey, pageParams *models.PageParams, resolveUsers bool) (models.Leaderboard, error) {
|
||||||
return srv.GetAggregatedByInterval(interval, nil, pageParams, resolveUsers)
|
return srv.GetAggregatedByInterval(interval, nil, pageParams, resolveUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *LeaderboardService) GetByIntervalAndUser(interval *models.IntervalKey, userId string, resolveUser bool) (models.Leaderboard, error) {
|
||||||
|
return srv.GetAggregatedByIntervalAndUser(interval, userId, nil, resolveUser)
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) GetAggregatedByInterval(interval *models.IntervalKey, by *uint8, pageParams *models.PageParams, resolveUsers bool) (models.Leaderboard, error) {
|
func (srv *LeaderboardService) GetAggregatedByInterval(interval *models.IntervalKey, by *uint8, pageParams *models.PageParams, resolveUsers bool) (models.Leaderboard, error) {
|
||||||
// check cache
|
// check cache
|
||||||
cacheKey := srv.getHash(interval, by, pageParams)
|
cacheKey := srv.getHash(interval, by, "", pageParams)
|
||||||
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
|
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
|
||||||
return cacheResult.([]*models.LeaderboardItem), nil
|
return cacheResult.([]*models.LeaderboardItem), nil
|
||||||
}
|
}
|
||||||
@ -143,8 +161,6 @@ func (srv *LeaderboardService) GetAggregatedByInterval(interval *models.Interval
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resolveUsers {
|
if resolveUsers {
|
||||||
a := models.Leaderboard(items).UserIDs()
|
|
||||||
println(a)
|
|
||||||
users, err := srv.userService.GetManyMapped(models.Leaderboard(items).UserIDs())
|
users, err := srv.userService.GetManyMapped(models.Leaderboard(items).UserIDs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log().Error("failed to resolve users for leaderboard item - %v", err)
|
config.Log().Error("failed to resolve users for leaderboard item - %v", err)
|
||||||
@ -161,6 +177,33 @@ func (srv *LeaderboardService) GetAggregatedByInterval(interval *models.Interval
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *LeaderboardService) GetAggregatedByIntervalAndUser(interval *models.IntervalKey, userId string, by *uint8, resolveUser bool) (models.Leaderboard, error) {
|
||||||
|
// check cache
|
||||||
|
cacheKey := srv.getHash(interval, by, userId, nil)
|
||||||
|
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
|
||||||
|
return cacheResult.([]*models.LeaderboardItem), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := srv.repository.GetAggregatedByUserAndInterval(userId, interval, by, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolveUser {
|
||||||
|
u, err := srv.userService.GetUserById(userId)
|
||||||
|
if err != nil {
|
||||||
|
config.Log().Error("failed to resolve user for leaderboard item - %v", err)
|
||||||
|
} else {
|
||||||
|
for _, item := range items {
|
||||||
|
item.User = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.cache.SetDefault(cacheKey, items)
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *models.IntervalKey) (*models.LeaderboardItem, error) {
|
func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *models.IntervalKey) (*models.LeaderboardItem, error) {
|
||||||
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -209,11 +252,13 @@ func (srv *LeaderboardService) GenerateAggregatedByUser(user *models.User, inter
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) getHash(interval *models.IntervalKey, by *uint8, pageParams *models.PageParams) string {
|
func (srv *LeaderboardService) getHash(interval *models.IntervalKey, by *uint8, user string, pageParams *models.PageParams) string {
|
||||||
k := strings.Join(*interval, "__")
|
k := strings.Join(*interval, "__") + "__" + user
|
||||||
if by != nil && !reflect.ValueOf(by).IsNil() {
|
if by != nil && !reflect.ValueOf(by).IsNil() {
|
||||||
k += "__" + models.GetEntityColumn(*by)
|
k += "__" + models.GetEntityColumn(*by)
|
||||||
}
|
}
|
||||||
|
if pageParams != nil {
|
||||||
k += "__" + strconv.Itoa(pageParams.Page) + "__" + strconv.Itoa(pageParams.PageSize)
|
k += "__" + strconv.Itoa(pageParams.Page) + "__" + strconv.Itoa(pageParams.PageSize)
|
||||||
|
}
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
@ -101,8 +101,11 @@ type ILeaderboardService interface {
|
|||||||
ScheduleDefault()
|
ScheduleDefault()
|
||||||
Run([]*models.User, *models.IntervalKey, []uint8) error
|
Run([]*models.User, *models.IntervalKey, []uint8) error
|
||||||
ExistsAnyByUser(string) (bool, error)
|
ExistsAnyByUser(string) (bool, error)
|
||||||
|
CountUsers() (int64, error)
|
||||||
GetByInterval(*models.IntervalKey, *models.PageParams, bool) (models.Leaderboard, error)
|
GetByInterval(*models.IntervalKey, *models.PageParams, bool) (models.Leaderboard, error)
|
||||||
|
GetByIntervalAndUser(*models.IntervalKey, string, bool) (models.Leaderboard, error)
|
||||||
GetAggregatedByInterval(*models.IntervalKey, *uint8, *models.PageParams, bool) (models.Leaderboard, error)
|
GetAggregatedByInterval(*models.IntervalKey, *uint8, *models.PageParams, bool) (models.Leaderboard, error)
|
||||||
|
GetAggregatedByIntervalAndUser(*models.IntervalKey, string, *uint8, bool) (models.Leaderboard, error)
|
||||||
GenerateByUser(*models.User, *models.IntervalKey) (*models.LeaderboardItem, error)
|
GenerateByUser(*models.User, *models.IntervalKey) (*models.LeaderboardItem, error)
|
||||||
GenerateAggregatedByUser(*models.User, *models.IntervalKey, uint8) ([]*models.LeaderboardItem, error)
|
GenerateAggregatedByUser(*models.User, *models.IntervalKey, uint8) ([]*models.LeaderboardItem, error)
|
||||||
}
|
}
|
||||||
|
@ -56,3 +56,14 @@ func ParsePageParams(r *http.Request) *models.PageParams {
|
|||||||
}
|
}
|
||||||
return pageParams
|
return pageParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParsePageParamsWithDefault(r *http.Request, page, size int) *models.PageParams {
|
||||||
|
pageParams := ParsePageParams(r)
|
||||||
|
if pageParams.Page == 0 {
|
||||||
|
pageParams.Page = page
|
||||||
|
}
|
||||||
|
if pageParams.PageSize == 0 {
|
||||||
|
pageParams.PageSize = size
|
||||||
|
}
|
||||||
|
return pageParams
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user