Implement GET scores in official ripple api
This commit is contained in:
parent
346f26177c
commit
a6ca8de13e
14
app/start.go
14
app/start.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/gin-gonic/contrib/gzip"
|
"github.com/gin-gonic/contrib/gzip"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/serenize/snaker"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -18,11 +19,23 @@ var (
|
||||||
cf common.Conf
|
cf common.Conf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var commonClusterfucks = map[string]string{
|
||||||
|
"RegisteredOn": "register_datetime",
|
||||||
|
"UsernameAKA": "username_aka",
|
||||||
|
}
|
||||||
|
|
||||||
// Start begins taking HTTP connections.
|
// Start begins taking HTTP connections.
|
||||||
func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine {
|
func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine {
|
||||||
db = dbO
|
db = dbO
|
||||||
cf = conf
|
cf = conf
|
||||||
|
|
||||||
|
db.MapperFunc(func(s string) string {
|
||||||
|
if x, ok := commonClusterfucks[s]; ok {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return snaker.CamelToSnake(s)
|
||||||
|
})
|
||||||
|
|
||||||
setUpLimiter()
|
setUpLimiter()
|
||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
@ -68,6 +81,7 @@ func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine {
|
||||||
gv1.GET("/tokens/self", Method(v1.TokenSelfGET))
|
gv1.GET("/tokens/self", Method(v1.TokenSelfGET))
|
||||||
gv1.GET("/blog/posts", Method(v1.BlogPostsGET))
|
gv1.GET("/blog/posts", Method(v1.BlogPostsGET))
|
||||||
gv1.GET("/blog/posts/content", Method(v1.BlogPostsContentGET))
|
gv1.GET("/blog/posts/content", Method(v1.BlogPostsContentGET))
|
||||||
|
gv1.GET("/scores", Method(v1.ScoresGET))
|
||||||
|
|
||||||
// ReadConfidential privilege required
|
// ReadConfidential privilege required
|
||||||
gv1.GET("/friends", Method(v1.FriendsGET, common.PrivilegeReadConfidential))
|
gv1.GET("/friends", Method(v1.FriendsGET, common.PrivilegeReadConfidential))
|
||||||
|
|
|
@ -24,9 +24,9 @@ func BadgesGET(md common.MethodData) common.CodeMessager {
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if md.C.Query("id") != "" {
|
if md.Query("id") != "" {
|
||||||
// TODO(howl): ID validation
|
// TODO(howl): ID validation
|
||||||
rows, err = md.DB.Query("SELECT id, name, icon FROM badges WHERE id = ?", md.C.Query("id"))
|
rows, err = md.DB.Query("SELECT id, name, icon FROM badges WHERE id = ?", md.Query("id"))
|
||||||
} else {
|
} else {
|
||||||
rows, err = md.DB.Query("SELECT id, name, icon FROM badges")
|
rows, err = md.DB.Query("SELECT id, name, icon FROM badges")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,14 +87,14 @@ func BeatmapSetStatusPOST(md common.MethodData) common.CodeMessager {
|
||||||
|
|
||||||
// BeatmapGET retrieves a beatmap.
|
// BeatmapGET retrieves a beatmap.
|
||||||
func BeatmapGET(md common.MethodData) common.CodeMessager {
|
func BeatmapGET(md common.MethodData) common.CodeMessager {
|
||||||
if md.C.Query("s") == "" && md.C.Query("b") == "" {
|
if md.Query("s") == "" && md.Query("b") == "" {
|
||||||
return common.SimpleResponse(400, "Must pass either querystring param 'b' or 's'")
|
return common.SimpleResponse(400, "Must pass either querystring param 'b' or 's'")
|
||||||
}
|
}
|
||||||
setID := common.Int(md.C.Query("s"))
|
setID := common.Int(md.Query("s"))
|
||||||
if setID != 0 {
|
if setID != 0 {
|
||||||
return getSet(md, setID)
|
return getSet(md, setID)
|
||||||
}
|
}
|
||||||
beatmapID := common.Int(md.C.Query("b"))
|
beatmapID := common.Int(md.Query("b"))
|
||||||
if beatmapID != 0 {
|
if beatmapID != 0 {
|
||||||
return getBeatmap(md, beatmapID)
|
return getBeatmap(md, beatmapID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@ type blogPostsResponse struct {
|
||||||
func BlogPostsGET(md common.MethodData) common.CodeMessager {
|
func BlogPostsGET(md common.MethodData) common.CodeMessager {
|
||||||
var and string
|
var and string
|
||||||
var params []interface{}
|
var params []interface{}
|
||||||
if md.C.Query("id") != "" {
|
if md.Query("id") != "" {
|
||||||
and = "b.id = ?"
|
and = "b.id = ?"
|
||||||
params = append(params, md.C.Query("id"))
|
params = append(params, md.Query("id"))
|
||||||
}
|
}
|
||||||
rows, err := md.DB.Query(`
|
rows, err := md.DB.Query(`
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -37,7 +37,7 @@ func BlogPostsGET(md common.MethodData) common.CodeMessager {
|
||||||
LEFT JOIN users u ON b.author = u.id
|
LEFT JOIN users u ON b.author = u.id
|
||||||
LEFT JOIN users_stats s ON b.author = s.id
|
LEFT JOIN users_stats s ON b.author = s.id
|
||||||
WHERE status = "published" `+and+`
|
WHERE status = "published" `+and+`
|
||||||
ORDER BY b.id DESC `+common.Paginate(md.C.Query("p"), md.C.Query("l"), 50), params...)
|
ORDER BY b.id DESC `+common.Paginate(md.Query("p"), md.Query("l"), 50), params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
return Err500
|
return Err500
|
||||||
|
@ -79,12 +79,12 @@ func BlogPostsContentGET(md common.MethodData) common.CodeMessager {
|
||||||
val string
|
val string
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case md.C.Query("slug") != "":
|
case md.Query("slug") != "":
|
||||||
by = "slug"
|
by = "slug"
|
||||||
val = md.C.Query("slug")
|
val = md.Query("slug")
|
||||||
case md.C.Query("id") != "":
|
case md.Query("id") != "":
|
||||||
by = "id"
|
by = "id"
|
||||||
val = md.C.Query("id")
|
val = md.Query("id")
|
||||||
default:
|
default:
|
||||||
return ErrMissingField("id|slug")
|
return ErrMissingField("id|slug")
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ type docResponse struct {
|
||||||
// DocGET retrieves a list of documentation files.
|
// DocGET retrieves a list of documentation files.
|
||||||
func DocGET(md common.MethodData) common.CodeMessager {
|
func DocGET(md common.MethodData) common.CodeMessager {
|
||||||
var wc string
|
var wc string
|
||||||
if !md.User.Privileges.HasPrivilegeBlog() || md.C.Query("public") == "1" {
|
if !md.User.Privileges.HasPrivilegeBlog() || md.Query("public") == "1" {
|
||||||
wc = "WHERE public = '1'"
|
wc = "WHERE public = '1'"
|
||||||
}
|
}
|
||||||
rows, err := md.DB.Query("SELECT id, doc_name, public, is_rule FROM docs " + wc)
|
rows, err := md.DB.Query("SELECT id, doc_name, public, is_rule FROM docs " + wc)
|
||||||
|
@ -50,12 +50,12 @@ type docContentResponse struct {
|
||||||
|
|
||||||
// DocContentGET retrieves the raw markdown file of a doc file
|
// DocContentGET retrieves the raw markdown file of a doc file
|
||||||
func DocContentGET(md common.MethodData) common.CodeMessager {
|
func DocContentGET(md common.MethodData) common.CodeMessager {
|
||||||
docID := common.Int(md.C.Query("id"))
|
docID := common.Int(md.Query("id"))
|
||||||
if docID == 0 {
|
if docID == 0 {
|
||||||
return common.SimpleResponse(404, "Documentation file not found!")
|
return common.SimpleResponse(404, "Documentation file not found!")
|
||||||
}
|
}
|
||||||
var wc string
|
var wc string
|
||||||
if !md.User.Privileges.HasPrivilegeBlog() || md.C.Query("public") == "1" {
|
if !md.User.Privileges.HasPrivilegeBlog() || md.Query("public") == "1" {
|
||||||
wc = "AND public = '1'"
|
wc = "AND public = '1'"
|
||||||
}
|
}
|
||||||
var r docContentResponse
|
var r docContentResponse
|
||||||
|
|
|
@ -16,6 +16,6 @@ var (
|
||||||
func ErrMissingField(missingFields ...string) common.CodeMessager {
|
func ErrMissingField(missingFields ...string) common.CodeMessager {
|
||||||
return common.ResponseBase{
|
return common.ResponseBase{
|
||||||
Code: 422, // http://stackoverflow.com/a/10323055/5328069
|
Code: 422, // http://stackoverflow.com/a/10323055/5328069
|
||||||
Message: "Missing fields: " + strings.Join(missingFields, ", ") + ".",
|
Message: "Missing parameters: " + strings.Join(missingFields, ", ") + ".",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,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"), 50), md.ID())
|
results, err := md.DB.Query(myFriendsQuery+common.Paginate(md.Query("p"), md.Query("l"), 50), md.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
return Err500
|
return Err500
|
||||||
|
@ -105,7 +105,7 @@ type friendsWithResponse struct {
|
||||||
func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
||||||
var r friendsWithResponse
|
var r friendsWithResponse
|
||||||
r.Code = 200
|
r.Code = 200
|
||||||
uid := common.Int(md.C.Query("id"))
|
uid := common.Int(md.Query("id"))
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
||||||
|
|
||||||
// FriendsAddGET is the GET version of FriendsAddPOST.
|
// FriendsAddGET is the GET version of FriendsAddPOST.
|
||||||
func FriendsAddGET(md common.MethodData) common.CodeMessager {
|
func FriendsAddGET(md common.MethodData) common.CodeMessager {
|
||||||
return addFriend(md, common.Int(md.C.Query("id")))
|
return addFriend(md, common.Int(md.Query("id")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFriend(md common.MethodData, u int) common.CodeMessager {
|
func addFriend(md common.MethodData, u int) common.CodeMessager {
|
||||||
|
@ -166,7 +166,7 @@ func userExists(md common.MethodData, u int) (r bool) {
|
||||||
|
|
||||||
// FriendsDelGET is the GET version of FriendDelPOST.
|
// FriendsDelGET is the GET version of FriendDelPOST.
|
||||||
func FriendsDelGET(md common.MethodData) common.CodeMessager {
|
func FriendsDelGET(md common.MethodData) common.CodeMessager {
|
||||||
return delFriend(md, common.Int(md.C.Query("id")))
|
return delFriend(md, common.Int(md.Query("id")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func delFriend(md common.MethodData, u int) common.CodeMessager {
|
func delFriend(md common.MethodData, u int) common.CodeMessager {
|
||||||
|
|
|
@ -35,9 +35,9 @@ INNER JOIN users_stats ON users_stats.id = leaderboard_%[1]s.user
|
||||||
|
|
||||||
// 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.C.Query("mode"))
|
m := getMode(md.Query("mode"))
|
||||||
query := fmt.Sprintf(lbUserQuery, m, `WHERE users.privileges & 1 > 0 ORDER BY leaderboard_`+m+`.position `+
|
query := fmt.Sprintf(lbUserQuery, m, `WHERE users.privileges & 1 > 0 ORDER BY leaderboard_`+m+`.position `+
|
||||||
common.Paginate(md.C.Query("p"), md.C.Query("l"), 100))
|
common.Paginate(md.Query("p"), md.Query("l"), 100))
|
||||||
rows, err := md.DB.Query(query)
|
rows, err := md.DB.Query(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
|
|
|
@ -50,5 +50,5 @@ LEFT JOIN users_stats
|
||||||
ON users.id=users_stats.id
|
ON users.id=users_stats.id
|
||||||
WHERE users.id=?
|
WHERE users.id=?
|
||||||
LIMIT 1`
|
LIMIT 1`
|
||||||
return userPuts(md, md.DB.QueryRow(query, data.UserID))
|
return userPuts(md, md.DB.QueryRowx(query, data.UserID))
|
||||||
}
|
}
|
||||||
|
|
115
app/v1/score.go
Normal file
115
app/v1/score.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"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"`
|
||||||
|
FullCombo bool `json:"full_combo"`
|
||||||
|
Mods int `json:"mods"`
|
||||||
|
Count300 int `json:"count_300"`
|
||||||
|
Count100 int `json:"count_100"`
|
||||||
|
Count50 int `json:"count_50"`
|
||||||
|
CountGeki int `json:"count_geki"`
|
||||||
|
CountKatu int `json:"count_katu"`
|
||||||
|
CountMiss int `json:"count_miss"`
|
||||||
|
Time common.OsuTime `json:"time"`
|
||||||
|
PlayMode int `json:"play_mode"`
|
||||||
|
Accuracy float64 `json:"accuracy"`
|
||||||
|
PP float32 `json:"pp"`
|
||||||
|
Completed int `json:"completed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// beatmapScore is to differentiate from userScore, as beatmapScore contains
|
||||||
|
// also an user, while userScore contains the beatmap.
|
||||||
|
type beatmapScore struct {
|
||||||
|
score
|
||||||
|
User userData `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type scoresResponse struct {
|
||||||
|
common.ResponseBase
|
||||||
|
Scores []beatmapScore `json:"scores"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScoresGET retrieves the top scores for a certain beatmap.
|
||||||
|
func ScoresGET(md common.MethodData) common.CodeMessager {
|
||||||
|
var (
|
||||||
|
beatmapMD5 string
|
||||||
|
r scoresResponse
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case md.Query("md5") != "":
|
||||||
|
beatmapMD5 = md.Query("md5")
|
||||||
|
case md.Query("b") != "":
|
||||||
|
err := md.DB.Get(&beatmapMD5, "SELECT beatmap_md5 FROM beatmaps WHERE beatmap_id = ? LIMIT 1", md.Query("b"))
|
||||||
|
switch {
|
||||||
|
case err == sql.ErrNoRows:
|
||||||
|
r.Code = 200
|
||||||
|
return r
|
||||||
|
case err != nil:
|
||||||
|
md.Err(err)
|
||||||
|
return Err500
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrMissingField("md5|b")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort := common.Sort(md, common.SortConfiguration{
|
||||||
|
Default: "scores.pp DESC, scores.score DESC",
|
||||||
|
Table: "scores",
|
||||||
|
Allowed: []string{"pp", "score", "accuracy", "time", "id"},
|
||||||
|
})
|
||||||
|
|
||||||
|
rows, err := md.DB.Query(`
|
||||||
|
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,
|
||||||
|
scores.completed,
|
||||||
|
|
||||||
|
users.id, users.username, users.register_datetime, users.privileges,
|
||||||
|
users.latest_activity, users_stats.username_aka, users_stats.country
|
||||||
|
FROM scores
|
||||||
|
INNER JOIN users ON users.id = scores.userid
|
||||||
|
INNER JOIN users_stats ON users_stats.id = scores.userid
|
||||||
|
WHERE scores.beatmap_md5 = ? AND scores.completed = '3'
|
||||||
|
`+sort+common.Paginate(md.Query("p"), md.Query("l"), 100), beatmapMD5)
|
||||||
|
if err != nil {
|
||||||
|
md.Err(err)
|
||||||
|
return Err500
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
s beatmapScore
|
||||||
|
u userData
|
||||||
|
)
|
||||||
|
err := rows.Scan(
|
||||||
|
&s.ID, &s.BeatmapMD5, &s.Score,
|
||||||
|
&s.MaxCombo, &s.FullCombo, &s.Mods,
|
||||||
|
&s.Count300, &s.Count100, &s.Count50,
|
||||||
|
&s.CountGeki, &s.CountKatu, &s.CountMiss,
|
||||||
|
&s.Time, &s.PlayMode, &s.Accuracy, &s.PP,
|
||||||
|
&s.Completed,
|
||||||
|
|
||||||
|
&u.ID, &u.Username, &u.RegisteredOn, &u.Privileges,
|
||||||
|
&u.LatestActivity, &u.UsernameAKA, &u.Country,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
md.Err(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.User = u
|
||||||
|
r.Scores = append(r.Scores, s)
|
||||||
|
}
|
||||||
|
r.Code = 200
|
||||||
|
return r
|
||||||
|
}
|
|
@ -198,8 +198,8 @@ func TokenSelfGET(md common.MethodData) common.CodeMessager {
|
||||||
// TokenFixPrivilegesGET fixes the privileges on the token of the given user,
|
// TokenFixPrivilegesGET fixes the privileges on the token of the given user,
|
||||||
// or of all the users if no user is given.
|
// or of all the users if no user is given.
|
||||||
func TokenFixPrivilegesGET(md common.MethodData) common.CodeMessager {
|
func TokenFixPrivilegesGET(md common.MethodData) common.CodeMessager {
|
||||||
id := common.Int(md.C.Query("id"))
|
id := common.Int(md.Query("id"))
|
||||||
if md.C.Query("id") == "self" {
|
if md.Query("id") == "self" {
|
||||||
id = md.ID()
|
id = md.ID()
|
||||||
}
|
}
|
||||||
go fixPrivileges(id, md.DB)
|
go fixPrivileges(id, md.DB)
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
"git.zxq.co/ripple/ocl"
|
"git.zxq.co/ripple/ocl"
|
||||||
"git.zxq.co/ripple/rippleapi/common"
|
"git.zxq.co/ripple/rippleapi/common"
|
||||||
)
|
)
|
||||||
|
@ -36,7 +38,7 @@ LEFT JOIN users_stats
|
||||||
ON users.id=users_stats.id
|
ON users.id=users_stats.id
|
||||||
WHERE ` + whereClause + ` AND users.privileges & 1 > 0
|
WHERE ` + whereClause + ` AND users.privileges & 1 > 0
|
||||||
LIMIT 1`
|
LIMIT 1`
|
||||||
return userPuts(md, md.DB.QueryRow(query, param))
|
return userPuts(md, md.DB.QueryRowx(query, param))
|
||||||
}
|
}
|
||||||
|
|
||||||
type userPutsUserData struct {
|
type userPutsUserData struct {
|
||||||
|
@ -44,14 +46,11 @@ type userPutsUserData struct {
|
||||||
userData
|
userData
|
||||||
}
|
}
|
||||||
|
|
||||||
func userPuts(md common.MethodData, row *sql.Row) common.CodeMessager {
|
func userPuts(md common.MethodData, row *sqlx.Row) common.CodeMessager {
|
||||||
var err error
|
var err error
|
||||||
var user userPutsUserData
|
var user userPutsUserData
|
||||||
|
|
||||||
err = row.Scan(
|
err = row.StructScan(&user.userData)
|
||||||
&user.ID, &user.Username, &user.RegisteredOn, &user.Privileges, &user.LatestActivity,
|
|
||||||
&user.UsernameAKA, &user.Country,
|
|
||||||
)
|
|
||||||
switch {
|
switch {
|
||||||
case err == sql.ErrNoRows:
|
case err == sql.ErrNoRows:
|
||||||
return common.SimpleResponse(404, "No such user was found!")
|
return common.SimpleResponse(404, "No such user was found!")
|
||||||
|
@ -95,7 +94,7 @@ func UserWhatsTheIDGET(md common.MethodData) common.CodeMessager {
|
||||||
r whatIDResponse
|
r whatIDResponse
|
||||||
privileges int
|
privileges int
|
||||||
)
|
)
|
||||||
err := md.DB.QueryRow("SELECT id, privileges FROM users WHERE username = ? LIMIT 1", md.C.Query("name")).Scan(&r.ID, &privileges)
|
err := md.DB.QueryRow("SELECT id, privileges FROM users WHERE username = ? LIMIT 1", md.Query("name")).Scan(&r.ID, &privileges)
|
||||||
if err != nil || ((privileges&common.UserPrivilegePublic) == 0 && !md.User.Privileges.HasPrivilegeViewUserAdvanced()) {
|
if err != nil || ((privileges&common.UserPrivilegePublic) == 0 && !md.User.Privileges.HasPrivilegeViewUserAdvanced()) {
|
||||||
return common.SimpleResponse(404, "That user could not be found!")
|
return common.SimpleResponse(404, "That user could not be found!")
|
||||||
}
|
}
|
||||||
|
@ -243,17 +242,17 @@ func UserUserpageGET(md common.MethodData) common.CodeMessager {
|
||||||
|
|
||||||
func whereClauseUser(md common.MethodData, tableName string) (*common.CodeMessager, string, interface{}) {
|
func whereClauseUser(md common.MethodData, tableName string) (*common.CodeMessager, string, interface{}) {
|
||||||
switch {
|
switch {
|
||||||
case md.C.Query("id") == "self":
|
case md.Query("id") == "self":
|
||||||
return nil, tableName + ".id = ?", md.ID()
|
return nil, tableName + ".id = ?", md.ID()
|
||||||
case md.C.Query("id") != "":
|
case md.Query("id") != "":
|
||||||
id, err := strconv.Atoi(md.C.Query("id"))
|
id, err := strconv.Atoi(md.Query("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a := common.SimpleResponse(400, "please pass a valid user ID")
|
a := common.SimpleResponse(400, "please pass a valid user ID")
|
||||||
return &a, "", nil
|
return &a, "", nil
|
||||||
}
|
}
|
||||||
return nil, tableName + ".id = ?", id
|
return nil, tableName + ".id = ?", id
|
||||||
case md.C.Query("name") != "":
|
case md.Query("name") != "":
|
||||||
return nil, tableName + ".username = ?", md.C.Query("name")
|
return nil, tableName + ".username = ?", md.Query("name")
|
||||||
}
|
}
|
||||||
a := common.SimpleResponse(400, "you need to pass either querystring parameters name or id")
|
a := common.SimpleResponse(400, "you need to pass either querystring parameters name or id")
|
||||||
return &a, "", nil
|
return &a, "", nil
|
||||||
|
@ -275,7 +274,7 @@ func UserLookupGET(md common.MethodData) common.CodeMessager {
|
||||||
"%", "\\%",
|
"%", "\\%",
|
||||||
"_", "\\_",
|
"_", "\\_",
|
||||||
"\\", "\\\\",
|
"\\", "\\\\",
|
||||||
).Replace(md.C.Query("name"))
|
).Replace(md.Query("name"))
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return common.SimpleResponse(400, "please provide an username to start searching")
|
return common.SimpleResponse(400, "please provide an username to start searching")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package v1
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.zxq.co/ripple/rippleapi/common"
|
"git.zxq.co/ripple/rippleapi/common"
|
||||||
)
|
)
|
||||||
|
@ -46,7 +45,7 @@ func UserScoresBestGET(md common.MethodData) common.CodeMessager {
|
||||||
}
|
}
|
||||||
mc := genModeClause(md)
|
mc := genModeClause(md)
|
||||||
// Do not print 0pp scores on std
|
// Do not print 0pp scores on std
|
||||||
if getMode(md.C.Query("mode")) == "std" {
|
if getMode(md.Query("mode")) == "std" {
|
||||||
mc += " AND scores.pp > 0"
|
mc += " AND scores.pp > 0"
|
||||||
}
|
}
|
||||||
return scoresPuts(md, fmt.Sprintf(
|
return scoresPuts(md, fmt.Sprintf(
|
||||||
|
@ -56,7 +55,7 @@ func UserScoresBestGET(md common.MethodData) common.CodeMessager {
|
||||||
%s
|
%s
|
||||||
AND users.privileges & 1 > 0
|
AND users.privileges & 1 > 0
|
||||||
ORDER BY scores.pp DESC, scores.score DESC %s`,
|
ORDER BY scores.pp DESC, scores.score DESC %s`,
|
||||||
wc, mc, common.Paginate(md.C.Query("p"), md.C.Query("l"), 100),
|
wc, mc, common.Paginate(md.Query("p"), md.Query("l"), 100),
|
||||||
), param)
|
), param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ func UserScoresRecentGET(md common.MethodData) common.CodeMessager {
|
||||||
%s
|
%s
|
||||||
AND users.privileges & 1 > 0
|
AND users.privileges & 1 > 0
|
||||||
ORDER BY scores.time DESC %s`,
|
ORDER BY scores.time DESC %s`,
|
||||||
wc, genModeClause(md), common.Paginate(md.C.Query("p"), md.C.Query("l"), 100),
|
wc, genModeClause(md), common.Paginate(md.Query("p"), md.Query("l"), 100),
|
||||||
), param)
|
), param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +90,8 @@ func getMode(m string) string {
|
||||||
|
|
||||||
func genModeClause(md common.MethodData) string {
|
func genModeClause(md common.MethodData) string {
|
||||||
var modeClause string
|
var modeClause string
|
||||||
if md.C.Query("mode") != "" {
|
if md.Query("mode") != "" {
|
||||||
m, err := strconv.Atoi(md.C.Query("mode"))
|
m, err := strconv.Atoi(md.Query("mode"))
|
||||||
if err == nil && m >= 0 && m <= 3 {
|
if err == nil && m >= 0 && m <= 3 {
|
||||||
modeClause = fmt.Sprintf("AND scores.play_mode = '%d'", m)
|
modeClause = fmt.Sprintf("AND scores.play_mode = '%d'", m)
|
||||||
}
|
}
|
||||||
|
@ -110,7 +109,6 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{})
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var (
|
var (
|
||||||
us userScore
|
us userScore
|
||||||
t string
|
|
||||||
b beatmap
|
b beatmap
|
||||||
)
|
)
|
||||||
err = rows.Scan(
|
err = rows.Scan(
|
||||||
|
@ -118,7 +116,7 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{})
|
||||||
&us.MaxCombo, &us.FullCombo, &us.Mods,
|
&us.MaxCombo, &us.FullCombo, &us.Mods,
|
||||||
&us.Count300, &us.Count100, &us.Count50,
|
&us.Count300, &us.Count100, &us.Count50,
|
||||||
&us.CountGeki, &us.CountKatu, &us.CountMiss,
|
&us.CountGeki, &us.CountKatu, &us.CountMiss,
|
||||||
&t, &us.PlayMode, &us.Accuracy, &us.PP,
|
&us.Time, &us.PlayMode, &us.Accuracy, &us.PP,
|
||||||
&us.Completed,
|
&us.Completed,
|
||||||
|
|
||||||
&b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5,
|
&b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5,
|
||||||
|
@ -127,17 +125,11 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{})
|
||||||
&b.MaxCombo, &b.HitLength, &b.Ranked,
|
&b.MaxCombo, &b.HitLength, &b.Ranked,
|
||||||
&b.RankedStatusFrozen, &b.LatestUpdate,
|
&b.RankedStatusFrozen, &b.LatestUpdate,
|
||||||
)
|
)
|
||||||
b.Difficulty = b.Diff2.STD
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
return Err500
|
return Err500
|
||||||
}
|
}
|
||||||
// puck feppy
|
b.Difficulty = b.Diff2.STD
|
||||||
us.Time, err = time.Parse(common.OsuTimeFormat, t)
|
|
||||||
if _, ok := err.(*time.ParseError); !ok && err != nil {
|
|
||||||
md.Err(err)
|
|
||||||
return Err500
|
|
||||||
}
|
|
||||||
us.Beatmap = b
|
us.Beatmap = b
|
||||||
scores = append(scores, us)
|
scores = append(scores, us)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,11 @@ func (md MethodData) ID() int {
|
||||||
return md.User.UserID
|
return md.User.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query is shorthand for md.C.Query.
|
||||||
|
func (md MethodData) Query(q string) string {
|
||||||
|
return md.C.Query(q)
|
||||||
|
}
|
||||||
|
|
||||||
// RequestData is the body of a request. It is wrapped into this type
|
// RequestData is the body of a request. It is wrapped into this type
|
||||||
// to implement the Unmarshal function, which is just a shorthand to
|
// to implement the Unmarshal function, which is just a shorthand to
|
||||||
// json.Unmarshal.
|
// json.Unmarshal.
|
||||||
|
|
|
@ -1,4 +1,60 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// OsuTimeFormat is the time format for scores in the DB. Can be used with time.Parse etc.
|
// OsuTimeFormat is the time format for scores in the DB. Can be used with time.Parse etc.
|
||||||
const OsuTimeFormat = "060102150405"
|
const OsuTimeFormat = "060102150405"
|
||||||
|
|
||||||
|
// OsuTime is simply a time.Time, but can be used to convert an
|
||||||
|
// osu timestamp in the database into a native time.Time.
|
||||||
|
type OsuTime time.Time
|
||||||
|
|
||||||
|
func (s *OsuTime) setTime(t string) error {
|
||||||
|
newTime, err := time.Parse(OsuTimeFormat, t)
|
||||||
|
if _, ok := err.(*time.ParseError); err != nil && !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
*s = OsuTime(newTime)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan decodes src into an OsuTime.
|
||||||
|
func (s *OsuTime) Scan(src interface{}) error {
|
||||||
|
if s == nil {
|
||||||
|
return errors.New("rippleapi/common: OsuTime is nil")
|
||||||
|
}
|
||||||
|
switch src := src.(type) {
|
||||||
|
case int64:
|
||||||
|
return s.setTime(strconv.FormatInt(src, 64))
|
||||||
|
case float64:
|
||||||
|
return s.setTime(strconv.FormatInt(int64(src), 64))
|
||||||
|
case string:
|
||||||
|
return s.setTime(src)
|
||||||
|
case []byte:
|
||||||
|
return s.setTime(string(src))
|
||||||
|
case nil:
|
||||||
|
// Nothing, leave zero value on timestamp
|
||||||
|
default:
|
||||||
|
return errors.New("rippleapi/common: unhandleable type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON -> time.Time.MarshalJSON
|
||||||
|
func (s OsuTime) MarshalJSON() ([]byte, error) {
|
||||||
|
return time.Time(s).MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON -> time.Time.UnmarshalJSON
|
||||||
|
func (s *OsuTime) UnmarshalJSON(x []byte) error {
|
||||||
|
t := new(time.Time)
|
||||||
|
err := t.UnmarshalJSON(x)
|
||||||
|
*s = OsuTime(*t)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
52
common/sort.go
Normal file
52
common/sort.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// SortConfiguration is the configuration of Sort.
|
||||||
|
type SortConfiguration struct {
|
||||||
|
Allowed []string // Allowed parameters
|
||||||
|
Default string
|
||||||
|
DefaultSorting string // if empty, DESC
|
||||||
|
Table string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort allows the request to modify how the query is sorted.
|
||||||
|
func Sort(md MethodData, config SortConfiguration) string {
|
||||||
|
if config.DefaultSorting == "" {
|
||||||
|
config.DefaultSorting = "DESC"
|
||||||
|
}
|
||||||
|
if config.Table != "" {
|
||||||
|
config.Table += "."
|
||||||
|
}
|
||||||
|
var sortBy string
|
||||||
|
for _, s := range md.C.Request.URL.Query()["sort"] {
|
||||||
|
sortParts := strings.Split(strings.ToLower(s), ",")
|
||||||
|
if contains(config.Allowed, sortParts[0]) {
|
||||||
|
if sortBy != "" {
|
||||||
|
sortBy += ", "
|
||||||
|
}
|
||||||
|
sortBy += config.Table + sortParts[0] + " "
|
||||||
|
if len(sortParts) > 1 && contains([]string{"asc", "desc"}, sortParts[1]) {
|
||||||
|
sortBy += sortParts[1]
|
||||||
|
} else {
|
||||||
|
sortBy += config.DefaultSorting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sortBy == "" {
|
||||||
|
sortBy = config.Default
|
||||||
|
}
|
||||||
|
if sortBy == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "ORDER BY " + sortBy
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(a []string, s string) bool {
|
||||||
|
for _, el := range a {
|
||||||
|
if s == el {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user