Implement user/scores/best

This commit is contained in:
Howl 2016-05-19 17:15:17 +02:00
parent 45c67cf9b7
commit da2a36537e
5 changed files with 163 additions and 8 deletions

View File

@ -37,6 +37,7 @@ func Start(conf common.Conf, dbO *sql.DB) *gin.Engine {
gv1.GET("/users/full", Method(v1.UserFullGET, common.PrivilegeRead)) gv1.GET("/users/full", Method(v1.UserFullGET, common.PrivilegeRead))
gv1.GET("/users/userpage", Method(v1.UserUserpageGET, common.PrivilegeRead)) gv1.GET("/users/userpage", Method(v1.UserUserpageGET, common.PrivilegeRead))
gv1.GET("/users/lookup", Method(v1.UserLookupGET, common.PrivilegeRead)) gv1.GET("/users/lookup", Method(v1.UserLookupGET, common.PrivilegeRead))
gv1.GET("/users/scores/best", Method(v1.UserScoresBestGET, common.PrivilegeRead))
gv1.GET("/badges", Method(v1.BadgesGET, common.PrivilegeRead)) gv1.GET("/badges", Method(v1.BadgesGET, common.PrivilegeRead))
// ReadConfidential privilege required // ReadConfidential privilege required

View File

@ -5,18 +5,53 @@ import "git.zxq.co/ripple/rippleapi/common"
type beatmap struct { type beatmap struct {
BeatmapID int `json:"beatmap_id"` BeatmapID int `json:"beatmap_id"`
BeatmapsetID int `json:"beatmapset_id"` BeatmapsetID int `json:"beatmapset_id"`
BeatmapMD5 int `json:"beatmap_md5"` BeatmapMD5 string `json:"beatmap_md5"`
SongName int `json:"song_name"` SongName string `json:"song_name"`
AR float32 `json:"ar"` AR float32 `json:"ar"`
OD float32 `json:"od"` OD float32 `json:"od"`
Difficulty float64 `json:"difficulty"` Difficulty float64 `json:"difficulty"`
MaxCombo int `json:"max_combo"` MaxCombo int `json:"max_combo"`
HitLength int `json:"hit_length"` HitLength int `json:"hit_length"`
BPM float64 `json:"bpm"`
Ranked int `json:"ranked"` Ranked int `json:"ranked"`
RankedStatusFrozen int `json:"ranked_status_frozen"` RankedStatusFrozen int `json:"ranked_status_frozen"`
LatestUpdate int `json:"latest_update"` LatestUpdate int `json:"latest_update"`
} }
type beatmapMayOrMayNotExist struct {
BeatmapID *int
BeatmapsetID *int
BeatmapMD5 *string
SongName *string
AR *float32
OD *float32
Difficulty *float64
MaxCombo *int
HitLength *int
Ranked *int
RankedStatusFrozen *int
LatestUpdate *int
}
func (b *beatmapMayOrMayNotExist) toBeatmap() *beatmap {
if b.BeatmapID == nil {
return nil
}
return &beatmap{
BeatmapID: *b.BeatmapID,
BeatmapsetID: *b.BeatmapsetID,
BeatmapMD5: *b.BeatmapMD5,
SongName: *b.SongName,
AR: *b.AR,
OD: *b.OD,
Difficulty: *b.Difficulty,
MaxCombo: *b.MaxCombo,
HitLength: *b.HitLength,
Ranked: *b.Ranked,
RankedStatusFrozen: *b.RankedStatusFrozen,
LatestUpdate: *b.LatestUpdate,
}
}
type beatmapResponse struct { type beatmapResponse struct {
common.ResponseBase common.ResponseBase
beatmap beatmap

View File

@ -56,7 +56,7 @@ ON users_relationships.user2=users_stats.id
WHERE users_relationships.user1=? WHERE users_relationships.user1=?
ORDER BY users_relationships.id` ORDER BY users_relationships.id`
results, err := md.DB.Query(myFriendsQuery+common.Paginate(md.C.Query("p"), md.C.Query("l")), md.ID()) results, err := md.DB.Query(myFriendsQuery+common.Paginate(md.C.Query("p"), md.C.Query("l"), 50), md.ID())
if err != nil { if err != nil {
md.Err(err) md.Err(err)
return Err500 return Err500

View File

@ -1,9 +1,15 @@
package v1 package v1
import "time" import (
"fmt"
"strconv"
"time"
type userScore struct { "git.zxq.co/ripple/rippleapi/common"
ScoreID int `json:"score_id"` )
type score struct {
ID int `json:"id"`
BeatmapMD5 string `json:"beatmap_md5"` BeatmapMD5 string `json:"beatmap_md5"`
Score int64 `json:"score"` Score int64 `json:"score"`
MaxCombo int `json:"max_combo"` MaxCombo int `json:"max_combo"`
@ -18,4 +24,114 @@ type userScore struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
PlayMode int `json:"play_mode"` PlayMode int `json:"play_mode"`
Accuracy float64 `json:"accuracy"` Accuracy float64 `json:"accuracy"`
PP float32 `json:"pp"`
}
type userScore struct {
score
Beatmap *beatmap `json:"beatmap"`
}
type userScoresResponse struct {
common.ResponseBase
Scores []userScore `json:"scores"`
}
const userScoreSelectBase = `
SELECT
scores.id, scores.beatmap_md5, scores.score,
scores.max_combo, scores.full_combo, scores.mods,
scores.300_count, scores.100_count, scores.50_count,
scores.gekis_count, scores.katus_count, scores.misses_count,
scores.time, scores.play_mode, scores.accuracy, scores.pp,
beatmaps.beatmap_id, beatmaps.beatmapset_id, beatmaps.beatmap_md5,
beatmaps.song_name, beatmaps.ar, beatmaps.od, beatmaps.difficulty,
beatmaps.max_combo, beatmaps.hit_length, beatmaps.ranked,
beatmaps.ranked_status_freezed, beatmaps.latest_update
FROM scores
LEFT JOIN beatmaps ON beatmaps.beatmap_md5 = scores.beatmap_md5
LEFT JOIN users ON users.username = scores.username
`
// UserScoresBestGET retrieves the best scores of an user, sorted by PP if
// mode is standard and sorted by ranked score otherwise.
func UserScoresBestGET(md common.MethodData) common.CodeMessager {
cm, wc, param := whereClauseUser(md, "users")
if cm != nil {
return *cm
}
var modeClause string
if md.C.Query("mode") != "" {
m, err := strconv.Atoi(md.C.Query("mode"))
if err == nil && m >= 0 && m <= 3 {
modeClause = fmt.Sprintf("AND scores.play_mode = '%d'", m)
}
}
return scoresPuts(md, fmt.Sprintf(
`WHERE
scores.completed = '3'
AND %s
%s
AND users.allowed = '1'
ORDER BY scores.pp DESC, scores.score DESC %s`,
wc, modeClause, common.Paginate(md.C.Query("p"), md.C.Query("l"), 100),
), param)
}
func getMode(m string) string {
switch m {
case "1":
return "taiko"
case "2":
return "ctb"
case "3":
return "mania"
default:
return "std"
}
}
func scoresPuts(md common.MethodData, whereClause string, params ...interface{}) common.CodeMessager {
rows, err := md.DB.Query(userScoreSelectBase+whereClause, params...)
if err != nil {
md.Err(err)
return Err500
}
var scores []userScore
for rows.Next() {
var (
us userScore
t string
b beatmapMayOrMayNotExist
)
err = rows.Scan(
&us.ID, &us.BeatmapMD5, &us.Score,
&us.MaxCombo, &us.FullCombo, &us.Mods,
&us.Count300, &us.Count100, &us.Count50,
&us.CountGeki, &us.CountKatu, &us.CountMiss,
&t, &us.PlayMode, &us.Accuracy, &us.PP,
&b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5,
&b.SongName, &b.AR, &b.OD, &b.Difficulty,
&b.MaxCombo, &b.HitLength, &b.Ranked,
&b.RankedStatusFrozen, &b.LatestUpdate,
)
if err != nil {
md.Err(err)
return Err500
}
// puck feppy
us.Time, err = time.Parse("060102150405", t)
if err != nil {
md.Err(err)
return Err500
}
us.Beatmap = b.toBeatmap()
scores = append(scores, us)
}
r := userScoresResponse{}
r.Code = 200
r.Scores = scores
return r
} }

View File

@ -6,7 +6,7 @@ import (
) )
// Paginate creates an additional SQL LIMIT clause for paginating. // Paginate creates an additional SQL LIMIT clause for paginating.
func Paginate(page, limit string) string { func Paginate(page, limit string, maxLimit int) string {
var ( var (
pInt int pInt int
lInt int lInt int
@ -34,6 +34,9 @@ func Paginate(page, limit string) string {
if lInt < 1 { if lInt < 1 {
lInt = 50 lInt = 50
} }
if lInt > maxLimit {
lInt = maxLimit
}
start := (pInt - 1) * lInt start := (pInt - 1) * lInt
return fmt.Sprintf(" LIMIT %d,%d ", start, lInt) return fmt.Sprintf(" LIMIT %d,%d ", start, lInt)
} }