Implement country leaderboards
This commit is contained in:
parent
120c027fce
commit
622658f5aa
|
@ -4,14 +4,21 @@ package peppy
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/thehowl/go-osuapi"
|
"github.com/thehowl/go-osuapi"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
"gopkg.in/redis.v5"
|
||||||
"zxq.co/ripple/ocl"
|
"zxq.co/ripple/ocl"
|
||||||
"zxq.co/ripple/rippleapi/common"
|
"zxq.co/ripple/rippleapi/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// R is a redis client.
|
||||||
|
var R *redis.Client
|
||||||
|
|
||||||
// GetUser retrieves general user information.
|
// GetUser retrieves general user information.
|
||||||
func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
|
func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
|
||||||
if query(c, "u") == "" {
|
if query(c, "u") == "" {
|
||||||
|
@ -24,23 +31,21 @@ func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
|
||||||
|
|
||||||
mode := genmode(query(c, "m"))
|
mode := genmode(query(c, "m"))
|
||||||
|
|
||||||
var lbpos *int
|
|
||||||
err := db.QueryRow(fmt.Sprintf(
|
err := db.QueryRow(fmt.Sprintf(
|
||||||
`SELECT
|
`SELECT
|
||||||
users.id, users.username,
|
users.id, users.username,
|
||||||
users_stats.playcount_%s, users_stats.ranked_score_%s, users_stats.total_score_%s,
|
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
|
users_stats.country
|
||||||
FROM users
|
FROM users
|
||||||
LEFT JOIN users_stats ON users_stats.id = users.id
|
LEFT JOIN users_stats ON users_stats.id = users.id
|
||||||
INNER JOIN leaderboard_%s ON leaderboard_%s.user = users.id
|
|
||||||
%s
|
%s
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
mode, mode, mode, mode, mode, mode, mode, mode, whereClause,
|
mode, mode, mode, mode, mode, whereClause,
|
||||||
), p).Scan(
|
), p).Scan(
|
||||||
&user.UserID, &user.Username,
|
&user.UserID, &user.Username,
|
||||||
&user.Playcount, &user.RankedScore, &user.TotalScore,
|
&user.Playcount, &user.RankedScore, &user.TotalScore,
|
||||||
&lbpos, &user.PP, &user.Accuracy,
|
&user.PP, &user.Accuracy,
|
||||||
&user.Country,
|
&user.Country,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -50,9 +55,9 @@ func GetUser(c *fasthttp.RequestCtx, db *sqlx.DB) {
|
||||||
}
|
}
|
||||||
return
|
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)
|
user.Level = ocl.GetLevelPrecise(user.TotalScore)
|
||||||
|
|
||||||
json(c, 200, []osuapi.User{user})
|
json(c, 200, []osuapi.User{user})
|
||||||
|
|
|
@ -62,6 +62,7 @@ func Start(conf common.Conf, dbO *sqlx.DB) *fhr.Router {
|
||||||
Password: conf.RedisPassword,
|
Password: conf.RedisPassword,
|
||||||
DB: conf.RedisDB,
|
DB: conf.RedisDB,
|
||||||
})
|
})
|
||||||
|
peppy.R = red
|
||||||
|
|
||||||
// token updater
|
// token updater
|
||||||
go tokenUpdater(db)
|
go tokenUpdater(db)
|
||||||
|
|
|
@ -2,6 +2,12 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
redis "gopkg.in/redis.v5"
|
||||||
|
|
||||||
"zxq.co/ripple/ocl"
|
"zxq.co/ripple/ocl"
|
||||||
"zxq.co/ripple/rippleapi/common"
|
"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.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.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
|
users_stats.avg_accuracy_%[1]s, users_stats.pp_%[1]s
|
||||||
FROM leaderboard_%[1]s
|
FROM users
|
||||||
INNER JOIN users ON users.id = leaderboard_%[1]s.user
|
INNER JOIN users_stats ON users_stats.id = users.id
|
||||||
INNER JOIN users_stats ON users_stats.id = leaderboard_%[1]s.user
|
WHERE users.id IN (?)
|
||||||
%[2]s`
|
`
|
||||||
|
|
||||||
// LeaderboardGET gets the leaderboard.
|
// LeaderboardGET gets the leaderboard.
|
||||||
func LeaderboardGET(md common.MethodData) common.CodeMessager {
|
func LeaderboardGET(md common.MethodData) common.CodeMessager {
|
||||||
m := getMode(md.Query("mode"))
|
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"))
|
l := common.InString(1, md.Query("l"), 500, 50)
|
||||||
// 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.
|
key := "ripple:leaderboard:" + m
|
||||||
query := fmt.Sprintf(lbUserQuery, m, w.Clause+
|
if md.Query("country") != "" {
|
||||||
` ORDER BY leaderboard_`+m+`.position `+common.Paginate(md.Query("p"), md.Query("l"), 500))
|
key += ":" + md.Query("country")
|
||||||
rows, err := md.DB.Query(query, w.Params...)
|
}
|
||||||
|
|
||||||
|
results, err := md.R.ZRevRange(key, int64(p*l), int64(p*l+l-1)).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
return Err500
|
return Err500
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp leaderboardResponse
|
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() {
|
for rows.Next() {
|
||||||
var u leaderboardUser
|
var u leaderboardUser
|
||||||
err := rows.Scan(
|
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.RankedScore, &u.ChosenMode.TotalScore, &u.ChosenMode.PlayCount,
|
||||||
&u.ChosenMode.ReplaysWatched, &u.ChosenMode.TotalHits,
|
&u.ChosenMode.ReplaysWatched, &u.ChosenMode.TotalHits,
|
||||||
&u.ChosenMode.Accuracy, &u.ChosenMode.PP, &u.ChosenMode.GlobalLeaderboardRank,
|
&u.ChosenMode.Accuracy, &u.ChosenMode.PP,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
u.ChosenMode.Level = ocl.GetLevelPrecise(int64(u.ChosenMode.TotalScore))
|
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.Users = append(resp.Users, u)
|
||||||
}
|
}
|
||||||
resp.Code = 200
|
|
||||||
return resp
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -166,6 +166,13 @@ func UserWhatsTheIDGET(md common.MethodData) common.CodeMessager {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var modesToReadable = [...]string{
|
||||||
|
"std",
|
||||||
|
"taiko",
|
||||||
|
"ctb",
|
||||||
|
"mania",
|
||||||
|
}
|
||||||
|
|
||||||
type modeData struct {
|
type modeData struct {
|
||||||
RankedScore uint64 `json:"ranked_score"`
|
RankedScore uint64 `json:"ranked_score"`
|
||||||
TotalScore uint64 `json:"total_score"`
|
TotalScore uint64 `json:"total_score"`
|
||||||
|
@ -176,6 +183,7 @@ type modeData struct {
|
||||||
Accuracy float64 `json:"accuracy"`
|
Accuracy float64 `json:"accuracy"`
|
||||||
PP int `json:"pp"`
|
PP int `json:"pp"`
|
||||||
GlobalLeaderboardRank *int `json:"global_leaderboard_rank"`
|
GlobalLeaderboardRank *int `json:"global_leaderboard_rank"`
|
||||||
|
CountryLeaderboardRank *int `json:"country_leaderboard_rank"`
|
||||||
}
|
}
|
||||||
type userFullResponse struct {
|
type userFullResponse struct {
|
||||||
common.ResponseBase
|
common.ResponseBase
|
||||||
|
@ -214,33 +222,25 @@ SELECT
|
||||||
|
|
||||||
users_stats.ranked_score_std, users_stats.total_score_std, users_stats.playcount_std,
|
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.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.ranked_score_taiko, users_stats.total_score_taiko, users_stats.playcount_taiko,
|
||||||
users_stats.replays_watched_taiko, users_stats.total_hits_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.ranked_score_ctb, users_stats.total_score_ctb, users_stats.playcount_ctb,
|
||||||
users_stats.replays_watched_ctb, users_stats.total_hits_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.ranked_score_mania, users_stats.total_score_mania, users_stats.playcount_mania,
|
||||||
users_stats.replays_watched_mania, users_stats.total_hits_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
|
users.silence_reason, users.silence_end
|
||||||
|
|
||||||
FROM users
|
FROM users
|
||||||
LEFT JOIN users_stats
|
LEFT JOIN users_stats
|
||||||
ON users.id=users_stats.id
|
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) + `
|
WHERE ` + whereClause + ` AND ` + md.User.OnlyUserPublic(true) + `
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`
|
||||||
|
@ -261,19 +261,19 @@ LIMIT 1
|
||||||
|
|
||||||
&r.STD.RankedScore, &r.STD.TotalScore, &r.STD.PlayCount,
|
&r.STD.RankedScore, &r.STD.TotalScore, &r.STD.PlayCount,
|
||||||
&r.STD.ReplaysWatched, &r.STD.TotalHits,
|
&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.RankedScore, &r.Taiko.TotalScore, &r.Taiko.PlayCount,
|
||||||
&r.Taiko.ReplaysWatched, &r.Taiko.TotalHits,
|
&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.RankedScore, &r.CTB.TotalScore, &r.CTB.PlayCount,
|
||||||
&r.CTB.ReplaysWatched, &r.CTB.TotalHits,
|
&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.RankedScore, &r.Mania.TotalScore, &r.Mania.PlayCount,
|
||||||
&r.Mania.ReplaysWatched, &r.Mania.TotalHits,
|
&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,
|
&r.SilenceInfo.Reason, &r.SilenceInfo.End,
|
||||||
)
|
)
|
||||||
|
@ -290,8 +290,15 @@ LIMIT 1
|
||||||
r.CustomBadge = &b
|
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))
|
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 "+
|
rows, err := md.DB.Query("SELECT b.id, b.name, b.icon FROM user_badges ub "+
|
||||||
|
|
Loading…
Reference in New Issue
Block a user