mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/x0gp/server/internal/races"
|
|
"github.com/x0gp/server/internal/transport"
|
|
)
|
|
|
|
// leaderboardHandler godoc
|
|
// @Summary Leaderboard
|
|
// @Description Returns paginated leaderboard list. Supports overall leaderboard or track-specific leaderboard for the current year.
|
|
// @Tags leaderboard
|
|
// @Produce json
|
|
// @Param track_id query string false "Track ID. If empty, returns overall leaderboard."
|
|
// @Param year query int false "Year. Defaults to current year."
|
|
// @Param limit query int false "Limit (1..200, default 50)"
|
|
// @Param offset query int false "Offset (default 0)"
|
|
// @Param X-Driver-Id header string false "Current driver ID. Defaults to driver-seed-001 (mock Alice)."
|
|
// @Success 200 {object} transport.LeaderboardResponse "Leaderboard page and current driver position"
|
|
// @Failure 400 {object} map[string]interface{} "Invalid input"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error"
|
|
// @Router /api/leaderboard [get]
|
|
func leaderboardHandler(svc *races.Service) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
trackID := r.URL.Query().Get("track_id")
|
|
|
|
yearVal := r.URL.Query().Get("year")
|
|
var year int
|
|
if yearVal != "" {
|
|
var err error
|
|
year, err = strconv.Atoi(yearVal)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_input", "invalid year")
|
|
return
|
|
}
|
|
}
|
|
|
|
limit := 50
|
|
limitVal := r.URL.Query().Get("limit")
|
|
if limitVal != "" {
|
|
var err error
|
|
limit, err = strconv.Atoi(limitVal)
|
|
if err != nil || limit <= 0 {
|
|
writeError(w, http.StatusBadRequest, "invalid_input", "invalid limit")
|
|
return
|
|
}
|
|
if limit > 200 {
|
|
limit = 200
|
|
}
|
|
}
|
|
|
|
offset := 0
|
|
offsetVal := r.URL.Query().Get("offset")
|
|
if offsetVal != "" {
|
|
var err error
|
|
offset, err = strconv.Atoi(offsetVal)
|
|
if err != nil || offset < 0 {
|
|
writeError(w, http.StatusBadRequest, "invalid_input", "invalid offset")
|
|
return
|
|
}
|
|
}
|
|
|
|
driverID := r.Header.Get("X-Driver-Id")
|
|
if driverID == "" {
|
|
driverID = "driver-seed-001" // mock current driver
|
|
}
|
|
|
|
res, err := svc.GetLeaderboard(r.Context(), trackID, year, limit, offset, driverID)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "internal", err.Error())
|
|
return
|
|
}
|
|
|
|
items := make([]transport.LeaderboardEntry, 0, len(res.Items))
|
|
for _, item := range res.Items {
|
|
items = append(items, leaderboardEntryToWire(item))
|
|
}
|
|
|
|
var curDriver *transport.LeaderboardEntry
|
|
if res.CurrentDriver != nil {
|
|
wire := leaderboardEntryToWire(*res.CurrentDriver)
|
|
curDriver = &wire
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, transport.LeaderboardResponse{
|
|
Items: items,
|
|
Total: res.Total,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
CurrentDriver: curDriver,
|
|
})
|
|
}
|
|
}
|
|
|
|
func leaderboardEntryToWire(e races.LeaderboardEntry) transport.LeaderboardEntry {
|
|
var bestPos *int
|
|
if e.BestPos != nil {
|
|
pos := *e.BestPos
|
|
bestPos = &pos
|
|
}
|
|
var bestTime *int64
|
|
if e.BestTimeMs != nil {
|
|
val := *e.BestTimeMs
|
|
bestTime = &val
|
|
}
|
|
return transport.LeaderboardEntry{
|
|
DriverID: e.DriverID,
|
|
Nickname: e.Nickname,
|
|
Name: e.Name,
|
|
AvatarURL: e.AvatarURL,
|
|
ClanID: e.ClanID,
|
|
ClanTag: e.ClanTag,
|
|
Points: e.Points,
|
|
BestPos: bestPos,
|
|
BestTimeMs: bestTime,
|
|
Rank: e.Rank,
|
|
}
|
|
}
|