mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
feat(server): initial implementation
This commit captures the full server code accumulated across
several development sessions. It includes the following layers,
applied in roughly this order during development:
1. Base infrastructure
- cmd/poc-server HTTP+WebSocket server, /health, /stats
- internal/transport wire types (Envelope + all message types)
- internal/catalog, internal/realtime, internal/stats
- internal/storage/postgres with migrations 001-004
- .env, .env.example, docker-compose, Dockerfile, scripts/
2. Swagger documentation
- go get github.com/swaggo/http-swagger
- swag init produces docs/docs.go
- /swagger/index.html UI, /swagger/doc.json spec
- annotations on health/stats/ws and catalog handlers
3. Races API
- internal/races/{store,service,keyset,types}.go
- migration 005_races.sql: finished_races, race_plans, race_queue
- GET /api/races with keyset pagination, status filter
- GET /api/races/upcoming
- POST /api/races/queue/join, GET, DELETE
- POST/GET /api/races/plans, DELETE /{id}
- lobby.RaceMeta simplification (no host_id/host_name)
4. Races seeder
- internal/races/seed with deterministic generator
- --seed-races / --reset CLI flags in main.go
- 30 finished + 5 live + 5 plans + 4 queue entries
5. Drivers and clans
- internal/drivers, internal/clans (CRUD, validation)
- migration 008_drivers_clans.sql
- /api/clans and /api/drivers endpoints
- 3-letter uppercase nickname/tag validation
- lobby.DriverMeta: Nickname, AvatarURL, ClanID, ClanTag
6. Podium
- internal/transport.RacePodiumEntry
- lobby.SetDriverProfile for in-memory metadata sync
- migration 007_podium.sql (podium JSONB column)
- seeder populates top-3 per finished race
7. Live races persistence
- migration 009_live_persistence.sql: live_races,
live_race_drivers, lobby_drivers
- internal/races/live_store.go: LiveStore with write-side
mirror for lobby.Service mutations
- Service.collectLive and Upcoming read from Postgres
- main.go RestoreFromDB rehydrates lobby on startup
8. Unify live + finished into one races table
- migration 010_unify_races.sql: rename finished_races to
races, expand status CHECK, merge live rows
- PgStore now hosts both write paths; LiveStore is a
thin facade implementing lobby.Persistence
- seeder resetAll drops only finished/cancelled rows and
race_plans / race_queue / lobby_drivers
Each layer is consistent on its own; cross-layer changes are
visible in the file history. A future refactor may split this
commit into the per-stage boundaries listed above.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
// Package main contains a small helper that prints the default seeds
|
||||
// (5 F1 tracks + 5 F1 cars) as JSON to stdout. It is run as:
|
||||
//
|
||||
// go run ./scripts/genseed-json > seeds.json
|
||||
//
|
||||
// which is then fed into scripts/genseed to regenerate the SQL.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/x0gp/server/internal/catalog/seeddefs"
|
||||
)
|
||||
|
||||
// Mirrors the seeddefs package but in the JSON shape that
|
||||
// scripts/genseed expects.
|
||||
type waypoint struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
HeadingRad float64 `json:"heading_rad"`
|
||||
SpeedMs float64 `json:"speed_ms"`
|
||||
Curvature float64 `json:"curvature"`
|
||||
}
|
||||
|
||||
type bounds struct {
|
||||
MinX float64 `json:"min_x"`
|
||||
MinY float64 `json:"min_y"`
|
||||
MaxX float64 `json:"max_x"`
|
||||
MaxY float64 `json:"max_y"`
|
||||
LengthM float64 `json:"length_m"`
|
||||
WidthM float64 `json:"width_m"`
|
||||
}
|
||||
|
||||
type track struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AuthorID string `json:"author_id"`
|
||||
Visibility string `json:"visibility"`
|
||||
Scale int `json:"scale"`
|
||||
LengthM float64 `json:"length_m"`
|
||||
WidthM float64 `json:"width_m"`
|
||||
LaneWidthM float64 `json:"lane_width_m"`
|
||||
Bounds bounds `json:"bounds"`
|
||||
Surface string `json:"surface"`
|
||||
Tags []string `json:"tags"`
|
||||
Centerline []waypoint `json:"centerline"`
|
||||
BestLapMs int64 `json:"best_lap_ms"`
|
||||
BestLapHolder string `json:"best_lap_holder"`
|
||||
CreatedMs int64 `json:"created_ms"`
|
||||
UpdatedMs int64 `json:"updated_ms"`
|
||||
}
|
||||
|
||||
type chassis struct {
|
||||
Model string `json:"model"`
|
||||
Material string `json:"material"`
|
||||
Printed bool `json:"printed"`
|
||||
WheelbaseMm float64 `json:"wheelbase_mm"`
|
||||
TrackMm float64 `json:"track_mm"`
|
||||
}
|
||||
|
||||
type motor struct {
|
||||
Kind string `json:"kind"`
|
||||
Class string `json:"class"`
|
||||
KV int `json:"kv"`
|
||||
PowerW float64 `json:"power_w"`
|
||||
}
|
||||
|
||||
type battery struct {
|
||||
VoltageV float64 `json:"voltage_v"`
|
||||
CapacityMah int `json:"capacity_mah"`
|
||||
Cells int `json:"cells"`
|
||||
Chemistry string `json:"chemistry"`
|
||||
}
|
||||
|
||||
type car struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
Visibility string `json:"visibility"`
|
||||
Scale int `json:"scale"`
|
||||
LengthMm float64 `json:"length_mm"`
|
||||
WidthMm float64 `json:"width_mm"`
|
||||
HeightMm float64 `json:"height_mm"`
|
||||
WeightG float64 `json:"weight_g"`
|
||||
Chassis chassis `json:"chassis"`
|
||||
Motor motor `json:"motor"`
|
||||
Battery battery `json:"battery"`
|
||||
Drive string `json:"drive"`
|
||||
TopSpeedMs float64 `json:"top_speed_ms"`
|
||||
ColorHex string `json:"color_hex"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
Active bool `json:"active"`
|
||||
CreatedMs int64 `json:"created_ms"`
|
||||
UpdatedMs int64 `json:"updated_ms"`
|
||||
}
|
||||
|
||||
func toWps(in []seeddefs.Waypoint) []waypoint {
|
||||
out := make([]waypoint, len(in))
|
||||
for i, w := range in {
|
||||
out[i] = waypoint{X: w.X, Y: w.Y, HeadingRad: w.HeadingRad, SpeedMs: w.SpeedMs, Curvature: w.Curvature}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toTrack(t seeddefs.Track) track {
|
||||
return track{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
AuthorID: t.AuthorID,
|
||||
Visibility: t.Visibility,
|
||||
Scale: t.Scale,
|
||||
LengthM: t.LengthM,
|
||||
WidthM: t.WidthM,
|
||||
LaneWidthM: t.LaneWidthM,
|
||||
Bounds: bounds{
|
||||
MinX: t.Bounds.MinX, MinY: t.Bounds.MinY,
|
||||
MaxX: t.Bounds.MaxX, MaxY: t.Bounds.MaxY,
|
||||
LengthM: t.Bounds.LengthM, WidthM: t.Bounds.WidthM,
|
||||
},
|
||||
Surface: t.Surface,
|
||||
Tags: t.Tags,
|
||||
Centerline: toWps(t.Centerline),
|
||||
BestLapHolder: t.BestLapHolder,
|
||||
CreatedMs: 1700000000000,
|
||||
UpdatedMs: 1700000000000,
|
||||
}
|
||||
}
|
||||
|
||||
func toCar(c seeddefs.Car) car {
|
||||
return car{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
OwnerID: c.OwnerID,
|
||||
Visibility: c.Visibility,
|
||||
Scale: c.Scale,
|
||||
LengthMm: c.LengthMm,
|
||||
WidthMm: c.WidthMm,
|
||||
HeightMm: c.HeightMm,
|
||||
WeightG: c.WeightG,
|
||||
Chassis: chassis{
|
||||
Model: c.Chassis.Model, Material: c.Chassis.Material,
|
||||
Printed: c.Chassis.Printed, WheelbaseMm: c.Chassis.WheelbaseMm,
|
||||
TrackMm: c.Chassis.TrackMm,
|
||||
},
|
||||
Motor: motor{
|
||||
Kind: c.Motor.Kind, Class: c.Motor.Class,
|
||||
KV: c.Motor.KV, PowerW: c.Motor.PowerW,
|
||||
},
|
||||
Battery: battery{
|
||||
VoltageV: c.Battery.VoltageV, CapacityMah: c.Battery.CapacityMah,
|
||||
Cells: c.Battery.Cells, Chemistry: c.Battery.Chemistry,
|
||||
},
|
||||
Drive: c.Drive,
|
||||
TopSpeedMs: c.TopSpeedMs,
|
||||
ColorHex: c.ColorHex,
|
||||
Active: c.Active,
|
||||
CreatedMs: 1700000000000,
|
||||
UpdatedMs: 1700000000000,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
srcTracks := seeddefs.DefaultTracks()
|
||||
srcCars := seeddefs.DefaultCars()
|
||||
out := struct {
|
||||
Tracks []track `json:"tracks"`
|
||||
Cars []car `json:"cars"`
|
||||
}{
|
||||
Tracks: make([]track, len(srcTracks)),
|
||||
Cars: make([]car, len(srcCars)),
|
||||
}
|
||||
for i, t := range srcTracks {
|
||||
out.Tracks[i] = toTrack(t)
|
||||
}
|
||||
for i, c := range srcCars {
|
||||
out.Cars[i] = toCar(c)
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(out)
|
||||
}
|
||||
Reference in New Issue
Block a user