Implement country leaderboards

This commit is contained in:
Morgan Bazalgette 2017-04-11 23:18:02 +02:00
parent 120c027fce
commit 622658f5aa
No known key found for this signature in database
GPG Key ID: 40D328300D245DA5
4 changed files with 100 additions and 49 deletions

View File

@ -4,14 +4,21 @@ package peppy
import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/thehowl/go-osuapi"
"github.com/valyala/fasthttp"
"gopkg.in/redis.v5"
"zxq.co/ripple/ocl"
"zxq.co/ripple/rippleapi/common"
)
// R is a redis client.
var R *redis.Client
// GetUser retrieves general user information.
func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
if query(c, "u") == "" {
@ -24,23 +31,21 @@ func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
mode := genmode(query(c, "m"))
var lbpos *int
err := db.QueryRow(fmt.Sprintf(
`SELECT
users.id, users.username,
users_stats.playcount_%s, users_stats.ranked_score_%s, users_stats.total_score_%s,
leaderboard_%s.position, users_stats.pp_%s, users_stats.avg_accuracy_%s,
users_stats.pp_%s, users_stats.avg_accuracy_%s,
users_stats.country
FROM users
LEFT JOIN users_stats ON users_stats.id = users.id
INNER JOIN leaderboard_%s ON leaderboard_%s.user = users.id
%s
LIMIT 1`,
mode, mode, mode, mode, mode, mode, mode, mode, whereClause,
mode, mode, mode, mode, mode, whereClause,
), p).Scan(
&user.UserID, &user.Username,
&user.Playcount, &user.RankedScore, &user.TotalScore,
&lbpos, &user.PP, &user.Accuracy,
&user.PP, &user.Accuracy,
&user.Country,
)
if err != nil {
@ -50,9 +55,9 @@ func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
}
return
}
if lbpos != nil {
user.Rank = *lbpos
}
user.Rank = int(R.ZRevRank("ripple:leaderboard:"+mode, strconv.Itoa(user.UserID)).Val()) + 1
user.CountryRank = int(R.ZRevRank("ripple:leaderboard:"+mode+":"+strings.ToLower(user.Country), strconv.Itoa(user.UserID)).Val()) + 1
user.Level = ocl.GetLevelPrecise(user.TotalScore)
json(c, 200, []osuapi.User{user})

View File

@ -62,6 +62,7 @@ func Start(conf common.Conf, dbO *sqlx.DB) *fhr.Router {
Password: conf.RedisPassword,
DB: conf.RedisDB,
})
peppy.R = red
// token updater
go tokenUpdater(db)

View File

@ -2,6 +2,12 @@ package v1
import (
"fmt"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
redis "gopkg.in/redis.v5"
"zxq.co/ripple/ocl"
"zxq.co/ripple/rippleapi/common"
@ -28,29 +34,48 @@ SELECT
users_stats.ranked_score_%[1]s, users_stats.total_score_%[1]s, users_stats.playcount_%[1]s,
users_stats.replays_watched_%[1]s, users_stats.total_hits_%[1]s,
users_stats.avg_accuracy_%[1]s, users_stats.pp_%[1]s, leaderboard_%[1]s.position as %[1]s_position
FROM leaderboard_%[1]s
INNER JOIN users ON users.id = leaderboard_%[1]s.user
INNER JOIN users_stats ON users_stats.id = leaderboard_%[1]s.user
%[2]s`
users_stats.avg_accuracy_%[1]s, users_stats.pp_%[1]s
FROM users
INNER JOIN users_stats ON users_stats.id = users.id
WHERE users.id IN (?)
`
// LeaderboardGET gets the leaderboard.
func LeaderboardGET(md common.MethodData) common.CodeMessager {
m := getMode(md.Query("mode"))
w := &common.WhereClause{
Clause: "WHERE " + md.User.OnlyUserPublic(md.HasQuery("see_everything")),
// md.Query.Country
p := common.Int(md.Query("p")) - 1
if p < 0 {
p = 0
}
w.Where("users_stats.country = ?", md.Query("country"))
// Admins may not want to see banned users on the leaderboard.
// This is the default setting. In case they do, they have to activate see_everything.
query := fmt.Sprintf(lbUserQuery, m, w.Clause+
` ORDER BY leaderboard_`+m+`.position `+common.Paginate(md.Query("p"), md.Query("l"), 500))
rows, err := md.DB.Query(query, w.Params...)
l := common.InString(1, md.Query("l"), 500, 50)
key := "ripple:leaderboard:" + m
if md.Query("country") != "" {
key += ":" + md.Query("country")
}
results, err := md.R.ZRevRange(key, int64(p*l), int64(p*l+l-1)).Result()
if err != nil {
md.Err(err)
return Err500
}
var resp leaderboardResponse
resp.Code = 200
if len(results) == 0 {
return resp
}
query := fmt.Sprintf(lbUserQuery+` ORDER BY users_stats.pp_%[1]s DESC, users_stats.ranked_score_%[1]s DESC`, m)
query, params, _ := sqlx.In(query, results)
rows, err := md.DB.Query(query, params...)
if err != nil {
md.Err(err)
return Err500
}
for rows.Next() {
var u leaderboardUser
err := rows.Scan(
@ -60,15 +85,28 @@ func LeaderboardGET(md common.MethodData) common.CodeMessager {
&u.ChosenMode.RankedScore, &u.ChosenMode.TotalScore, &u.ChosenMode.PlayCount,
&u.ChosenMode.ReplaysWatched, &u.ChosenMode.TotalHits,
&u.ChosenMode.Accuracy, &u.ChosenMode.PP, &u.ChosenMode.GlobalLeaderboardRank,
&u.ChosenMode.Accuracy, &u.ChosenMode.PP,
)
if err != nil {
md.Err(err)
continue
}
u.ChosenMode.Level = ocl.GetLevelPrecise(int64(u.ChosenMode.TotalScore))
if i := leaderboardPosition(md.R, m, u.ID); i != 0 {
u.ChosenMode.GlobalLeaderboardRank = &i
}
if i := countryPosition(md.R, m, u.ID, u.Country); i != 0 {
u.ChosenMode.CountryLeaderboardRank = &i
}
resp.Users = append(resp.Users, u)
}
resp.Code = 200
return resp
}
func leaderboardPosition(r *redis.Client, mode string, user int) int {
return int(r.ZRevRank("ripple:leaderboard:"+mode, strconv.Itoa(user)).Val()) + 1
}
func countryPosition(r *redis.Client, mode string, user int, country string) int {
return int(r.ZRevRank("ripple:leaderboard:"+mode+":"+strings.ToLower(country), strconv.Itoa(user)).Val()) + 1
}

View File

@ -166,16 +166,24 @@ func UserWhatsTheIDGET(md common.MethodData) common.CodeMessager {
return r
}
var modesToReadable = [...]string{
"std",
"taiko",
"ctb",
"mania",
}
type modeData struct {
RankedScore uint64 `json:"ranked_score"`
TotalScore uint64 `json:"total_score"`
PlayCount int `json:"playcount"`
ReplaysWatched int `json:"replays_watched"`
TotalHits int `json:"total_hits"`
Level float64 `json:"level"`
Accuracy float64 `json:"accuracy"`
PP int `json:"pp"`
GlobalLeaderboardRank *int `json:"global_leaderboard_rank"`
RankedScore uint64 `json:"ranked_score"`
TotalScore uint64 `json:"total_score"`
PlayCount int `json:"playcount"`
ReplaysWatched int `json:"replays_watched"`
TotalHits int `json:"total_hits"`
Level float64 `json:"level"`
Accuracy float64 `json:"accuracy"`
PP int `json:"pp"`
GlobalLeaderboardRank *int `json:"global_leaderboard_rank"`
CountryLeaderboardRank *int `json:"country_leaderboard_rank"`
}
type userFullResponse struct {
common.ResponseBase
@ -214,33 +222,25 @@ SELECT
users_stats.ranked_score_std, users_stats.total_score_std, users_stats.playcount_std,
users_stats.replays_watched_std, users_stats.total_hits_std,
users_stats.avg_accuracy_std, users_stats.pp_std, leaderboard_std.position as std_position,
users_stats.avg_accuracy_std, users_stats.pp_std,
users_stats.ranked_score_taiko, users_stats.total_score_taiko, users_stats.playcount_taiko,
users_stats.replays_watched_taiko, users_stats.total_hits_taiko,
users_stats.avg_accuracy_taiko, users_stats.pp_taiko, leaderboard_taiko.position as taiko_position,
users_stats.avg_accuracy_taiko, users_stats.pp_taiko,
users_stats.ranked_score_ctb, users_stats.total_score_ctb, users_stats.playcount_ctb,
users_stats.replays_watched_ctb, users_stats.total_hits_ctb,
users_stats.avg_accuracy_ctb, users_stats.pp_ctb, leaderboard_ctb.position as ctb_position,
users_stats.avg_accuracy_ctb, users_stats.pp_ctb,
users_stats.ranked_score_mania, users_stats.total_score_mania, users_stats.playcount_mania,
users_stats.replays_watched_mania, users_stats.total_hits_mania,
users_stats.avg_accuracy_mania, users_stats.pp_mania, leaderboard_mania.position as mania_position,
users_stats.avg_accuracy_mania, users_stats.pp_mania,
users.silence_reason, users.silence_end
FROM users
LEFT JOIN users_stats
ON users.id=users_stats.id
LEFT JOIN leaderboard_std
ON users.id=leaderboard_std.user
LEFT JOIN leaderboard_taiko
ON users.id=leaderboard_taiko.user
LEFT JOIN leaderboard_ctb
ON users.id=leaderboard_ctb.user
LEFT JOIN leaderboard_mania
ON users.id=leaderboard_mania.user
WHERE ` + whereClause + ` AND ` + md.User.OnlyUserPublic(true) + `
LIMIT 1
`
@ -261,19 +261,19 @@ LIMIT 1
&r.STD.RankedScore, &r.STD.TotalScore, &r.STD.PlayCount,
&r.STD.ReplaysWatched, &r.STD.TotalHits,
&r.STD.Accuracy, &r.STD.PP, &r.STD.GlobalLeaderboardRank,
&r.STD.Accuracy, &r.STD.PP,
&r.Taiko.RankedScore, &r.Taiko.TotalScore, &r.Taiko.PlayCount,
&r.Taiko.ReplaysWatched, &r.Taiko.TotalHits,
&r.Taiko.Accuracy, &r.Taiko.PP, &r.Taiko.GlobalLeaderboardRank,
&r.Taiko.Accuracy, &r.Taiko.PP,
&r.CTB.RankedScore, &r.CTB.TotalScore, &r.CTB.PlayCount,
&r.CTB.ReplaysWatched, &r.CTB.TotalHits,
&r.CTB.Accuracy, &r.CTB.PP, &r.CTB.GlobalLeaderboardRank,
&r.CTB.Accuracy, &r.CTB.PP,
&r.Mania.RankedScore, &r.Mania.TotalScore, &r.Mania.PlayCount,
&r.Mania.ReplaysWatched, &r.Mania.TotalHits,
&r.Mania.Accuracy, &r.Mania.PP, &r.Mania.GlobalLeaderboardRank,
&r.Mania.Accuracy, &r.Mania.PP,
&r.SilenceInfo.Reason, &r.SilenceInfo.End,
)
@ -290,8 +290,15 @@ LIMIT 1
r.CustomBadge = &b
}
for _, m := range []*modeData{&r.STD, &r.Taiko, &r.CTB, &r.Mania} {
for modeID, m := range [...]*modeData{&r.STD, &r.Taiko, &r.CTB, &r.Mania} {
m.Level = ocl.GetLevelPrecise(int64(m.TotalScore))
if i := leaderboardPosition(md.R, modesToReadable[modeID], r.ID); i != 0 {
m.GlobalLeaderboardRank = &i
}
if i := countryPosition(md.R, modesToReadable[modeID], r.ID, r.Country); i != 0 {
m.CountryLeaderboardRank = &i
}
}
rows, err := md.DB.Query("SELECT b.id, b.name, b.icon FROM user_badges ub "+