mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
Add gh workflow for release, new fields in podium and versioning
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
name: Release Go Binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Binary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
goos: [linux, darwin, windows]
|
||||||
|
goarch: [amd64, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: server/go.mod
|
||||||
|
|
||||||
|
- name: Build Binary
|
||||||
|
working-directory: server
|
||||||
|
run: |
|
||||||
|
mkdir -p bin
|
||||||
|
OUT_NAME="poc-server-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||||
|
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||||
|
OUT_NAME="${OUT_NAME}.exe"
|
||||||
|
fi
|
||||||
|
echo "Building for ${{ matrix.goos }}/${{ matrix.goarch }}..."
|
||||||
|
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -ldflags "-s -w -X main.serverVersion=${{ github.ref_name }}" -o bin/${OUT_NAME} ./cmd/poc-server
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: poc-server-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
|
path: server/bin/
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download Artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: release-binaries
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "Creating GitHub Release for tag ${{ github.ref_name }}..."
|
||||||
|
gh release create "${{ github.ref_name }}" \
|
||||||
|
--title "Release ${{ github.ref_name }}" \
|
||||||
|
--generate-notes \
|
||||||
|
release-binaries/*
|
||||||
@@ -62,7 +62,7 @@ import (
|
|||||||
"github.com/x0gp/server/internal/transport"
|
"github.com/x0gp/server/internal/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
const serverVersion = "0.1.0-poc"
|
var serverVersion = "0.1.0-poc"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
upgrader = websocket.Upgrader{
|
upgrader = websocket.Upgrader{
|
||||||
@@ -95,9 +95,15 @@ func main() {
|
|||||||
devMode = flag.Bool("dev", true, "enable development mode")
|
devMode = flag.Bool("dev", true, "enable development mode")
|
||||||
seedRaces = flag.Bool("seed-races", false, "seed mock race data (finished/live/plans) on startup")
|
seedRaces = flag.Bool("seed-races", false, "seed mock race data (finished/live/plans) on startup")
|
||||||
seedReset = flag.Bool("reset", false, "with --seed-races, wipe seed-managed data before inserting")
|
seedReset = flag.Bool("reset", false, "with --seed-races, wipe seed-managed data before inserting")
|
||||||
|
version = flag.Bool("version", false, "print version and exit")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *version {
|
||||||
|
fmt.Println(serverVersion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if *addr != "" {
|
if *addr != "" {
|
||||||
_ = os.Setenv("X0GP_HTTP_ADDR", *addr)
|
_ = os.Setenv("X0GP_HTTP_ADDR", *addr)
|
||||||
}
|
}
|
||||||
@@ -118,7 +124,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger := newLogger(cfg.LogLevel)
|
logger := newLogger(cfg.LogLevel)
|
||||||
logger.Info("starting x0gp poc-server",
|
logger.Info(fmt.Sprintf("starting x0gp poc-server version %s", serverVersion),
|
||||||
"version", serverVersion,
|
"version", serverVersion,
|
||||||
"addr", cfg.HTTPAddr,
|
"addr", cfg.HTTPAddr,
|
||||||
"tick_hz", cfg.TickRate,
|
"tick_hz", cfg.TickRate,
|
||||||
@@ -276,6 +282,7 @@ func main() {
|
|||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/health", healthHandler(hub, engine))
|
mux.HandleFunc("/health", healthHandler(hub, engine))
|
||||||
|
mux.HandleFunc("/api/version", versionHandler())
|
||||||
mux.HandleFunc("/stats", statsHandler(hub, engine))
|
mux.HandleFunc("/stats", statsHandler(hub, engine))
|
||||||
mux.HandleFunc("/api/catalog", catalogHandler(cat))
|
mux.HandleFunc("/api/catalog", catalogHandler(cat))
|
||||||
mux.HandleFunc("/api/tracks", tracksRouter(cat))
|
mux.HandleFunc("/api/tracks", tracksRouter(cat))
|
||||||
@@ -366,6 +373,23 @@ func (h *Hub) BroadcastSnapshot(snap transport.RaceSnapshot) {
|
|||||||
|
|
||||||
// HTTP handlers ------------------------------------------------------------
|
// HTTP handlers ------------------------------------------------------------
|
||||||
|
|
||||||
|
// versionHandler godoc
|
||||||
|
// @Summary Get server version
|
||||||
|
// @Description Returns the current server version.
|
||||||
|
// @Tags system
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} transport.VersionResponse "Success"
|
||||||
|
// @Router /api/version [get]
|
||||||
|
func versionHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(w).Encode(transport.VersionResponse{
|
||||||
|
Version: serverVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// healthHandler godoc
|
// healthHandler godoc
|
||||||
// @Summary Liveness probe
|
// @Summary Liveness probe
|
||||||
// @Description Returns 200 OK with current engine phase, WS connection count and total snapshot drops. Intended for load-balancer health checks.
|
// @Description Returns 200 OK with current engine phase, WS connection count and total snapshot drops. Intended for load-balancer health checks.
|
||||||
|
|||||||
@@ -485,6 +485,9 @@ func lobbyToWireFinished(m lobby.RaceMeta, podium []races.PodiumEntry) transport
|
|||||||
DriverID: p.DriverID,
|
DriverID: p.DriverID,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
TotalTimeMs: p.TotalTimeMs,
|
TotalTimeMs: p.TotalTimeMs,
|
||||||
|
Nickname: p.Nickname,
|
||||||
|
AvatarUrl: p.AvatarUrl,
|
||||||
|
ClanID: p.ClanID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1750,6 +1750,26 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/version": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the current server version.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Get server version",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/transport.VersionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/video/control": {
|
"/api/video/control": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888).",
|
"description": "Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888).",
|
||||||
@@ -2552,6 +2572,13 @@ const docTemplate = `{
|
|||||||
"transport.RacePodiumEntry": {
|
"transport.RacePodiumEntry": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clan_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "clan-bob"
|
||||||
|
},
|
||||||
"driver_id": {
|
"driver_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "driver-alice"
|
"example": "driver-alice"
|
||||||
@@ -2560,6 +2587,10 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "driver-alice"
|
"example": "driver-alice"
|
||||||
},
|
},
|
||||||
|
"nickname": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "driver-alice"
|
||||||
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
@@ -2851,6 +2882,15 @@ const docTemplate = `{
|
|||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"transport.VersionResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0.1.0-poc"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -1748,6 +1748,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/version": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the current server version.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Get server version",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/transport.VersionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/video/control": {
|
"/api/video/control": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888).",
|
"description": "Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888).",
|
||||||
@@ -2550,6 +2570,13 @@
|
|||||||
"transport.RacePodiumEntry": {
|
"transport.RacePodiumEntry": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clan_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "clan-bob"
|
||||||
|
},
|
||||||
"driver_id": {
|
"driver_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "driver-alice"
|
"example": "driver-alice"
|
||||||
@@ -2558,6 +2585,10 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "driver-alice"
|
"example": "driver-alice"
|
||||||
},
|
},
|
||||||
|
"nickname": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "driver-alice"
|
||||||
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
@@ -2849,6 +2880,15 @@
|
|||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"transport.VersionResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0.1.0-poc"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,12 +478,20 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
transport.RacePodiumEntry:
|
transport.RacePodiumEntry:
|
||||||
properties:
|
properties:
|
||||||
|
avatar_url:
|
||||||
|
type: string
|
||||||
|
clan_id:
|
||||||
|
example: clan-bob
|
||||||
|
type: string
|
||||||
driver_id:
|
driver_id:
|
||||||
example: driver-alice
|
example: driver-alice
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
example: driver-alice
|
example: driver-alice
|
||||||
type: string
|
type: string
|
||||||
|
nickname:
|
||||||
|
example: driver-alice
|
||||||
|
type: string
|
||||||
position:
|
position:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
@@ -677,6 +685,12 @@ definitions:
|
|||||||
width_m:
|
width_m:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
transport.VersionResponse:
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
example: 0.1.0-poc
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
host: localhost:8080
|
host: localhost:8080
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
@@ -1894,6 +1908,19 @@ paths:
|
|||||||
summary: Get / update / delete a track by id
|
summary: Get / update / delete a track by id
|
||||||
tags:
|
tags:
|
||||||
- tracks
|
- tracks
|
||||||
|
/api/version:
|
||||||
|
get:
|
||||||
|
description: Returns the current server version.
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Success
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/transport.VersionResponse'
|
||||||
|
summary: Get server version
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
/api/video/control:
|
/api/video/control:
|
||||||
post:
|
post:
|
||||||
description: Sends a text command (e.g. start, stop, left, right) to the ESP32
|
description: Sends a text command (e.g. start, stop, left, right) to the ESP32
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ import (
|
|||||||
// Position (1..3). DriverID/Name are denormalised from the future
|
// Position (1..3). DriverID/Name are denormalised from the future
|
||||||
// drivers table; TotalTimeMs is the driver's total race time.
|
// drivers table; TotalTimeMs is the driver's total race time.
|
||||||
type PodiumEntry struct {
|
type PodiumEntry struct {
|
||||||
Position int `json:"position" example:"1"`
|
Position int `json:"position" example:"1"`
|
||||||
DriverID string `json:"driver_id" example:"driver-alice"`
|
DriverID string `json:"driver_id" example:"driver-alice"`
|
||||||
Name string `json:"name" example:"driver-alice"`
|
Name string `json:"name" example:"driver-alice"`
|
||||||
TotalTimeMs int64 `json:"total_time_ms" example:"123456"`
|
TotalTimeMs int64 `json:"total_time_ms" example:"123456"`
|
||||||
|
Nickname string `json:"nickname" example:"driver-alice"`
|
||||||
|
AvatarUrl string `json:"avatar_url"`
|
||||||
|
ClanID string `json:"clan_id" example:"clan-bob"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DriverResult is the per-driver outcome of a race. Stored in
|
// DriverResult is the per-driver outcome of a race. Stored in
|
||||||
@@ -31,9 +34,13 @@ type PodiumEntry struct {
|
|||||||
// fields are derived from this row set on read.
|
// fields are derived from this row set on read.
|
||||||
type DriverResult struct {
|
type DriverResult struct {
|
||||||
DriverID string
|
DriverID string
|
||||||
|
Name string
|
||||||
TotalTimeMs int64
|
TotalTimeMs int64
|
||||||
BestLapMs int64
|
BestLapMs int64
|
||||||
Position *int // nil for DNF
|
Position *int // nil for DNF
|
||||||
|
Nickname string `json:"nickname" example:"driver-alice"`
|
||||||
|
AvatarUrl string `json:"avatar_url"`
|
||||||
|
ClanID string `json:"clan_id" example:"clan-bob"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinishedRace is the on-disk shape of a completed race. Mirrors the
|
// FinishedRace is the on-disk shape of a completed race. Mirrors the
|
||||||
@@ -88,10 +95,10 @@ type RacePlan struct {
|
|||||||
|
|
||||||
// QueueEntry is a single (driver, race) subscription.
|
// QueueEntry is a single (driver, race) subscription.
|
||||||
type QueueEntry struct {
|
type QueueEntry struct {
|
||||||
DriverID string
|
DriverID string
|
||||||
RaceID string
|
RaceID string
|
||||||
PlanID string
|
PlanID string
|
||||||
EnqueuedMs int64
|
EnqueuedMs int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// PgStore is the Postgres-backed half of the races package.
|
// PgStore is the Postgres-backed half of the races package.
|
||||||
@@ -587,8 +594,8 @@ func (s *PgStore) scanFinished(ctx context.Context, rows pgx.Rows) ([]FinishedRa
|
|||||||
// and derives the Winner* and Podium fields.
|
// and derives the Winner* and Podium fields.
|
||||||
func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
||||||
rows, err := s.pool.Query(ctx, `
|
rows, err := s.pool.Query(ctx, `
|
||||||
SELECT driver_id, total_time_ms, best_lap_ms, position
|
SELECT rd.driver_id, rd.total_time_ms, rd.best_lap_ms, rd.position, d.name, d.nickname, d.avatar_url, d.clan_id
|
||||||
FROM race_drivers
|
FROM race_drivers rd left join drivers d on rd.driver_id = d.id
|
||||||
WHERE race_id = $1
|
WHERE race_id = $1
|
||||||
ORDER BY slot ASC, joined_ms ASC`, r.ID)
|
ORDER BY slot ASC, joined_ms ASC`, r.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -602,10 +609,28 @@ func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var dr DriverResult
|
var dr DriverResult
|
||||||
var pos *int
|
var pos *int
|
||||||
if err := rows.Scan(&dr.DriverID, &dr.TotalTimeMs, &dr.BestLapMs, &pos); err != nil {
|
var name, nick, avatar, clanID *string
|
||||||
|
if err := rows.Scan(
|
||||||
|
&dr.DriverID, &dr.TotalTimeMs, &dr.BestLapMs, &pos,
|
||||||
|
&name, &nick, &avatar, &clanID,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dr.Position = pos
|
dr.Position = pos
|
||||||
|
if name != nil {
|
||||||
|
dr.Name = *name
|
||||||
|
} else {
|
||||||
|
dr.Name = dr.DriverID
|
||||||
|
}
|
||||||
|
if nick != nil {
|
||||||
|
dr.Nickname = *nick
|
||||||
|
}
|
||||||
|
if avatar != nil {
|
||||||
|
dr.AvatarUrl = *avatar
|
||||||
|
}
|
||||||
|
if clanID != nil {
|
||||||
|
dr.ClanID = *clanID
|
||||||
|
}
|
||||||
r.Results = append(r.Results, dr)
|
r.Results = append(r.Results, dr)
|
||||||
r.DriverIDs = append(r.DriverIDs, dr.DriverID)
|
r.DriverIDs = append(r.DriverIDs, dr.DriverID)
|
||||||
}
|
}
|
||||||
@@ -621,7 +646,7 @@ func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
|||||||
if bestTotal == -1 || dr.TotalTimeMs < bestTotal {
|
if bestTotal == -1 || dr.TotalTimeMs < bestTotal {
|
||||||
bestTotal = dr.TotalTimeMs
|
bestTotal = dr.TotalTimeMs
|
||||||
r.WinnerDriverID = dr.DriverID
|
r.WinnerDriverID = dr.DriverID
|
||||||
r.WinnerName = dr.DriverID
|
r.WinnerName = dr.Name
|
||||||
}
|
}
|
||||||
if dr.BestLapMs > 0 && (r.BestLapMs == 0 || dr.BestLapMs < r.BestLapMs) {
|
if dr.BestLapMs > 0 && (r.BestLapMs == 0 || dr.BestLapMs < r.BestLapMs) {
|
||||||
r.BestLapMs = dr.BestLapMs
|
r.BestLapMs = dr.BestLapMs
|
||||||
@@ -629,16 +654,28 @@ func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
|||||||
}
|
}
|
||||||
// Derive podium: top-3 drivers by total_time_ms (> 0) ASC.
|
// Derive podium: top-3 drivers by total_time_ms (> 0) ASC.
|
||||||
type row struct {
|
type row struct {
|
||||||
id string
|
id string
|
||||||
t int64
|
t int64
|
||||||
best int64
|
best int64
|
||||||
|
name string
|
||||||
|
nick string
|
||||||
|
avatar string
|
||||||
|
clanID string
|
||||||
}
|
}
|
||||||
var sorted []row
|
var sorted []row
|
||||||
for _, dr := range r.Results {
|
for _, dr := range r.Results {
|
||||||
if dr.TotalTimeMs <= 0 {
|
if dr.TotalTimeMs <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sorted = append(sorted, row{dr.DriverID, dr.TotalTimeMs, dr.BestLapMs})
|
sorted = append(sorted, row{
|
||||||
|
id: dr.DriverID,
|
||||||
|
t: dr.TotalTimeMs,
|
||||||
|
best: dr.BestLapMs,
|
||||||
|
name: dr.Name,
|
||||||
|
nick: dr.Nickname,
|
||||||
|
avatar: dr.AvatarUrl,
|
||||||
|
clanID: dr.ClanID,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for i := 1; i < len(sorted); i++ {
|
for i := 1; i < len(sorted); i++ {
|
||||||
for j := i; j > 0 && sorted[j-1].t > sorted[j].t; j-- {
|
for j := i; j > 0 && sorted[j-1].t > sorted[j].t; j-- {
|
||||||
@@ -649,8 +686,11 @@ func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
|
|||||||
r.Podium = append(r.Podium, PodiumEntry{
|
r.Podium = append(r.Podium, PodiumEntry{
|
||||||
Position: i + 1,
|
Position: i + 1,
|
||||||
DriverID: sorted[i].id,
|
DriverID: sorted[i].id,
|
||||||
Name: sorted[i].id,
|
Name: sorted[i].name,
|
||||||
TotalTimeMs: sorted[i].t,
|
TotalTimeMs: sorted[i].t,
|
||||||
|
Nickname: sorted[i].nick,
|
||||||
|
AvatarUrl: sorted[i].avatar,
|
||||||
|
ClanID: sorted[i].clanID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -229,6 +229,14 @@ type RacePodiumEntry struct {
|
|||||||
DriverID string `json:"driver_id" example:"driver-alice"`
|
DriverID string `json:"driver_id" example:"driver-alice"`
|
||||||
Name string `json:"name" example:"driver-alice"`
|
Name string `json:"name" example:"driver-alice"`
|
||||||
TotalTimeMs int64 `json:"total_time_ms" example:"123456"`
|
TotalTimeMs int64 `json:"total_time_ms" example:"123456"`
|
||||||
|
Nickname string `json:"nickname" example:"driver-alice"`
|
||||||
|
AvatarUrl string `json:"avatar_url"`
|
||||||
|
ClanID string `json:"clan_id" example:"clan-bob"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionResponse is the body of the version API.
|
||||||
|
type VersionResponse struct {
|
||||||
|
Version string `json:"version" example:"0.1.0-poc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LobbyDriver is the wire form of lobby.DriverMeta.
|
// LobbyDriver is the wire form of lobby.DriverMeta.
|
||||||
|
|||||||
Reference in New Issue
Block a user