Completely change response structure

This commit is contained in:
Howl 2016-04-16 18:05:24 +02:00
parent 14d926e31d
commit 7387ed4295
12 changed files with 280 additions and 277 deletions

View File

@ -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 {

View File

@ -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{
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!",
Data: surpriseMe(),
},
Cats: surpriseMe(),
})
}

View File

@ -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
}

View File

@ -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, ", ") + ".",
}

View File

@ -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{
r := friendsWithResponse{
Friends: false,
Mutual: false,
},
}
r.Code = 200
return r
}

View File

@ -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))
}

View File

@ -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{
func MetaUpSinceGET(md common.MethodData) common.CodeMessager {
return metaUpSinceResponse{
Code: 200,
Data: upSince.UnixNano(),
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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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, &registeredOn, &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, &registeredOn, &fd.Rank, &latestActivity,
&r.ID, &r.Username, &registeredOn, &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
}

View File

@ -1,8 +1,36 @@
package common
// Response is the data that is always returned with an API request.
type Response struct {
// ResponseBase is the data that is always returned with an API request.
type ResponseBase struct {
Code int `json:"code"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data"`
}
// 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,
}
}