diff --git a/models/leaderboard.go b/models/leaderboard.go index b92119d..0e88067 100644 --- a/models/leaderboard.go +++ b/models/leaderboard.go @@ -11,7 +11,6 @@ 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"` - Rank uint `json:"rank" gorm:"-:migration"` Interval string `json:"interval" gorm:"not null; size:32; index:idx_leaderboard_combined"` By *uint8 `json:"aggregated_by" gorm:"index:idx_leaderboard_combined"` // pointer because nullable Total time.Duration `json:"total" gorm:"not null" swaggertype:"primitive,integer"` @@ -19,28 +18,35 @@ type LeaderboardItem struct { 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 { +// 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 { return l1.ID == l2.ID } -type Leaderboard []*LeaderboardItem +type Leaderboard []*LeaderboardItemRanked -func (l *Leaderboard) Add(item *LeaderboardItem) { - if _, found := slice.Find[*LeaderboardItem](*l, func(i int, item2 *LeaderboardItem) bool { +func (l *Leaderboard) Add(item *LeaderboardItemRanked) { + if _, found := slice.Find[*LeaderboardItemRanked](*l, func(i int, item2 *LeaderboardItemRanked) bool { return item.Equals(item2) }); !found { *l = append(*l, item) } } -func (l *Leaderboard) AddMany(items []*LeaderboardItem) { +func (l *Leaderboard) AddMany(items []*LeaderboardItemRanked) { for _, item := range items { l.Add(item) } } 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[*LeaderboardItemRanked, string](l, func(i int, item *LeaderboardItemRanked) string { return item.UserID })) } @@ -50,7 +56,7 @@ func (l Leaderboard) HasUser(userId string) bool { } func (l Leaderboard) TopByKey(by uint8, key string) Leaderboard { - return slice.Filter[*LeaderboardItem](l, func(i int, item *LeaderboardItem) bool { + return slice.Filter[*LeaderboardItemRanked](l, func(i int, item *LeaderboardItemRanked) bool { return item.By != nil && *item.By == by && item.Key != nil && strings.ToLower(*item.Key) == strings.ToLower(key) }) } @@ -87,7 +93,7 @@ func (l Leaderboard) TopKeys(by uint8) []string { } func (l Leaderboard) TopKeysByUser(by uint8, userId string) []string { - return Leaderboard(slice.Filter[*LeaderboardItem](l, func(i int, item *LeaderboardItem) bool { + return Leaderboard(slice.Filter[*LeaderboardItemRanked](l, func(i int, item *LeaderboardItemRanked) bool { return item.UserID == userId })).TopKeys(by) } diff --git a/models/view/leaderboard.go b/models/view/leaderboard.go index c701874..009afb0 100644 --- a/models/view/leaderboard.go +++ b/models/view/leaderboard.go @@ -10,7 +10,7 @@ type LeaderboardViewModel struct { User *models.User By string Key string - Items []*models.LeaderboardItem + Items []*models.LeaderboardItemRanked TopKeys []string UserLanguages map[string][]string ApiKey string @@ -29,7 +29,7 @@ func (s *LeaderboardViewModel) WithError(m string) *LeaderboardViewModel { return s } -func (s *LeaderboardViewModel) ColorModifier(item *models.LeaderboardItem, principal *models.User) string { +func (s *LeaderboardViewModel) ColorModifier(item *models.LeaderboardItemRanked, principal *models.User) string { if principal != nil && item.UserID == principal.ID { return "self" } diff --git a/repositories/leaderboard.go b/repositories/leaderboard.go index b9963ea..4f489aa 100644 --- a/repositories/leaderboard.go +++ b/repositories/leaderboard.go @@ -42,9 +42,10 @@ func (r *LeaderboardRepository) CountUsers() (int64, 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.LeaderboardItemRanked, error) { // TODO: distinct by (user, key) to filter out potential duplicates ? - var items []*models.LeaderboardItem + + var items []*models.LeaderboardItemRanked subq := r.db. Table("leaderboard_items"). Select("*, rank() over (partition by \"key\" order by total desc) as \"rank\""). @@ -60,8 +61,8 @@ func (r *LeaderboardRepository) GetAllAggregatedByInterval(key *models.IntervalK return items, nil } -func (r *LeaderboardRepository) GetAggregatedByUserAndInterval(userId string, key *models.IntervalKey, by *uint8, limit, skip int) ([]*models.LeaderboardItem, error) { - var items []*models.LeaderboardItem +func (r *LeaderboardRepository) GetAggregatedByUserAndInterval(userId string, key *models.IntervalKey, by *uint8, limit, skip int) ([]*models.LeaderboardItemRanked, error) { + var items []*models.LeaderboardItemRanked subq := r.db. Table("leaderboard_items"). Select("*, rank() over (partition by \"key\" order by total desc) as \"rank\""). diff --git a/repositories/repositories.go b/repositories/repositories.go index d700d83..9b9bbe0 100644 --- a/repositories/repositories.go +++ b/repositories/repositories.go @@ -93,6 +93,6 @@ type ILeaderboardRepository interface { CountUsers() (int64, error) DeleteByUser(string) error DeleteByUserAndInterval(string, *models.IntervalKey) error - GetAllAggregatedByInterval(*models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItem, error) - GetAggregatedByUserAndInterval(string, *models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItem, error) + GetAllAggregatedByInterval(*models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItemRanked, error) + GetAggregatedByUserAndInterval(string, *models.IntervalKey, *uint8, int, int) ([]*models.LeaderboardItemRanked, error) } diff --git a/routes/leaderboard.go b/routes/leaderboard.go index 8e1ad4c..223c838 100644 --- a/routes/leaderboard.go +++ b/routes/leaderboard.go @@ -103,7 +103,7 @@ func (h *LeaderboardHandler) buildViewModel(r *http.Request) *view.LeaderboardVi } } - userLeaderboards := slice.GroupWith[*models.LeaderboardItem, string](leaderboard, func(item *models.LeaderboardItem) string { + userLeaderboards := slice.GroupWith[*models.LeaderboardItemRanked, string](leaderboard, func(item *models.LeaderboardItemRanked) string { return item.UserID }) userLanguages = map[string][]string{} diff --git a/services/leaderboard.go b/services/leaderboard.go index 136c62d..0dd2cb1 100644 --- a/services/leaderboard.go +++ b/services/leaderboard.go @@ -152,7 +152,7 @@ func (srv *LeaderboardService) GetAggregatedByInterval(interval *models.Interval // check cache cacheKey := srv.getHash(interval, by, "", pageParams) if cacheResult, ok := srv.cache.Get(cacheKey); ok { - return cacheResult.([]*models.LeaderboardItem), nil + return cacheResult.([]*models.LeaderboardItemRanked), nil } items, err := srv.repository.GetAllAggregatedByInterval(interval, by, pageParams.Limit(), pageParams.Offset()) @@ -181,7 +181,7 @@ func (srv *LeaderboardService) GetAggregatedByIntervalAndUser(interval *models.I // check cache cacheKey := srv.getHash(interval, by, userId, nil) if cacheResult, ok := srv.cache.Get(cacheKey); ok { - return cacheResult.([]*models.LeaderboardItem), nil + return cacheResult.([]*models.LeaderboardItemRanked), nil } items, err := srv.repository.GetAggregatedByUserAndInterval(userId, interval, by, 0, 0)