diff --git a/app/method.go b/app/method.go index d8b73a0..7df01c0 100644 --- a/app/method.go +++ b/app/method.go @@ -48,6 +48,7 @@ func initialCaretaker(c *gin.Context, f func(md common.MethodData) common.CodeMe RequestData: data, C: c, Doggo: doggo, + R: red, } if token != "" { tokenReal, exists := GetTokenFull(token, db) diff --git a/app/start.go b/app/start.go index aacc615..3ac683c 100644 --- a/app/start.go +++ b/app/start.go @@ -13,12 +13,14 @@ import ( "github.com/gin-gonic/gin" "github.com/jmoiron/sqlx" "github.com/serenize/snaker" + "gopkg.in/redis.v5" ) var ( db *sqlx.DB cf common.Conf doggo *statsd.Client + red *redis.Client ) var commonClusterfucks = map[string]string{ @@ -65,6 +67,13 @@ func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine { doggo.Incr("requests", nil, 1) }) + // redis + red = redis.NewClient(&redis.Options{ + Addr: conf.RedisAddr, + Password: conf.RedisPassword, + DB: conf.RedisDB, + }) + api := r.Group("/api") { p := api.Group("/") @@ -119,7 +128,7 @@ func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine { gv1.POST("/friends/del", Method(v1.FriendsDelPOST, common.PrivilegeWrite)) gv1.POST("/users/self/settings", Method(v1.UsersSelfSettingsPOST, common.PrivilegeWrite)) gv1.POST("/users/self/userpage", Method(v1.UserSelfUserpagePOST, common.PrivilegeWrite)) - //gv1.POST("/beatmaps/rank_requests", Method(v1.BeatmapRankRequestsSubmitPOST, common.PrivilegeWrite)) + gv1.POST("/beatmaps/rank_requests", Method(v1.BeatmapRankRequestsSubmitPOST, common.PrivilegeWrite)) // Admin: beatmap gv1.POST("/beatmaps/set_status", Method(v1.BeatmapSetStatusPOST, common.PrivilegeBeatmap)) diff --git a/app/v1/beatmap_requests.go b/app/v1/beatmap_requests.go index 51c5c7a..42d6c40 100644 --- a/app/v1/beatmap_requests.go +++ b/app/v1/beatmap_requests.go @@ -5,7 +5,6 @@ import ( "strconv" "time" - "git.zxq.co/ripple/rippleapi/beatmapget" "git.zxq.co/ripple/rippleapi/common" "git.zxq.co/ripple/rippleapi/limit" ) @@ -106,26 +105,45 @@ func BeatmapRankRequestsSubmitPOST(md common.MethodData) common.CodeMessager { return common.SimpleResponse(403, "It's not possible to do a rank request at this time.") } - if d.SetID == 0 { - d.SetID, err = beatmapget.Beatmap(d.ID) - } else { - err = beatmapget.Set(d.SetID) + w := common. + Where("beatmap_id = ?", strconv.Itoa(d.ID)).Or(). + Where("beatmapset_id = ?", strconv.Itoa(d.SetID)) + + var ranked int + err = md.DB.QueryRow("SELECT ranked FROM beatmaps "+w.Clause+" LIMIT 1", w.Params...).Scan(&ranked) + if ranked >= 2 { + return common.SimpleResponse(406, "That beatmap is already ranked.") } - if err == beatmapget.ErrBeatmapNotFound { - return common.SimpleResponse(404, "That beatmap could not be found anywhere!") - } - if err != nil { + + switch err { + case nil: + // move on + case sql.ErrNoRows: + if d.SetID != 0 { + md.R.Publish("ripple:beatmap_updates:sets", strconv.Itoa(d.SetID)) + } else { + md.R.Publish("ripple:beatmap_updates:single", strconv.Itoa(d.ID)) + } + default: md.Err(err) return Err500 } + // type and value of beatmap rank request + t := "b" + v := d.ID + if d.SetID != 0 { + t = "s" + v = d.SetID + } err = md.DB.QueryRow("SELECT 1 FROM rank_requests WHERE bid = ? AND type = ? AND time > ?", - d.SetID, "s", time.Now().Unix()).Scan(new(int)) + v, t, time.Now().Add(-time.Hour*24).Unix()).Scan(new(int)) + + // error handling switch err { case sql.ErrNoRows: break case nil: - // TODO: return beatmap // we're returning a success because if the request was already sent in the past 24 // hours, it's as if the user submitted it. return common.SimpleResponse(200, "Your request has been submitted.") @@ -136,12 +154,11 @@ func BeatmapRankRequestsSubmitPOST(md common.MethodData) common.CodeMessager { _, err = md.DB.Exec( "INSERT INTO rank_requests (userid, bid, type, time, blacklisted) VALUES (?, ?, ?, ?, 0)", - md.ID(), d.SetID, "s", time.Now().Unix()) + md.ID(), v, t, time.Now().Unix()) if err != nil { md.Err(err) return Err500 } - // TODO: return beatmap return common.SimpleResponse(200, "Your request has been submitted.") } diff --git a/common/conf.go b/common/conf.go index d9d83d7..19130b0 100644 --- a/common/conf.go +++ b/common/conf.go @@ -22,6 +22,9 @@ type Conf struct { BeatmapRequestsPerUser int RankQueueSize int OsuAPIKey string + RedisAddr string + RedisPassword string + RedisDB int } var cachedConf *Conf @@ -43,6 +46,7 @@ func Load() (c Conf, halt bool) { HanayoKey: "Potato", BeatmapRequestsPerUser: 2, RankQueueSize: 25, + RedisAddr: "localhost:6379", }, "api.conf") fmt.Println("Please compile the configuration file (api.conf).") } diff --git a/common/method_data.go b/common/method_data.go index c60c187..d1636b4 100644 --- a/common/method_data.go +++ b/common/method_data.go @@ -6,6 +6,7 @@ import ( "github.com/DataDog/datadog-go/statsd" "github.com/gin-gonic/gin" "github.com/jmoiron/sqlx" + "gopkg.in/redis.v5" ) // MethodData is a struct containing the data passed over to an API method. @@ -15,6 +16,7 @@ type MethodData struct { RequestData RequestData C *gin.Context Doggo *statsd.Client + R *redis.Client } // Err logs an error into gin. diff --git a/common/where.go b/common/where.go index 60e09be..3da0fca 100644 --- a/common/where.go +++ b/common/where.go @@ -5,6 +5,7 @@ package common type WhereClause struct { Clause string Params []interface{} + useOr bool } // Where adds a new WHERE clause to the WhereClause. @@ -26,10 +27,26 @@ func (w *WhereClause) addWhere() { if w.Clause == "" { w.Clause += "WHERE " } else { + if w.useOr { + w.Clause += " OR " + return + } w.Clause += " AND " } } +// Or enables using OR instead of AND +func (w *WhereClause) Or() *WhereClause { + w.useOr = true + return w +} + +// And enables using AND instead of OR +func (w *WhereClause) And() *WhereClause { + w.useOr = false + return w +} + // In generates an IN clause. // initial is the initial part, e.g. "users.id". // Fields are the possible values.