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/gin"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/serenize/snaker"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -18,11 +19,23 @@ var (
|
|||
cf common.Conf
|
||||
)
|
||||
|
||||
var commonClusterfucks = map[string]string{
|
||||
"RegisteredOn": "register_datetime",
|
||||
"UsernameAKA": "username_aka",
|
||||
}
|
||||
|
||||
// Start begins taking HTTP connections.
|
||||
func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine {
|
||||
db = dbO
|
||||
cf = conf
|
||||
|
||||
db.MapperFunc(func(s string) string {
|
||||
if x, ok := commonClusterfucks[s]; ok {
|
||||
return x
|
||||
}
|
||||
return snaker.CamelToSnake(s)
|
||||
})
|
||||
|
||||
setUpLimiter()
|
||||
|
||||
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("/blog/posts", Method(v1.BlogPostsGET))
|
||||
gv1.GET("/blog/posts/content", Method(v1.BlogPostsContentGET))
|
||||
gv1.GET("/scores", Method(v1.ScoresGET))
|
||||
|
||||
// ReadConfidential privilege required
|
||||
gv1.GET("/friends", Method(v1.FriendsGET, common.PrivilegeReadConfidential))
|
||||
|
|
|
@ -24,9 +24,9 @@ func BadgesGET(md common.MethodData) common.CodeMessager {
|
|||
rows *sql.Rows
|
||||
err error
|
||||
)
|
||||
if md.C.Query("id") != "" {
|
||||
if md.Query("id") != "" {
|
||||
// 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 {
|
||||
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.
|
||||
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'")
|
||||
}
|
||||
setID := common.Int(md.C.Query("s"))
|
||||
setID := common.Int(md.Query("s"))
|
||||
if setID != 0 {
|
||||
return getSet(md, setID)
|
||||
}
|
||||
beatmapID := common.Int(md.C.Query("b"))
|
||||
beatmapID := common.Int(md.Query("b"))
|
||||
if beatmapID != 0 {
|
||||
return getBeatmap(md, beatmapID)
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ type blogPostsResponse struct {
|
|||
func BlogPostsGET(md common.MethodData) common.CodeMessager {
|
||||
var and string
|
||||
var params []interface{}
|
||||
if md.C.Query("id") != "" {
|
||||
if md.Query("id") != "" {
|
||||
and = "b.id = ?"
|
||||
params = append(params, md.C.Query("id"))
|
||||
params = append(params, md.Query("id"))
|
||||
}
|
||||
rows, err := md.DB.Query(`
|
||||
SELECT
|
||||
|
@ -37,7 +37,7 @@ func BlogPostsGET(md common.MethodData) common.CodeMessager {
|
|||
LEFT JOIN users u ON b.author = u.id
|
||||
LEFT JOIN users_stats s ON b.author = s.id
|
||||
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 {
|
||||
md.Err(err)
|
||||
return Err500
|
||||
|
@ -79,12 +79,12 @@ func BlogPostsContentGET(md common.MethodData) common.CodeMessager {
|
|||
val string
|
||||
)
|
||||
switch {
|
||||
case md.C.Query("slug") != "":
|
||||
case md.Query("slug") != "":
|
||||
by = "slug"
|
||||
val = md.C.Query("slug")
|
||||
case md.C.Query("id") != "":
|
||||
val = md.Query("slug")
|
||||
case md.Query("id") != "":
|
||||
by = "id"
|
||||
val = md.C.Query("id")
|
||||
val = md.Query("id")
|
||||
default:
|
||||
return ErrMissingField("id|slug")
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ type docResponse struct {
|
|||
// DocGET retrieves a list of documentation files.
|
||||
func DocGET(md common.MethodData) common.CodeMessager {
|
||||
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'"
|
||||
}
|
||||
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
|
||||
func DocContentGET(md common.MethodData) common.CodeMessager {
|
||||
docID := common.Int(md.C.Query("id"))
|
||||
docID := common.Int(md.Query("id"))
|
||||
if docID == 0 {
|
||||
return common.SimpleResponse(404, "Documentation file not found!")
|
||||
}
|
||||
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'"
|
||||
}
|
||||
var r docContentResponse
|
||||
|
|
|
@ -16,6 +16,6 @@ var (
|
|||
func ErrMissingField(missingFields ...string) common.CodeMessager {
|
||||
return common.ResponseBase{
|
||||
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=?
|
||||
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 {
|
||||
md.Err(err)
|
||||
return Err500
|
||||
|
@ -105,7 +105,7 @@ type friendsWithResponse struct {
|
|||
func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
||||
var r friendsWithResponse
|
||||
r.Code = 200
|
||||
uid := common.Int(md.C.Query("id"))
|
||||
uid := common.Int(md.Query("id"))
|
||||
if uid == 0 {
|
||||
return r
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
|||
|
||||
// FriendsAddGET is the GET version of FriendsAddPOST.
|
||||
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 {
|
||||
|
@ -166,7 +166,7 @@ func userExists(md common.MethodData, u int) (r bool) {
|
|||
|
||||
// FriendsDelGET is the GET version of FriendDelPOST.
|
||||
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 {
|
||||
|
|
|
@ -35,9 +35,9 @@ INNER JOIN users_stats ON users_stats.id = leaderboard_%[1]s.user
|
|||
|
||||
// LeaderboardGET gets the leaderboard.
|
||||
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 `+
|
||||
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)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
|
|
|
@ -50,5 +50,5 @@ LEFT JOIN users_stats
|
|||
ON users.id=users_stats.id
|
||||
WHERE users.id=?
|
||||
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,
|
||||
// or of all the users if no user is given.
|
||||
func TokenFixPrivilegesGET(md common.MethodData) common.CodeMessager {
|
||||
id := common.Int(md.C.Query("id"))
|
||||
if md.C.Query("id") == "self" {
|
||||
id := common.Int(md.Query("id"))
|
||||
if md.Query("id") == "self" {
|
||||
id = md.ID()
|
||||
}
|
||||
go fixPrivileges(id, md.DB)
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"git.zxq.co/ripple/ocl"
|
||||
"git.zxq.co/ripple/rippleapi/common"
|
||||
)
|
||||
|
@ -36,7 +38,7 @@ LEFT JOIN users_stats
|
|||
ON users.id=users_stats.id
|
||||
WHERE ` + whereClause + ` AND users.privileges & 1 > 0
|
||||
LIMIT 1`
|
||||
return userPuts(md, md.DB.QueryRow(query, param))
|
||||
return userPuts(md, md.DB.QueryRowx(query, param))
|
||||
}
|
||||
|
||||
type userPutsUserData struct {
|
||||
|
@ -44,14 +46,11 @@ type userPutsUserData struct {
|
|||
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 user userPutsUserData
|
||||
|
||||
err = row.Scan(
|
||||
&user.ID, &user.Username, &user.RegisteredOn, &user.Privileges, &user.LatestActivity,
|
||||
&user.UsernameAKA, &user.Country,
|
||||
)
|
||||
err = row.StructScan(&user.userData)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return common.SimpleResponse(404, "No such user was found!")
|
||||
|
@ -95,7 +94,7 @@ func UserWhatsTheIDGET(md common.MethodData) common.CodeMessager {
|
|||
r whatIDResponse
|
||||
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()) {
|
||||
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{}) {
|
||||
switch {
|
||||
case md.C.Query("id") == "self":
|
||||
case md.Query("id") == "self":
|
||||
return nil, tableName + ".id = ?", md.ID()
|
||||
case md.C.Query("id") != "":
|
||||
id, err := strconv.Atoi(md.C.Query("id"))
|
||||
case md.Query("id") != "":
|
||||
id, err := strconv.Atoi(md.Query("id"))
|
||||
if err != nil {
|
||||
a := common.SimpleResponse(400, "please pass a valid user ID")
|
||||
return &a, "", nil
|
||||
}
|
||||
return nil, tableName + ".id = ?", id
|
||||
case md.C.Query("name") != "":
|
||||
return nil, tableName + ".username = ?", md.C.Query("name")
|
||||
case md.Query("name") != "":
|
||||
return nil, tableName + ".username = ?", md.Query("name")
|
||||
}
|
||||
a := common.SimpleResponse(400, "you need to pass either querystring parameters name or id")
|
||||
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 == "" {
|
||||
return common.SimpleResponse(400, "please provide an username to start searching")
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package v1
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.zxq.co/ripple/rippleapi/common"
|
||||
)
|
||||
|
@ -46,7 +45,7 @@ func UserScoresBestGET(md common.MethodData) common.CodeMessager {
|
|||
}
|
||||
mc := genModeClause(md)
|
||||
// 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"
|
||||
}
|
||||
return scoresPuts(md, fmt.Sprintf(
|
||||
|
@ -56,7 +55,7 @@ func UserScoresBestGET(md common.MethodData) common.CodeMessager {
|
|||
%s
|
||||
AND users.privileges & 1 > 0
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -72,7 +71,7 @@ func UserScoresRecentGET(md common.MethodData) common.CodeMessager {
|
|||
%s
|
||||
AND users.privileges & 1 > 0
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -91,8 +90,8 @@ func getMode(m string) string {
|
|||
|
||||
func genModeClause(md common.MethodData) string {
|
||||
var modeClause string
|
||||
if md.C.Query("mode") != "" {
|
||||
m, err := strconv.Atoi(md.C.Query("mode"))
|
||||
if md.Query("mode") != "" {
|
||||
m, err := strconv.Atoi(md.Query("mode"))
|
||||
if err == nil && m >= 0 && m <= 3 {
|
||||
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() {
|
||||
var (
|
||||
us userScore
|
||||
t string
|
||||
b beatmap
|
||||
)
|
||||
err = rows.Scan(
|
||||
|
@ -118,7 +116,7 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{})
|
|||
&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,
|
||||
&us.Time, &us.PlayMode, &us.Accuracy, &us.PP,
|
||||
&us.Completed,
|
||||
|
||||
&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.RankedStatusFrozen, &b.LatestUpdate,
|
||||
)
|
||||
b.Difficulty = b.Diff2.STD
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
return Err500
|
||||
}
|
||||
// puck feppy
|
||||
us.Time, err = time.Parse(common.OsuTimeFormat, t)
|
||||
if _, ok := err.(*time.ParseError); !ok && err != nil {
|
||||
md.Err(err)
|
||||
return Err500
|
||||
}
|
||||
b.Difficulty = b.Diff2.STD
|
||||
us.Beatmap = b
|
||||
scores = append(scores, us)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@ func (md MethodData) ID() int {
|
|||
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
|
||||
// to implement the Unmarshal function, which is just a shorthand to
|
||||
// json.Unmarshal.
|
||||
|
|
|
@ -1,4 +1,60 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OsuTimeFormat is the time format for scores in the DB. Can be used with time.Parse etc.
|
||||
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