Completely change response structure
This commit is contained in:
parent
14d926e31d
commit
7387ed4295
|
@ -10,13 +10,13 @@ import (
|
|||
)
|
||||
|
||||
// Method wraps an API method to a HandlerFunc.
|
||||
func Method(f func(md common.MethodData) common.Response, db *sql.DB, privilegesNeeded ...int) gin.HandlerFunc {
|
||||
func Method(f func(md common.MethodData) common.CodeMessager, db *sql.DB, privilegesNeeded ...int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
initialCaretaker(c, f, db, privilegesNeeded...)
|
||||
}
|
||||
}
|
||||
|
||||
func initialCaretaker(c *gin.Context, f func(md common.MethodData) common.Response, db *sql.DB, privilegesNeeded ...int) {
|
||||
func initialCaretaker(c *gin.Context, f func(md common.MethodData) common.CodeMessager, db *sql.DB, privilegesNeeded ...int) {
|
||||
data, err := ioutil.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
|
@ -52,22 +52,25 @@ func initialCaretaker(c *gin.Context, f func(md common.MethodData) common.Respon
|
|||
}
|
||||
}
|
||||
if missingPrivileges != 0 {
|
||||
c.IndentedJSON(401, common.Response{
|
||||
Code: 401,
|
||||
Message: "You don't have the privilege(s): " + common.Privileges(missingPrivileges).String() + ".",
|
||||
})
|
||||
c.IndentedJSON(401, common.SimpleResponse(401, "You don't have the privilege(s): "+common.Privileges(missingPrivileges).String()+"."))
|
||||
return
|
||||
}
|
||||
|
||||
resp := f(md)
|
||||
if resp.Code == 0 {
|
||||
resp.Code = 500
|
||||
if resp.GetCode() == 0 {
|
||||
// Dirty hack to set the code
|
||||
type setCoder interface {
|
||||
SetCode(int)
|
||||
}
|
||||
if newver, can := resp.(setCoder); can {
|
||||
newver.SetCode(500)
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := c.GetQuery("pls200"); exists {
|
||||
c.Writer.WriteHeader(200)
|
||||
} else {
|
||||
c.Writer.WriteHeader(resp.Code)
|
||||
c.Writer.WriteHeader(resp.GetCode())
|
||||
}
|
||||
|
||||
if _, exists := c.GetQuery("callback"); exists {
|
||||
|
|
|
@ -5,11 +5,18 @@ import (
|
|||
"github.com/osuripple/api/common"
|
||||
)
|
||||
|
||||
type response404 struct {
|
||||
common.ResponseBase
|
||||
Cats string `json:"cats"`
|
||||
}
|
||||
|
||||
// Handle404 handles requests with no implemented handlers.
|
||||
func Handle404(c *gin.Context) {
|
||||
c.IndentedJSON(404, common.Response{
|
||||
Code: 404,
|
||||
Message: "Oh dear... that API request could not be found! Perhaps the API is not up-to-date? Either way, have a surprise!",
|
||||
Data: surpriseMe(),
|
||||
c.IndentedJSON(404, response404{
|
||||
ResponseBase: common.ResponseBase{
|
||||
Code: 404,
|
||||
Message: "Oh dear... that API request could not be found! Perhaps the API is not up-to-date? Either way, have a surprise!",
|
||||
},
|
||||
Cats: surpriseMe(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,53 +6,57 @@ import (
|
|||
"github.com/osuripple/api/common"
|
||||
)
|
||||
|
||||
type badgeData struct {
|
||||
type singleBadge struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
type badgeData struct {
|
||||
common.ResponseBase
|
||||
singleBadge
|
||||
}
|
||||
|
||||
// BadgeByIDGET is the handler for /badge/:id
|
||||
func BadgeByIDGET(md common.MethodData) (r common.Response) {
|
||||
b := badgeData{}
|
||||
func BadgeByIDGET(md common.MethodData) common.CodeMessager {
|
||||
var b badgeData
|
||||
err := md.DB.QueryRow("SELECT id, name, icon FROM badges WHERE id=? LIMIT 1", md.C.Param("id")).Scan(&b.ID, &b.Name, &b.Icon)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
r.Code = 404
|
||||
r.Message = "No such badge was found"
|
||||
return
|
||||
return common.SimpleResponse(404, "No such badge was found")
|
||||
case err != nil:
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
r.Code = 200
|
||||
r.Data = b
|
||||
return
|
||||
b.Code = 200
|
||||
return b
|
||||
}
|
||||
|
||||
type multiBadgeData struct {
|
||||
common.ResponseBase
|
||||
Badges []singleBadge `json:"badges"`
|
||||
}
|
||||
|
||||
// BadgesGET retrieves all the badges on this ripple instance.
|
||||
func BadgesGET(md common.MethodData) (r common.Response) {
|
||||
var badges []badgeData
|
||||
func BadgesGET(md common.MethodData) common.CodeMessager {
|
||||
var r multiBadgeData
|
||||
rows, err := md.DB.Query("SELECT id, name, icon FROM badges")
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
nb := badgeData{}
|
||||
nb := singleBadge{}
|
||||
err = rows.Scan(&nb.ID, &nb.Name, &nb.Icon)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
}
|
||||
badges = append(badges, nb)
|
||||
r.Badges = append(r.Badges, nb)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
md.Err(err)
|
||||
}
|
||||
r.Code = 200
|
||||
r.Data = badges
|
||||
return
|
||||
r.ResponseBase.Code = 200
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -8,19 +8,13 @@ import (
|
|||
|
||||
// Boilerplate errors
|
||||
var (
|
||||
Err500 = common.Response{
|
||||
Code: 500,
|
||||
Message: "An error occurred. Try again, perhaps?",
|
||||
}
|
||||
ErrBadJSON = common.Response{
|
||||
Code: 400,
|
||||
Message: "There was an error processing your JSON data.",
|
||||
}
|
||||
Err500 = common.SimpleResponse(500, "An error occurred. Trying again may work. If it doesn't, yell at this Ripple instance admin and tell them to fix the API.")
|
||||
ErrBadJSON = common.SimpleResponse(400, "Your JSON for this request is invalid.")
|
||||
)
|
||||
|
||||
// ErrMissingField generates a response to a request when some fields in the JSON are missing.
|
||||
func ErrMissingField(missingFields ...string) common.Response {
|
||||
return common.Response{
|
||||
func ErrMissingField(missingFields ...string) common.CodeMessager {
|
||||
return common.ResponseBase{
|
||||
Code: 422, // http://stackoverflow.com/a/10323055/5328069
|
||||
Message: "Missing fields: " + strings.Join(missingFields, ", ") + ".",
|
||||
}
|
||||
|
|
101
app/v1/friend.go
101
app/v1/friend.go
|
@ -13,15 +13,19 @@ type friendData struct {
|
|||
IsMutual bool `json:"is_mutual"`
|
||||
}
|
||||
|
||||
type friendsGETResponse struct {
|
||||
common.ResponseBase
|
||||
Friends []friendData `json:"friends"`
|
||||
}
|
||||
|
||||
// FriendsGET is the API request handler for GET /friends.
|
||||
// It retrieves an user's friends, and whether the friendship is mutual or not.
|
||||
func FriendsGET(md common.MethodData) (r common.Response) {
|
||||
func FriendsGET(md common.MethodData) common.CodeMessager {
|
||||
var myFrienders []int
|
||||
myFriendersRaw, err := md.DB.Query("SELECT user1 FROM users_relationships WHERE user2 = ?", md.ID())
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
defer myFriendersRaw.Close()
|
||||
for myFriendersRaw.Next() {
|
||||
|
@ -55,8 +59,7 @@ ORDER BY users_relationships.id`
|
|||
results, err := md.DB.Query(myFriendsQuery+common.Paginate(md.C.Query("p"), md.C.Query("l")), md.ID())
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
|
||||
var myFriends []friendData
|
||||
|
@ -76,9 +79,10 @@ ORDER BY users_relationships.id`
|
|||
md.Err(err)
|
||||
}
|
||||
|
||||
r := friendsGETResponse{}
|
||||
r.Code = 200
|
||||
r.Data = myFriends
|
||||
return
|
||||
r.Friends = myFriends
|
||||
return r
|
||||
}
|
||||
|
||||
func friendPuts(md common.MethodData, row *sql.Rows) (user friendData) {
|
||||
|
@ -104,39 +108,37 @@ func friendPuts(md common.MethodData, row *sql.Rows) (user friendData) {
|
|||
return
|
||||
}
|
||||
|
||||
type friendsWithData struct {
|
||||
type friendsWithResponse struct {
|
||||
common.ResponseBase
|
||||
Friends bool `json:"friend"`
|
||||
Mutual bool `json:"mutual"`
|
||||
}
|
||||
|
||||
// FriendsWithGET checks the current user is friends with the one passed in the request path.
|
||||
func FriendsWithGET(md common.MethodData) (r common.Response) {
|
||||
func FriendsWithGET(md common.MethodData) common.CodeMessager {
|
||||
r := friendsWithResponse{}
|
||||
r.Code = 200
|
||||
var d friendsWithData
|
||||
uid, err := strconv.Atoi(md.C.Param("id"))
|
||||
if err != nil {
|
||||
r.Data = d
|
||||
return
|
||||
return common.SimpleResponse(400, "That is not a number!")
|
||||
}
|
||||
err = md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users_relationships WHERE user1 = ? AND user2 = ? LIMIT 1), EXISTS(SELECT 1 FROM users_relationships WHERE user2 = ? AND user1 = ? LIMIT 1)", md.ID(), uid, md.ID(), uid).Scan(&d.Friends, &d.Mutual)
|
||||
err = md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users_relationships WHERE user1 = ? AND user2 = ? LIMIT 1), EXISTS(SELECT 1 FROM users_relationships WHERE user2 = ? AND user1 = ? LIMIT 1)", md.ID(), uid, md.ID(), uid).Scan(&r.Friends, &r.Mutual)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
r.Data = d
|
||||
return
|
||||
if !r.Friends {
|
||||
r.Mutual = false
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// FriendsAddGET is the GET version of FriendsAddPOST.
|
||||
func FriendsAddGET(md common.MethodData) common.Response {
|
||||
func FriendsAddGET(md common.MethodData) common.CodeMessager {
|
||||
uidS := md.C.Param("id")
|
||||
uid, err := strconv.Atoi(uidS)
|
||||
if err != nil {
|
||||
return common.Response{
|
||||
Code: 400,
|
||||
Message: "Nope. That's not a number.",
|
||||
}
|
||||
return common.SimpleResponse(400, "Nope. That's not a number.")
|
||||
}
|
||||
return addFriend(md, uid)
|
||||
}
|
||||
|
@ -146,26 +148,21 @@ type friendAddPOSTData struct {
|
|||
}
|
||||
|
||||
// FriendsAddPOST allows for adding friends. Yup. Easy as that.
|
||||
func FriendsAddPOST(md common.MethodData) (r common.Response) {
|
||||
func FriendsAddPOST(md common.MethodData) common.CodeMessager {
|
||||
d := friendAddPOSTData{}
|
||||
err := md.RequestData.Unmarshal(&d)
|
||||
if err != nil {
|
||||
r = ErrBadJSON
|
||||
return
|
||||
return ErrBadJSON
|
||||
}
|
||||
return addFriend(md, d.UserID)
|
||||
}
|
||||
|
||||
func addFriend(md common.MethodData, u int) (r common.Response) {
|
||||
func addFriend(md common.MethodData, u int) common.CodeMessager {
|
||||
if md.ID() == u {
|
||||
r.Code = 400
|
||||
r.Message = "Just so you know: you can't add yourself to your friends."
|
||||
return
|
||||
return common.SimpleResponse(406, "Just so you know: you can't add yourself to your friends.")
|
||||
}
|
||||
if !userExists(md, u) {
|
||||
r.Code = 404
|
||||
r.Message = "I'd also like to be friends with someone who doesn't even exist (???), however that's NOT POSSIBLE."
|
||||
return
|
||||
return common.SimpleResponse(404, "I'd also like to be friends with someone who doesn't even exist (???), however that's NOT POSSIBLE.")
|
||||
}
|
||||
var (
|
||||
relExists bool
|
||||
|
@ -174,23 +171,20 @@ func addFriend(md common.MethodData, u int) (r common.Response) {
|
|||
err := md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users_relationships WHERE user1 = ? AND user2 = ?), EXISTS(SELECT 1 FROM users_relationships WHERE user2 = ? AND user1 = ?)", md.ID(), u, md.ID(), u).Scan(&relExists, &isMutual)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
if !relExists {
|
||||
_, err := md.DB.Exec("INSERT INTO users_relationships(user1, user2) VALUES (?, ?)", md.User.UserID, u)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
}
|
||||
var r friendsWithResponse
|
||||
r.Code = 200
|
||||
r.Data = friendsWithData{
|
||||
Friends: true,
|
||||
Mutual: isMutual,
|
||||
}
|
||||
return
|
||||
r.Friends = true
|
||||
r.Mutual = isMutual
|
||||
return r
|
||||
}
|
||||
|
||||
// userExists makes sure an user exists.
|
||||
|
@ -203,40 +197,35 @@ func userExists(md common.MethodData, u int) (r bool) {
|
|||
}
|
||||
|
||||
// FriendsDelGET is the GET version of FriendDelPOST.
|
||||
func FriendsDelGET(md common.MethodData) common.Response {
|
||||
func FriendsDelGET(md common.MethodData) common.CodeMessager {
|
||||
uidS := md.C.Param("id")
|
||||
uid, err := strconv.Atoi(uidS)
|
||||
if err != nil {
|
||||
return common.Response{
|
||||
Code: 400,
|
||||
Message: "Nope. That's not a number.",
|
||||
}
|
||||
return common.SimpleResponse(400, "Nope. That's not a number.")
|
||||
}
|
||||
return delFriend(md, uid)
|
||||
}
|
||||
|
||||
// FriendsDelPOST allows for deleting friends.
|
||||
func FriendsDelPOST(md common.MethodData) (r common.Response) {
|
||||
func FriendsDelPOST(md common.MethodData) common.CodeMessager {
|
||||
d := friendAddPOSTData{}
|
||||
err := md.RequestData.Unmarshal(&d)
|
||||
if err != nil {
|
||||
r = ErrBadJSON
|
||||
return
|
||||
return ErrBadJSON
|
||||
}
|
||||
return delFriend(md, d.UserID)
|
||||
}
|
||||
|
||||
func delFriend(md common.MethodData, u int) common.Response {
|
||||
func delFriend(md common.MethodData, u int) common.CodeMessager {
|
||||
_, err := md.DB.Exec("DELETE FROM users_relationships WHERE user1 = ? AND user2 = ?", md.ID(), u)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
return Err500
|
||||
}
|
||||
return common.Response{
|
||||
Code: 200,
|
||||
Data: friendsWithData{
|
||||
Friends: false,
|
||||
Mutual: false,
|
||||
},
|
||||
r := friendsWithResponse{
|
||||
Friends: false,
|
||||
Mutual: false,
|
||||
}
|
||||
r.Code = 200
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -8,22 +8,18 @@ type setAllowedData struct {
|
|||
}
|
||||
|
||||
// UserManageSetAllowedPOST allows to set the allowed status of an user.
|
||||
func UserManageSetAllowedPOST(md common.MethodData) (r common.Response) {
|
||||
func UserManageSetAllowedPOST(md common.MethodData) common.CodeMessager {
|
||||
data := setAllowedData{}
|
||||
if err := md.RequestData.Unmarshal(&data); err != nil {
|
||||
r = ErrBadJSON
|
||||
return
|
||||
return ErrBadJSON
|
||||
}
|
||||
if data.Allowed < 0 || data.Allowed > 2 {
|
||||
r.Code = 400
|
||||
r.Message = "Allowed status must be between 0 and 2"
|
||||
return
|
||||
return common.SimpleResponse(400, "Allowed status must be between 0 and 2")
|
||||
}
|
||||
_, err := md.DB.Exec("UPDATE users SET allowed = ? WHERE id = ?", data.Allowed, data.UserID)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
query := `
|
||||
SELECT users.id, users.username, register_datetime, rank,
|
||||
|
@ -34,6 +30,5 @@ LEFT JOIN users_stats
|
|||
ON users.id=users_stats.id
|
||||
WHERE users.id=?
|
||||
LIMIT 1`
|
||||
r = userPuts(md, md.DB.QueryRow(query, data.UserID))
|
||||
return
|
||||
return userPuts(md, md.DB.QueryRow(query, data.UserID))
|
||||
}
|
||||
|
|
|
@ -14,60 +14,59 @@ import (
|
|||
)
|
||||
|
||||
// MetaRestartGET restarts the API with Zero Downtime™.
|
||||
func MetaRestartGET(md common.MethodData) (r common.Response) {
|
||||
func MetaRestartGET(md common.MethodData) common.CodeMessager {
|
||||
proc, err := os.FindProcess(syscall.Getpid())
|
||||
if err != nil {
|
||||
r.Code = 500
|
||||
r.Message = "couldn't find process. what the fuck?"
|
||||
return
|
||||
return common.SimpleResponse(500, "couldn't find process. what the fuck?")
|
||||
}
|
||||
r.Code = 200
|
||||
r.Message = "brb"
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
proc.Signal(syscall.SIGUSR2)
|
||||
}()
|
||||
return
|
||||
return common.SimpleResponse(200, "brb")
|
||||
}
|
||||
|
||||
// MetaKillGET kills the API process. NOTE TO EVERYONE: NEVER. EVER. USE IN PROD.
|
||||
// Mainly created because I couldn't bother to fire up a terminal, do htop and kill the API each time.
|
||||
func MetaKillGET(md common.MethodData) (r common.Response) {
|
||||
func MetaKillGET(md common.MethodData) common.CodeMessager {
|
||||
proc, err := os.FindProcess(syscall.Getpid())
|
||||
if err != nil {
|
||||
r.Code = 500
|
||||
r.Message = "couldn't find process. what the fuck?"
|
||||
return
|
||||
return common.SimpleResponse(500, "couldn't find process. what the fuck?")
|
||||
}
|
||||
const form = "02/01/2006"
|
||||
r.Code = 200
|
||||
r := common.ResponseBase{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("RIP ripple API %s - %s", upSince.Format(form), time.Now().Format(form)),
|
||||
}
|
||||
// yes
|
||||
r.Message = fmt.Sprintf("RIP ripple API %s - %s", upSince.Format(form), time.Now().Format(form))
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
proc.Kill()
|
||||
}()
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
||||
var upSince = time.Now()
|
||||
|
||||
type metaUpSinceResponse struct {
|
||||
common.ResponseBase
|
||||
Code int `json:"code"`
|
||||
Since int64 `json:"since"`
|
||||
}
|
||||
|
||||
// MetaUpSinceGET retrieves the moment the API application was started.
|
||||
// Mainly used to get if the API was restarted.
|
||||
func MetaUpSinceGET(md common.MethodData) common.Response {
|
||||
return common.Response{
|
||||
Code: 200,
|
||||
Data: upSince.UnixNano(),
|
||||
func MetaUpSinceGET(md common.MethodData) common.CodeMessager {
|
||||
return metaUpSinceResponse{
|
||||
Code: 200,
|
||||
Since: int64(upSince.UnixNano()),
|
||||
}
|
||||
}
|
||||
|
||||
// MetaUpdateGET updates the API to the latest version, and restarts it.
|
||||
func MetaUpdateGET(md common.MethodData) common.Response {
|
||||
func MetaUpdateGET(md common.MethodData) common.CodeMessager {
|
||||
if f, err := os.Stat(".git"); err == os.ErrNotExist || !f.IsDir() {
|
||||
return common.Response{
|
||||
Code: 500,
|
||||
Message: "repo is not using git",
|
||||
}
|
||||
return common.SimpleResponse(500, "instance is not using git")
|
||||
}
|
||||
go func() {
|
||||
// go get
|
||||
|
@ -87,10 +86,7 @@ func MetaUpdateGET(md common.MethodData) common.Response {
|
|||
}
|
||||
proc.Signal(syscall.SIGUSR2)
|
||||
}()
|
||||
return common.Response{
|
||||
Code: 200,
|
||||
Message: "Started updating! " + surpriseMe(),
|
||||
}
|
||||
return common.SimpleResponse(200, "Started updating! "+surpriseMe())
|
||||
}
|
||||
|
||||
func execCommand(command string, args ...string) bool {
|
||||
|
|
|
@ -75,42 +75,52 @@ var randomSentences = [...]string{
|
|||
"sudo rm -rf /",
|
||||
"Hi! I'm Flowey! Flowey the flower!",
|
||||
"Ripple devs are actually cats",
|
||||
"Support Howl's fund for buying a power supply for his SSD",
|
||||
"Support Howl's fund for buying a power supply for his SSD!",
|
||||
"Superman dies",
|
||||
"PP when?",
|
||||
"RWC hype",
|
||||
}
|
||||
|
||||
func surpriseMe() string {
|
||||
return randomSentences[rn.Intn(len(randomSentences))] + " " + kaomojis[rn.Intn(len(kaomojis))]
|
||||
}
|
||||
|
||||
type pingData struct {
|
||||
type pingResponse struct {
|
||||
common.ResponseBase
|
||||
ID int `json:"user_id"`
|
||||
Privileges int `json:"privileges"`
|
||||
}
|
||||
|
||||
// PingGET is a message to check with the API that we are logged in, and know what are our privileges.
|
||||
func PingGET(md common.MethodData) (r common.Response) {
|
||||
func PingGET(md common.MethodData) common.CodeMessager {
|
||||
var r pingResponse
|
||||
r.Code = 200
|
||||
|
||||
if md.ID() == 0 {
|
||||
r.Message = "You have not given us a token, so we don't know who you are! But you can still login with /api/v1/tokens/new " + kaomojis[rn.Intn(len(kaomojis))]
|
||||
} else {
|
||||
r.Message = surpriseMe()
|
||||
}
|
||||
r.Data = pingData{
|
||||
ID: md.ID(),
|
||||
Privileges: int(md.User.Privileges),
|
||||
}
|
||||
return
|
||||
|
||||
r.ID = md.ID()
|
||||
r.Privileges = int(md.User.Privileges)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
type surpriseMeResponse struct {
|
||||
common.ResponseBase
|
||||
Cats [100]string `json:"cats"`
|
||||
}
|
||||
|
||||
// SurpriseMeGET generates cute cats.
|
||||
//
|
||||
// ... Yes.
|
||||
func SurpriseMeGET(md common.MethodData) (r common.Response) {
|
||||
func SurpriseMeGET(md common.MethodData) common.CodeMessager {
|
||||
var r surpriseMeResponse
|
||||
r.Code = 200
|
||||
cats := make([]string, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
cats[i] = surpriseMe()
|
||||
r.Cats[i] = surpriseMe()
|
||||
}
|
||||
r.Data = cats
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
)
|
||||
|
||||
type privilegesData struct {
|
||||
common.ResponseBase
|
||||
Read bool `json:"read"`
|
||||
ReadConfidential bool `json:"read_confidential"`
|
||||
Write bool `json:"write"`
|
||||
|
@ -20,22 +21,21 @@ type privilegesData struct {
|
|||
}
|
||||
|
||||
// PrivilegesGET returns an explaination for the privileges, telling the client what they can do with this token.
|
||||
func PrivilegesGET(md common.MethodData) (r common.Response) {
|
||||
// This code sucks.
|
||||
func PrivilegesGET(md common.MethodData) common.CodeMessager {
|
||||
r := privilegesData{}
|
||||
r.Code = 200
|
||||
r.Data = privilegesData{
|
||||
Read: md.User.Privileges.HasPrivilegeRead(),
|
||||
ReadConfidential: md.User.Privileges.HasPrivilegeReadConfidential(),
|
||||
Write: md.User.Privileges.HasPrivilegeWrite(),
|
||||
ManageBadges: md.User.Privileges.HasPrivilegeManageBadges(),
|
||||
BetaKeys: md.User.Privileges.HasPrivilegeBetaKeys(),
|
||||
ManageSettings: md.User.Privileges.HasPrivilegeManageSettings(),
|
||||
ViewUserAdvanced: md.User.Privileges.HasPrivilegeViewUserAdvanced(),
|
||||
ManageUser: md.User.Privileges.HasPrivilegeManageUser(),
|
||||
ManageRoles: md.User.Privileges.HasPrivilegeManageRoles(),
|
||||
ManageAPIKeys: md.User.Privileges.HasPrivilegeManageAPIKeys(),
|
||||
Blog: md.User.Privileges.HasPrivilegeBlog(),
|
||||
APIMeta: md.User.Privileges.HasPrivilegeAPIMeta(),
|
||||
}
|
||||
return
|
||||
// This code sucks.
|
||||
r.Read = md.User.Privileges.HasPrivilegeRead()
|
||||
r.ReadConfidential = md.User.Privileges.HasPrivilegeReadConfidential()
|
||||
r.Write = md.User.Privileges.HasPrivilegeWrite()
|
||||
r.ManageBadges = md.User.Privileges.HasPrivilegeManageBadges()
|
||||
r.BetaKeys = md.User.Privileges.HasPrivilegeBetaKeys()
|
||||
r.ManageSettings = md.User.Privileges.HasPrivilegeManageSettings()
|
||||
r.ViewUserAdvanced = md.User.Privileges.HasPrivilegeViewUserAdvanced()
|
||||
r.ManageUser = md.User.Privileges.HasPrivilegeManageUser()
|
||||
r.ManageRoles = md.User.Privileges.HasPrivilegeManageRoles()
|
||||
r.ManageAPIKeys = md.User.Privileges.HasPrivilegeManageAPIKeys()
|
||||
r.Blog = md.User.Privileges.HasPrivilegeBlog()
|
||||
r.APIMeta = md.User.Privileges.HasPrivilegeAPIMeta()
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ type tokenNewInData struct {
|
|||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type tokenNewOutData struct {
|
||||
type tokenNewResponse struct {
|
||||
common.ResponseBase
|
||||
Username string `json:"username"`
|
||||
ID int `json:"id"`
|
||||
Privileges int `json:"privileges"`
|
||||
|
@ -28,12 +29,12 @@ type tokenNewOutData struct {
|
|||
}
|
||||
|
||||
// TokenNewPOST is the handler for POST /token/new.
|
||||
func TokenNewPOST(md common.MethodData) (r common.Response) {
|
||||
func TokenNewPOST(md common.MethodData) common.CodeMessager {
|
||||
var r tokenNewResponse
|
||||
data := tokenNewInData{}
|
||||
err := md.RequestData.Unmarshal(&data)
|
||||
if err != nil {
|
||||
r = ErrBadJSON
|
||||
return
|
||||
return ErrBadJSON
|
||||
}
|
||||
|
||||
var miss []string
|
||||
|
@ -44,8 +45,7 @@ func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|||
miss = append(miss, "password")
|
||||
}
|
||||
if len(miss) != 0 {
|
||||
r = ErrMissingField(miss...)
|
||||
return
|
||||
return ErrMissingField(miss...)
|
||||
}
|
||||
|
||||
var q *sql.Row
|
||||
|
@ -56,7 +56,6 @@ func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|||
q = md.DB.QueryRow(base+"WHERE username = ? LIMIT 1", data.Username)
|
||||
}
|
||||
|
||||
ret := tokenNewOutData{}
|
||||
var (
|
||||
rank int
|
||||
pw string
|
||||
|
@ -64,48 +63,37 @@ func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|||
allowed int
|
||||
)
|
||||
|
||||
err = q.Scan(&ret.ID, &ret.Username, &rank, &pw, &pwVersion, &allowed)
|
||||
err = q.Scan(&r.ID, &r.Username, &rank, &pw, &pwVersion, &allowed)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
r.Code = 404
|
||||
r.Message = "No user with that username/id was found."
|
||||
return
|
||||
return common.SimpleResponse(404, "No user with that username/id was found.")
|
||||
case err != nil:
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
|
||||
if nFailedAttempts(ret.ID) > 20 {
|
||||
r.Code = 429
|
||||
r.Message = "You've made too many login attempts. Try again later."
|
||||
return
|
||||
if nFailedAttempts(r.ID) > 20 {
|
||||
return common.SimpleResponse(429, "You've made too many login attempts. Try again later.")
|
||||
}
|
||||
|
||||
if pwVersion == 1 {
|
||||
r.Code = 418 // Teapots!
|
||||
r.Message = "That user still has a password in version 1. Unfortunately, in order for the API to check for the password to be OK, the user has to first log in through the website."
|
||||
return
|
||||
return common.SimpleResponse(418, "That user still has a password in version 1. Unfortunately, in order for the API to check for the password to be OK, the user has to first log in through the website.")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(pw), []byte(fmt.Sprintf("%x", md5.Sum([]byte(data.Password))))); err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
go addFailedAttempt(ret.ID)
|
||||
r.Code = 403
|
||||
r.Message = "That password doesn't match!"
|
||||
return
|
||||
go addFailedAttempt(r.ID)
|
||||
return common.SimpleResponse(403, "That password doesn't match!")
|
||||
}
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
if allowed == 0 {
|
||||
r.Code = 200
|
||||
r.Message = "That user is banned."
|
||||
ret.Banned = true
|
||||
r.Data = ret
|
||||
return
|
||||
r.Banned = true
|
||||
return r
|
||||
}
|
||||
ret.Privileges = int(common.Privileges(data.Privileges).CanOnly(rank))
|
||||
r.Privileges = int(common.Privileges(data.Privileges).CanOnly(rank))
|
||||
|
||||
var (
|
||||
tokenStr string
|
||||
|
@ -114,7 +102,7 @@ func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|||
for {
|
||||
tokenStr = common.RandomString(32)
|
||||
tokenMD5 = fmt.Sprintf("%x", md5.Sum([]byte(tokenStr)))
|
||||
ret.Token = tokenStr
|
||||
r.Token = tokenStr
|
||||
id := 0
|
||||
|
||||
err := md.DB.QueryRow("SELECT id FROM tokens WHERE token=? LIMIT 1", tokenMD5).Scan(&id)
|
||||
|
@ -123,18 +111,15 @@ func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|||
}
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
}
|
||||
_, err = md.DB.Exec("INSERT INTO tokens(user, privileges, description, token) VALUES (?, ?, ?, ?)", ret.ID, ret.Privileges, data.Description, tokenMD5)
|
||||
_, err = md.DB.Exec("INSERT INTO tokens(user, privileges, description, token) VALUES (?, ?, ?, ?)", r.ID, r.Privileges, data.Description, tokenMD5)
|
||||
if err != nil {
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
|
||||
r.Code = 200
|
||||
r.Data = ret
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
|
128
app/v1/user.go
128
app/v1/user.go
|
@ -13,6 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type userData struct {
|
||||
common.ResponseBase
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
UsernameAKA string `json:"username_aka"`
|
||||
|
@ -23,7 +24,7 @@ type userData struct {
|
|||
}
|
||||
|
||||
// UserByIDGET is the API handler for GET /users/id/:id
|
||||
func UserByIDGET(md common.MethodData) (r common.Response) {
|
||||
func UserByIDGET(md common.MethodData) common.CodeMessager {
|
||||
var err error
|
||||
var uid int
|
||||
uidStr := md.C.Param("id")
|
||||
|
@ -32,9 +33,7 @@ func UserByIDGET(md common.MethodData) (r common.Response) {
|
|||
} else {
|
||||
uid, err = strconv.Atoi(uidStr)
|
||||
if err != nil {
|
||||
r.Code = 400
|
||||
r.Message = fmt.Sprintf("%s ain't a number", uidStr)
|
||||
return
|
||||
return common.SimpleResponse(400, fmt.Sprintf("%s ain't a number", uidStr))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,12 +46,11 @@ LEFT JOIN users_stats
|
|||
ON users.id=users_stats.id
|
||||
WHERE users.id=? AND users.allowed='1'
|
||||
LIMIT 1`
|
||||
r = userPuts(md, md.DB.QueryRow(query, uid))
|
||||
return
|
||||
return userPuts(md, md.DB.QueryRow(query, uid))
|
||||
}
|
||||
|
||||
// UserByNameGET is the API handler for GET /users/name/:name
|
||||
func UserByNameGET(md common.MethodData) (r common.Response) {
|
||||
func UserByNameGET(md common.MethodData) common.CodeMessager {
|
||||
username := md.C.Param("name")
|
||||
|
||||
query := `
|
||||
|
@ -64,11 +62,10 @@ LEFT JOIN users_stats
|
|||
ON users.id=users_stats.id
|
||||
WHERE users.username=? AND users.allowed='1'
|
||||
LIMIT 1`
|
||||
r = userPuts(md, md.DB.QueryRow(query, username))
|
||||
return
|
||||
return userPuts(md, md.DB.QueryRow(query, username))
|
||||
}
|
||||
|
||||
func userPuts(md common.MethodData, row *sql.Row) (r common.Response) {
|
||||
func userPuts(md common.MethodData, row *sql.Row) common.CodeMessager {
|
||||
var err error
|
||||
var user userData
|
||||
|
||||
|
@ -80,13 +77,10 @@ func userPuts(md common.MethodData, row *sql.Row) (r common.Response) {
|
|||
err = row.Scan(&user.ID, &user.Username, ®isteredOn, &user.Rank, &latestActivity, &user.UsernameAKA, &user.Country, &showCountry)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
r.Code = 404
|
||||
r.Message = "No such user was found!"
|
||||
return
|
||||
return common.SimpleResponse(404, "No such user was found!")
|
||||
case err != nil:
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
|
||||
user.RegisteredOn = time.Unix(registeredOn, 0)
|
||||
|
@ -94,9 +88,8 @@ func userPuts(md common.MethodData, row *sql.Row) (r common.Response) {
|
|||
|
||||
user.Country = genCountry(md, user.ID, showCountry, user.Country)
|
||||
|
||||
r.Code = 200
|
||||
r.Data = user
|
||||
return
|
||||
user.Code = 200
|
||||
return user
|
||||
}
|
||||
|
||||
func badgesToArray(badges string) []int {
|
||||
|
@ -125,7 +118,7 @@ func genCountry(md common.MethodData, uid int, showCountry bool, country string)
|
|||
}
|
||||
|
||||
// UserSelfGET is a shortcut for /users/id/self. (/users/self)
|
||||
func UserSelfGET(md common.MethodData) common.Response {
|
||||
func UserSelfGET(md common.MethodData) common.CodeMessager {
|
||||
md.C.Params = append(md.C.Params, gin.Param{
|
||||
Key: "id",
|
||||
Value: "self",
|
||||
|
@ -133,23 +126,23 @@ func UserSelfGET(md common.MethodData) common.Response {
|
|||
return UserByIDGET(md)
|
||||
}
|
||||
|
||||
type whatIDResponse struct {
|
||||
common.ResponseBase
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
// UserWhatsTheIDGET is an API request that only returns an user's ID.
|
||||
func UserWhatsTheIDGET(md common.MethodData) common.Response {
|
||||
func UserWhatsTheIDGET(md common.MethodData) common.CodeMessager {
|
||||
var (
|
||||
id int
|
||||
r whatIDResponse
|
||||
allowed int
|
||||
)
|
||||
err := md.DB.QueryRow("SELECT id, allowed FROM users WHERE username = ? LIMIT 1", md.C.Param("username")).Scan(&id, &allowed)
|
||||
err := md.DB.QueryRow("SELECT id, allowed FROM users WHERE username = ? LIMIT 1", md.C.Param("username")).Scan(&r.ID, &allowed)
|
||||
if err != nil || (allowed != 1 && !md.User.Privileges.HasPrivilegeViewUserAdvanced()) {
|
||||
return common.Response{
|
||||
Code: 404,
|
||||
Message: "That user could not be found!",
|
||||
}
|
||||
}
|
||||
return common.Response{
|
||||
Code: 200,
|
||||
Data: id,
|
||||
return common.SimpleResponse(404, "That user could not be found!")
|
||||
}
|
||||
r.Code = 200
|
||||
return r
|
||||
}
|
||||
|
||||
type modeData struct {
|
||||
|
@ -162,7 +155,8 @@ type modeData struct {
|
|||
Accuracy float64 `json:"accuracy"`
|
||||
GlobalLeaderboardRank int `json:"global_leaderboard_rank"`
|
||||
}
|
||||
type userFullData struct {
|
||||
type userFullResponse struct {
|
||||
common.ResponseBase
|
||||
userData
|
||||
STD modeData `json:"std"`
|
||||
Taiko modeData `json:"taiko"`
|
||||
|
@ -174,7 +168,7 @@ type userFullData struct {
|
|||
}
|
||||
|
||||
// UserFullGET gets all of an user's information, with one exception: their userpage.
|
||||
func UserFullGET(md common.MethodData) (r common.Response) {
|
||||
func UserFullGET(md common.MethodData) common.CodeMessager {
|
||||
// Hellest query I've ever done.
|
||||
query := `
|
||||
SELECT
|
||||
|
@ -214,7 +208,7 @@ WHERE users.id=? AND users.allowed = '1'
|
|||
LIMIT 1
|
||||
`
|
||||
// Fuck.
|
||||
fd := userFullData{}
|
||||
r := userFullResponse{}
|
||||
var (
|
||||
badges string
|
||||
country string
|
||||
|
@ -223,63 +217,61 @@ LIMIT 1
|
|||
latestActivity int64
|
||||
)
|
||||
err := md.DB.QueryRow(query, md.C.Param("id")).Scan(
|
||||
&fd.ID, &fd.Username, ®isteredOn, &fd.Rank, &latestActivity,
|
||||
&r.ID, &r.Username, ®isteredOn, &r.Rank, &latestActivity,
|
||||
|
||||
&fd.UsernameAKA, &badges, &country, &showCountry,
|
||||
&fd.PlayStyle, &fd.FavouriteMode,
|
||||
&r.UsernameAKA, &badges, &country, &showCountry,
|
||||
&r.PlayStyle, &r.FavouriteMode,
|
||||
|
||||
&fd.STD.RankedScore, &fd.STD.TotalScore, &fd.STD.PlayCount,
|
||||
&fd.STD.ReplaysWatched, &fd.STD.TotalHits, &fd.STD.Level,
|
||||
&fd.STD.Accuracy, &fd.STD.GlobalLeaderboardRank,
|
||||
&r.STD.RankedScore, &r.STD.TotalScore, &r.STD.PlayCount,
|
||||
&r.STD.ReplaysWatched, &r.STD.TotalHits, &r.STD.Level,
|
||||
&r.STD.Accuracy, &r.STD.GlobalLeaderboardRank,
|
||||
|
||||
&fd.Taiko.RankedScore, &fd.Taiko.TotalScore, &fd.Taiko.PlayCount,
|
||||
&fd.Taiko.ReplaysWatched, &fd.Taiko.TotalHits, &fd.Taiko.Level,
|
||||
&fd.Taiko.Accuracy, &fd.Taiko.GlobalLeaderboardRank,
|
||||
&r.Taiko.RankedScore, &r.Taiko.TotalScore, &r.Taiko.PlayCount,
|
||||
&r.Taiko.ReplaysWatched, &r.Taiko.TotalHits, &r.Taiko.Level,
|
||||
&r.Taiko.Accuracy, &r.Taiko.GlobalLeaderboardRank,
|
||||
|
||||
&fd.CTB.RankedScore, &fd.CTB.TotalScore, &fd.CTB.PlayCount,
|
||||
&fd.CTB.ReplaysWatched, &fd.CTB.TotalHits, &fd.CTB.Level,
|
||||
&fd.CTB.Accuracy, &fd.CTB.GlobalLeaderboardRank,
|
||||
&r.CTB.RankedScore, &r.CTB.TotalScore, &r.CTB.PlayCount,
|
||||
&r.CTB.ReplaysWatched, &r.CTB.TotalHits, &r.CTB.Level,
|
||||
&r.CTB.Accuracy, &r.CTB.GlobalLeaderboardRank,
|
||||
|
||||
&fd.Mania.RankedScore, &fd.Mania.TotalScore, &fd.Mania.PlayCount,
|
||||
&fd.Mania.ReplaysWatched, &fd.Mania.TotalHits, &fd.Mania.Level,
|
||||
&fd.Mania.Accuracy, &fd.Mania.GlobalLeaderboardRank,
|
||||
&r.Mania.RankedScore, &r.Mania.TotalScore, &r.Mania.PlayCount,
|
||||
&r.Mania.ReplaysWatched, &r.Mania.TotalHits, &r.Mania.Level,
|
||||
&r.Mania.Accuracy, &r.Mania.GlobalLeaderboardRank,
|
||||
)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
r.Code = 404
|
||||
r.Message = "That user could not be found!"
|
||||
return
|
||||
return common.SimpleResponse(404, "That user could not be found!")
|
||||
case err != nil:
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
|
||||
fd.Country = genCountry(md, fd.ID, showCountry, country)
|
||||
fd.Badges = badgesToArray(badges)
|
||||
r.Country = genCountry(md, r.ID, showCountry, country)
|
||||
r.Badges = badgesToArray(badges)
|
||||
|
||||
fd.RegisteredOn = time.Unix(registeredOn, 0)
|
||||
fd.LatestActivity = time.Unix(latestActivity, 0)
|
||||
r.RegisteredOn = time.Unix(registeredOn, 0)
|
||||
r.LatestActivity = time.Unix(latestActivity, 0)
|
||||
|
||||
r.Code = 200
|
||||
r.Data = fd
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
||||
type userpageResponse struct {
|
||||
common.ResponseBase
|
||||
Userpage string `json:"userpage"`
|
||||
}
|
||||
|
||||
// UserUserpageGET gets an user's userpage, as in the customisable thing.
|
||||
func UserUserpageGET(md common.MethodData) (r common.Response) {
|
||||
var userpage string
|
||||
err := md.DB.QueryRow("SELECT userpage_content FROM users_stats WHERE id = ? LIMIT 1", md.C.Param("id")).Scan(&userpage)
|
||||
func UserUserpageGET(md common.MethodData) common.CodeMessager {
|
||||
var r userpageResponse
|
||||
err := md.DB.QueryRow("SELECT userpage_content FROM users_stats WHERE id = ? LIMIT 1", md.C.Param("id")).Scan(&r.Userpage)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
r.Code = 404
|
||||
r.Message = "No user with that user ID!"
|
||||
return common.SimpleResponse(404, "No user with that user ID!")
|
||||
case err != nil:
|
||||
md.Err(err)
|
||||
r = Err500
|
||||
return
|
||||
return Err500
|
||||
}
|
||||
r.Code = 200
|
||||
r.Data = userpage
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -1,8 +1,36 @@
|
|||
package common
|
||||
|
||||
// Response is the data that is always returned with an API request.
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data"`
|
||||
// ResponseBase is the data that is always returned with an API request.
|
||||
type ResponseBase struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// GetCode retrieves the response code.
|
||||
func (r ResponseBase) GetCode() int {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
// SetCode changes the response code.
|
||||
func (r *ResponseBase) SetCode(i int) {
|
||||
r.Code = i
|
||||
}
|
||||
|
||||
// GetMessage retrieves the response message.
|
||||
func (r ResponseBase) GetMessage() string {
|
||||
return r.Message
|
||||
}
|
||||
|
||||
// CodeMessager is something that has the Code() and Message() methods.
|
||||
type CodeMessager interface {
|
||||
GetMessage() string
|
||||
GetCode() int
|
||||
}
|
||||
|
||||
// SimpleResponse returns the most basic response.
|
||||
func SimpleResponse(code int, message string) CodeMessager {
|
||||
return ResponseBase{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user