Add gh workflow for release, new fields in podium and versioning

This commit is contained in:
x0gp
2026-07-14 18:36:51 +04:00
parent 13495de5bd
commit 7a7913d2ee
8 changed files with 269 additions and 20 deletions
+58 -18
View File
@@ -18,10 +18,13 @@ import (
// Position (1..3). DriverID/Name are denormalised from the future
// drivers table; TotalTimeMs is the driver's total race time.
type PodiumEntry struct {
Position int `json:"position" example:"1"`
DriverID string `json:"driver_id" example:"driver-alice"`
Name string `json:"name" example:"driver-alice"`
TotalTimeMs int64 `json:"total_time_ms" example:"123456"`
Position int `json:"position" example:"1"`
DriverID string `json:"driver_id" example:"driver-alice"`
Name string `json:"name" example:"driver-alice"`
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
@@ -31,9 +34,13 @@ type PodiumEntry struct {
// fields are derived from this row set on read.
type DriverResult struct {
DriverID string
Name string
TotalTimeMs 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
@@ -88,10 +95,10 @@ type RacePlan struct {
// QueueEntry is a single (driver, race) subscription.
type QueueEntry struct {
DriverID string
RaceID string
PlanID string
EnqueuedMs int64
DriverID string
RaceID string
PlanID string
EnqueuedMs int64
}
// 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.
func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
rows, err := s.pool.Query(ctx, `
SELECT driver_id, total_time_ms, best_lap_ms, position
FROM race_drivers
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 rd left join drivers d on rd.driver_id = d.id
WHERE race_id = $1
ORDER BY slot ASC, joined_ms ASC`, r.ID)
if err != nil {
@@ -602,10 +609,28 @@ func (s *PgStore) hydrateFinished(ctx context.Context, r *FinishedRace) error {
for rows.Next() {
var dr DriverResult
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
}
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.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 {
bestTotal = dr.TotalTimeMs
r.WinnerDriverID = dr.DriverID
r.WinnerName = dr.DriverID
r.WinnerName = dr.Name
}
if dr.BestLapMs > 0 && (r.BestLapMs == 0 || dr.BestLapMs < r.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.
type row struct {
id string
t int64
best int64
id string
t int64
best int64
name string
nick string
avatar string
clanID string
}
var sorted []row
for _, dr := range r.Results {
if dr.TotalTimeMs <= 0 {
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 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{
Position: i + 1,
DriverID: sorted[i].id,
Name: sorted[i].id,
Name: sorted[i].name,
TotalTimeMs: sorted[i].t,
Nickname: sorted[i].nick,
AvatarUrl: sorted[i].avatar,
ClanID: sorted[i].clanID,
})
}
return nil
+8
View File
@@ -229,6 +229,14 @@ type RacePodiumEntry struct {
DriverID string `json:"driver_id" example:"driver-alice"`
Name string `json:"name" example:"driver-alice"`
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.