diff --git a/app/v1/beatmap.go b/app/v1/beatmap.go index 3adedba..de559d0 100644 --- a/app/v1/beatmap.go +++ b/app/v1/beatmap.go @@ -2,24 +2,23 @@ package v1 import ( "database/sql" - "time" "git.zxq.co/ripple/rippleapi/common" ) type beatmap struct { - BeatmapID int `json:"beatmap_id"` - BeatmapsetID int `json:"beatmapset_id"` - BeatmapMD5 string `json:"beatmap_md5"` - SongName string `json:"song_name"` - AR float32 `json:"ar"` - OD float32 `json:"od"` - Difficulty float64 `json:"difficulty"` - MaxCombo int `json:"max_combo"` - HitLength int `json:"hit_length"` - Ranked int `json:"ranked"` - RankedStatusFrozen int `json:"ranked_status_frozen"` - LatestUpdate time.Time `json:"latest_update"` + BeatmapID int `json:"beatmap_id"` + BeatmapsetID int `json:"beatmapset_id"` + BeatmapMD5 string `json:"beatmap_md5"` + SongName string `json:"song_name"` + AR float32 `json:"ar"` + OD float32 `json:"od"` + Difficulty float64 `json:"difficulty"` + MaxCombo int `json:"max_combo"` + HitLength int `json:"hit_length"` + Ranked int `json:"ranked"` + RankedStatusFrozen int `json:"ranked_status_frozen"` + LatestUpdate common.UnixTimestamp `json:"latest_update"` } type beatmapMayOrMayNotExist struct { @@ -34,7 +33,7 @@ type beatmapMayOrMayNotExist struct { HitLength *int Ranked *int RankedStatusFrozen *int - LatestUpdate *time.Time + LatestUpdate *common.UnixTimestamp } func (b *beatmapMayOrMayNotExist) toBeatmap() *beatmap { @@ -146,21 +145,17 @@ func getSet(md common.MethodData, setID int) common.CodeMessager { } var r beatmapSetResponse for rows.Next() { - var ( - b beatmap - rawLatestUpdate int64 - ) + var b beatmap err = rows.Scan( &b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5, &b.SongName, &b.AR, &b.OD, &b.Difficulty, &b.MaxCombo, &b.HitLength, &b.Ranked, &b.RankedStatusFrozen, - &rawLatestUpdate, + &b.LatestUpdate, ) if err != nil { md.Err(err) continue } - b.LatestUpdate = time.Unix(rawLatestUpdate, 0) r.Beatmaps = append(r.Beatmaps, b) } r.Code = 200 @@ -168,15 +163,12 @@ func getSet(md common.MethodData, setID int) common.CodeMessager { } func getBeatmap(md common.MethodData, beatmapID int) common.CodeMessager { - var ( - b beatmap - rawLatestUpdate int64 - ) + var b beatmap err := md.DB.QueryRow(baseBeatmapSelect+"WHERE beatmap_id = ? LIMIT 1", beatmapID).Scan( &b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5, &b.SongName, &b.AR, &b.OD, &b.Difficulty, &b.MaxCombo, &b.HitLength, &b.Ranked, &b.RankedStatusFrozen, - &rawLatestUpdate, + &b.LatestUpdate, ) switch { case err == sql.ErrNoRows: @@ -185,7 +177,6 @@ func getBeatmap(md common.MethodData, beatmapID int) common.CodeMessager { md.Err(err) return Err500 } - b.LatestUpdate = time.Unix(rawLatestUpdate, 0) var r beatmapResponse r.Code = 200 r.beatmap = b diff --git a/app/v1/friend.go b/app/v1/friend.go index 44ed9a7..77ff1ea 100644 --- a/app/v1/friend.go +++ b/app/v1/friend.go @@ -2,7 +2,6 @@ package v1 import ( "database/sql" - "time" "git.zxq.co/ripple/rippleapi/common" ) @@ -87,18 +86,13 @@ ORDER BY users_relationships.id` func friendPuts(md common.MethodData, row *sql.Rows) (user friendData) { var err error - registeredOn := int64(0) - latestActivity := int64(0) var showcountry bool - err = row.Scan(&user.ID, &user.Username, ®isteredOn, &user.Privileges, &latestActivity, &user.UsernameAKA, &user.Country, &showcountry) + err = row.Scan(&user.ID, &user.Username, &user.RegisteredOn, &user.Privileges, &user.LatestActivity, &user.UsernameAKA, &user.Country, &showcountry) if err != nil { md.Err(err) return } - user.RegisteredOn = time.Unix(registeredOn, 0) - user.LatestActivity = time.Unix(latestActivity, 0) - // If the user wants to stay anonymous, don't show their country. // This can be overriden if we have the ReadConfidential privilege and the user we are accessing is the token owner. if !(showcountry || (md.User.Privileges.HasPrivilegeReadConfidential() && user.ID == md.ID())) { diff --git a/app/v1/leaderboard.go b/app/v1/leaderboard.go index 3c5b435..c27ce50 100644 --- a/app/v1/leaderboard.go +++ b/app/v1/leaderboard.go @@ -2,7 +2,6 @@ package v1 import ( "fmt" - "time" "git.zxq.co/ripple/rippleapi/common" ) @@ -47,13 +46,11 @@ func LeaderboardGET(md common.MethodData) common.CodeMessager { var resp leaderboardResponse for rows.Next() { var ( - u leaderboardUser - register int64 - latestActivity int64 - showCountry bool + u leaderboardUser + showCountry bool ) err := rows.Scan( - &u.ID, &u.Username, ®ister, &u.Privileges, &latestActivity, + &u.ID, &u.Username, &u.RegisteredOn, &u.Privileges, &u.LatestActivity, &u.UsernameAKA, &u.Country, &showCountry, &u.PlayStyle, &u.FavouriteMode, @@ -69,8 +66,6 @@ func LeaderboardGET(md common.MethodData) common.CodeMessager { if !showCountry { u.Country = "XX" } - u.RegisteredOn = time.Unix(register, 0) - u.LatestActivity = time.Unix(latestActivity, 0) resp.Users = append(resp.Users, u) } resp.Code = 200 diff --git a/app/v1/user.go b/app/v1/user.go index 845b876..4049b21 100644 --- a/app/v1/user.go +++ b/app/v1/user.go @@ -5,20 +5,19 @@ import ( "database/sql" "strconv" "strings" - "time" "git.zxq.co/ripple/ocl" "git.zxq.co/ripple/rippleapi/common" ) type userData struct { - ID int `json:"id"` - Username string `json:"username"` - UsernameAKA string `json:"username_aka"` - RegisteredOn time.Time `json:"registered_on"` - Privileges uint64 `json:"privileges"` - LatestActivity time.Time `json:"latest_activity"` - Country string `json:"country"` + ID int `json:"id"` + Username string `json:"username"` + UsernameAKA string `json:"username_aka"` + RegisteredOn common.UnixTimestamp `json:"registered_on"` + Privileges uint64 `json:"privileges"` + LatestActivity common.UnixTimestamp `json:"latest_activity"` + Country string `json:"country"` } // UsersGET is the API handler for GET /users @@ -49,12 +48,11 @@ func userPuts(md common.MethodData, row *sql.Row) common.CodeMessager { var err error var user userPutsUserData - var ( - registeredOn int64 - latestActivity int64 - showCountry bool + var showCountry bool + err = row.Scan( + &user.ID, &user.Username, &user.RegisteredOn, &user.Privileges, &user.LatestActivity, + &user.UsernameAKA, &user.Country, &showCountry, ) - err = row.Scan(&user.ID, &user.Username, ®isteredOn, &user.Privileges, &latestActivity, &user.UsernameAKA, &user.Country, &showCountry) switch { case err == sql.ErrNoRows: return common.SimpleResponse(404, "No such user was found!") @@ -63,9 +61,6 @@ func userPuts(md common.MethodData, row *sql.Row) common.CodeMessager { return Err500 } - user.RegisteredOn = time.Unix(registeredOn, 0) - user.LatestActivity = time.Unix(latestActivity, 0) - user.Country = genCountry(md, user.ID, showCountry, user.Country) user.Code = 200 @@ -191,14 +186,12 @@ LIMIT 1 // Fuck. r := userFullResponse{} var ( - badges string - country string - showCountry bool - registeredOn int64 - latestActivity int64 + badges string + country string + showCountry bool ) err := md.DB.QueryRow(query, param).Scan( - &r.ID, &r.Username, ®isteredOn, &r.Privileges, &latestActivity, + &r.ID, &r.Username, &r.RegisteredOn, &r.Privileges, &r.LatestActivity, &r.UsernameAKA, &badges, &country, &showCountry, &r.PlayStyle, &r.FavouriteMode, @@ -230,9 +223,6 @@ LIMIT 1 r.Country = genCountry(md, r.ID, showCountry, country) r.Badges = badgesToArray(badges) - r.RegisteredOn = time.Unix(registeredOn, 0) - r.LatestActivity = time.Unix(latestActivity, 0) - for _, m := range []*modeData{&r.STD, &r.Taiko, &r.CTB, &r.Mania} { m.Level = ocl.GetLevelPrecise(int64(m.TotalScore)) } diff --git a/app/v1/user_scores.go b/app/v1/user_scores.go index b853bea..483f2ce 100644 --- a/app/v1/user_scores.go +++ b/app/v1/user_scores.go @@ -128,10 +128,9 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{}) var scores []userScore for rows.Next() { var ( - us userScore - t string - b beatmapMayOrMayNotExist - rawLatestUpdate *int64 + us userScore + t string + b beatmapMayOrMayNotExist ) err = rows.Scan( &us.ID, &us.BeatmapMD5, &us.Score, @@ -144,7 +143,7 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{}) &b.BeatmapID, &b.BeatmapsetID, &b.BeatmapMD5, &b.SongName, &b.AR, &b.OD, &b.Difficulty, &b.MaxCombo, &b.HitLength, &b.Ranked, - &b.RankedStatusFrozen, &rawLatestUpdate, + &b.RankedStatusFrozen, &b.LatestUpdate, ) if err != nil { md.Err(err) @@ -156,11 +155,6 @@ func scoresPuts(md common.MethodData, whereClause string, params ...interface{}) md.Err(err) return Err500 } - if rawLatestUpdate != nil { - // fml i should have used an inner join - xd := time.Unix(*rawLatestUpdate, 0) - b.LatestUpdate = &xd - } us.Beatmap = b.toBeatmap() scores = append(scores, us) } diff --git a/common/unix_timestamp.go b/common/unix_timestamp.go new file mode 100644 index 0000000..8125945 --- /dev/null +++ b/common/unix_timestamp.go @@ -0,0 +1,55 @@ +package common + +import ( + "errors" + "strconv" + "time" +) + +// UnixTimestamp is simply a time.Time, but can be used to convert an +// unix timestamp in the database into a native time.Time. +type UnixTimestamp time.Time + +// Scan decodes src into an unix timestamp. +func (u *UnixTimestamp) Scan(src interface{}) error { + if u == nil { + return errors.New("rippleapi/common: UnixTimestamp is nil") + } + switch src := src.(type) { + case int64: + *u = UnixTimestamp(time.Unix(src, 0)) + case float64: + *u = UnixTimestamp(time.Unix(int64(src), 0)) + case string: + return u._string(src) + case []byte: + return u._string(string(src)) + case nil: + // Nothing, leave zero value on timestamp + default: + return errors.New("rippleapi/common: unhandleable type") + } + return nil +} + +func (u *UnixTimestamp) _string(s string) error { + ts, err := strconv.Atoi(s) + if err != nil { + return err + } + *u = UnixTimestamp(time.Unix(int64(ts), 0)) + return nil +} + +// MarshalJSON -> time.Time.MarshalJSON +func (u UnixTimestamp) MarshalJSON() ([]byte, error) { + return time.Time(u).MarshalJSON() +} + +// UnmarshalJSON -> time.Time.UnmarshalJSON +func (u *UnixTimestamp) UnmarshalJSON(x []byte) error { + t := new(time.Time) + err := t.UnmarshalJSON(x) + *u = UnixTimestamp(*t) + return err +}