From da2a36537e5352ee326bef0d189d12c521590a4c Mon Sep 17 00:00:00 2001 From: Howl Date: Thu, 19 May 2016 17:15:17 +0200 Subject: [PATCH] Implement user/scores/best --- app/start.go | 1 + app/v1/beatmap.go | 41 ++++++++++++-- app/v1/friend.go | 2 +- app/v1/user_scores.go | 122 ++++++++++++++++++++++++++++++++++++++++-- common/paginate.go | 5 +- 5 files changed, 163 insertions(+), 8 deletions(-) diff --git a/app/start.go b/app/start.go index d99c6cc..c8c6c21 100644 --- a/app/start.go +++ b/app/start.go @@ -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/userpage", Method(v1.UserUserpageGET, 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)) // ReadConfidential privilege required diff --git a/app/v1/beatmap.go b/app/v1/beatmap.go index 401f85e..e738112 100644 --- a/app/v1/beatmap.go +++ b/app/v1/beatmap.go @@ -5,18 +5,53 @@ import "git.zxq.co/ripple/rippleapi/common" type beatmap struct { BeatmapID int `json:"beatmap_id"` BeatmapsetID int `json:"beatmapset_id"` - BeatmapMD5 int `json:"beatmap_md5"` - SongName int `json:"song_name"` + BeatmapMD5 string `json:"beatmap_md5"` + SongName string `json:"song_name"` AR float32 `json:"ar"` OD float32 `json:"od"` Difficulty float64 `json:"difficulty"` MaxCombo int `json:"max_combo"` HitLength int `json:"hit_length"` - BPM float64 `json:"bpm"` Ranked int `json:"ranked"` RankedStatusFrozen int `json:"ranked_status_frozen"` 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 { common.ResponseBase beatmap diff --git a/app/v1/friend.go b/app/v1/friend.go index 39f8ca6..d5410b2 100644 --- a/app/v1/friend.go +++ b/app/v1/friend.go @@ -56,7 +56,7 @@ ON users_relationships.user2=users_stats.id WHERE users_relationships.user1=? 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 { md.Err(err) return Err500 diff --git a/app/v1/user_scores.go b/app/v1/user_scores.go index 24144e7..c877c8e 100644 --- a/app/v1/user_scores.go +++ b/app/v1/user_scores.go @@ -1,9 +1,15 @@ package v1 -import "time" +import ( + "fmt" + "strconv" + "time" -type userScore struct { - ScoreID int `json:"score_id"` + "git.zxq.co/ripple/rippleapi/common" +) + +type score struct { + ID int `json:"id"` BeatmapMD5 string `json:"beatmap_md5"` Score int64 `json:"score"` MaxCombo int `json:"max_combo"` @@ -18,4 +24,114 @@ type userScore struct { Time time.Time `json:"time"` PlayMode int `json:"play_mode"` 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 } diff --git a/common/paginate.go b/common/paginate.go index e679e13..a175fc4 100644 --- a/common/paginate.go +++ b/common/paginate.go @@ -6,7 +6,7 @@ import ( ) // Paginate creates an additional SQL LIMIT clause for paginating. -func Paginate(page, limit string) string { +func Paginate(page, limit string, maxLimit int) string { var ( pInt int lInt int @@ -34,6 +34,9 @@ func Paginate(page, limit string) string { if lInt < 1 { lInt = 50 } + if lInt > maxLimit { + lInt = maxLimit + } start := (pInt - 1) * lInt return fmt.Sprintf(" LIMIT %d,%d ", start, lInt) }