diff --git a/app/start.go b/app/start.go index 99b8429..c820334 100644 --- a/app/start.go +++ b/app/start.go @@ -101,6 +101,7 @@ func Start(conf common.Conf, dbO *sqlx.DB) *gin.Engine { // Write privilege required gv1.GET("/friends/add", Method(v1.FriendsAddGET, common.PrivilegeWrite)) gv1.GET("/friends/del", Method(v1.FriendsDelGET, 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 09c16a5..fb0e560 100644 --- a/app/v1/beatmap_requests.go +++ b/app/v1/beatmap_requests.go @@ -1,19 +1,22 @@ package v1 import ( + "database/sql" "strconv" "time" + "git.zxq.co/ripple/rippleapi/beatmapget" "git.zxq.co/ripple/rippleapi/common" + "git.zxq.co/ripple/rippleapi/limit" ) type rankRequestsStatusResponse struct { common.ResponseBase QueueSize int `json:"queue_size"` - MaxPeruser int `json:"max_per_user"` + MaxPerUser int `json:"max_per_user"` Submitted int `json:"submitted"` - SubmittedByUser int `json:"submitted_by_user"` - CanSubmit bool `json:"can_submit"` + SubmittedByUser *int `json:"submitted_by_user,omitempty"` + CanSubmit *bool `json:"can_submit,omitempty"` NextExpiration *time.Time `json:"next_expiration"` } @@ -26,6 +29,9 @@ func BeatmapRankRequestsStatusGET(md common.MethodData) common.CodeMessager { return Err500 } var r rankRequestsStatusResponse + if md.ID() != 0 { + r.SubmittedByUser = new(int) + } isFirst := true for rows.Next() { var ( @@ -37,9 +43,13 @@ func BeatmapRankRequestsStatusGET(md common.MethodData) common.CodeMessager { md.Err(err) continue } + // if the user submitted this rank request, increase the number of + // rank requests submitted by this user if user == md.ID() { - r.SubmittedByUser++ + (*r.SubmittedByUser)++ } + // also, if this is the first result, it means it will be the next to + // expire. if isFirst { x := time.Time(timestamp) r.NextExpiration = &x @@ -48,8 +58,87 @@ func BeatmapRankRequestsStatusGET(md common.MethodData) common.CodeMessager { r.Submitted++ } r.QueueSize = c.RankQueueSize - r.MaxPeruser = c.BeatmapRequestsPerUser - r.CanSubmit = r.Submitted < r.QueueSize && r.SubmittedByUser < r.MaxPeruser + r.MaxPerUser = c.BeatmapRequestsPerUser + if md.ID() != 0 { + x := r.Submitted < r.QueueSize && *r.SubmittedByUser < r.MaxPerUser + r.CanSubmit = &x + } r.Code = 200 return r } + +type submitRequestData struct { + ID int `json:"id"` + SetID int `json:"set_id"` +} + +// BeatmapRankRequestsSubmitPOST submits a new beatmap for ranking approval. +func BeatmapRankRequestsSubmitPOST(md common.MethodData) common.CodeMessager { + var d submitRequestData + err := md.RequestData.Unmarshal(&d) + if err != nil { + return ErrBadJSON + } + // check json data is present + if d.ID == 0 && d.SetID == 0 { + return ErrMissingField("id|set_id") + } + + // you've been rate limited + if !limit.NonBlockingRequest("rankrequest:u:"+strconv.Itoa(md.ID()), 5) { + return common.SimpleResponse(429, "You may only try to request 5 beatmaps per minute.") + } + if !limit.NonBlockingRequest("rankrequest:ip:"+md.C.ClientIP(), 8) { + return common.SimpleResponse(429, "You may only try to request 8 beatmaps per minute from the same IP.") + } + + // find out from BeatmapRankRequestsStatusGET if we can submit beatmaps. + statusRaw := BeatmapRankRequestsStatusGET(md) + status, ok := statusRaw.(rankRequestsStatusResponse) + if !ok { + // if it's not a rankRequestsStatusResponse, it means it's an error + return statusRaw + } + if !*status.CanSubmit { + 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) + } + if err == beatmapget.ErrBeatmapNotFound { + return common.SimpleResponse(404, "That beatmap could not be found anywhere!") + } + if err != nil { + md.Err(err) + return Err500 + } + + err = md.DB.QueryRow("SELECT 1 FROM rank_requests WHERE bid = ? AND type = ? AND time > ?", + d.SetID, "s", time.Now().Unix()).Scan(new(int)) + 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.") + default: + md.Err(err) + return Err500 + } + + _, err = md.DB.Exec( + "INSERT INTO rank_requests (userid, bid, type, time, blacklisted) VALUES (?, ?, ?, ?, 0)", + md.ID(), d.SetID, "s", 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/beatmapget/beatmapget.go b/beatmapget/beatmapget.go index 69b64d6..44a6ec8 100644 --- a/beatmapget/beatmapget.go +++ b/beatmapget/beatmapget.go @@ -119,8 +119,7 @@ func UpdateRequired(b *BeatmapDefiningQuality) (bool, error) { expire *= 6 } - if expire != 0 && time.Now().After(time.Time(data.LatestUpdate).Add(expire)) && - !data.Frozen { + if expire != 0 && time.Now().After(time.Time(data.LatestUpdate).Add(expire)) && !data.Frozen { return true, nil } return false, nil diff --git a/beatmapget/fullset.go b/beatmapget/fullset.go index 2f89bf7..d6212e3 100644 --- a/beatmapget/fullset.go +++ b/beatmapget/fullset.go @@ -1,6 +1,8 @@ package beatmapget import ( + "database/sql" + "errors" "time" "git.zxq.co/ripple/rippleapi/common" @@ -13,28 +15,44 @@ func Set(s int) error { lastUpdated common.UnixTimestamp ranked int ) - err := DB.QueryRow("SELECT last_updated, ranked FROM beatmaps WHERE beatmapset_id = ? LIMIT 1", s). + err := DB.QueryRow("SELECT latest_update, ranked FROM beatmaps WHERE beatmapset_id = ? LIMIT 1", s). Scan(&lastUpdated, &ranked) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } return set(s, lastUpdated, ranked) } +// ErrBeatmapNotFound is returned by Beatmap if a beatmap could not be found. +var ErrBeatmapNotFound = errors.New("beatmapget: beatmap not found") + // Beatmap check if an update is required for all beatmaps in the set // containing this beatmap. -func Beatmap(b int) error { +func Beatmap(b int) (int, error) { var ( setID int lastUpdated common.UnixTimestamp ranked int ) - err := DB.QueryRow("SELECT beatmapset_id, last_updated, ranked FROM beatmaps WHERE beatmap_id = ? LIMIT 1", b). + err := DB.QueryRow("SELECT beatmapset_id, latest_update, ranked FROM beatmaps WHERE beatmap_id = ? LIMIT 1", b). Scan(&setID, &lastUpdated, &ranked) - if err != nil { - return err + switch err { + case nil: + return setID, set(setID, lastUpdated, ranked) + case sql.ErrNoRows: + beatmaps, err := Client.GetBeatmaps(osuapi.GetBeatmapsOpts{ + BeatmapID: b, + }) + if err != nil { + return 0, err + } + if len(beatmaps) == 0 { + return 0, ErrBeatmapNotFound + } + return beatmaps[0].BeatmapSetID, set(beatmaps[0].BeatmapSetID, common.UnixTimestamp(time.Time{}), 0) + default: + return setID, err } - return set(setID, lastUpdated, ranked) } func set(s int, updated common.UnixTimestamp, ranked int) error {