Implement GET scores in official ripple api
This commit is contained in:
		
							
								
								
									
										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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user