mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-17 04:57:55 +00:00
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.
116 lines
4.6 KiB
SQL
116 lines
4.6 KiB
SQL
-- 001_init.sql — initial schema for x0gp catalog.
|
|
--
|
|
-- Tables:
|
|
-- tracks static track definitions (oval, figure-eight, etc.)
|
|
-- track_waypoints racing line samples (1-to-many with tracks)
|
|
-- track_tags many-to-many tags for tracks
|
|
-- cars car definitions (chassis, motor, battery, etc.)
|
|
--
|
|
-- TimescaleDB extension is enabled opportunistically — not required for the
|
|
-- catalog itself; the telemetry hypertable will be added in a later phase
|
|
-- (S2). We enable it here so that the extension is provisioned alongside
|
|
-- the base schema.
|
|
|
|
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Tracks
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
CREATE TABLE IF NOT EXISTS tracks (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
author_id TEXT NOT NULL DEFAULT 'system',
|
|
visibility TEXT NOT NULL DEFAULT 'system'
|
|
CHECK (visibility IN ('system', 'public', 'private')),
|
|
scale INT NOT NULL DEFAULT 27,
|
|
length_m DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
width_m DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
lane_width_m DOUBLE PRECISION NOT NULL DEFAULT 0.20,
|
|
surface TEXT NOT NULL DEFAULT 'carpet'
|
|
CHECK (surface IN ('carpet', 'tile', 'wood', 'paper', 'mixed')),
|
|
best_lap_ms BIGINT NOT NULL DEFAULT 0,
|
|
best_lap_holder TEXT NOT NULL DEFAULT '',
|
|
bounds_min_x DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
bounds_min_y DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
bounds_max_x DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
bounds_max_y DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
created_ms BIGINT NOT NULL,
|
|
updated_ms BIGINT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS tracks_visibility_idx ON tracks (visibility);
|
|
CREATE INDEX IF NOT EXISTS tracks_author_idx ON tracks (author_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS track_waypoints (
|
|
track_id TEXT NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
|
seq INT NOT NULL,
|
|
x DOUBLE PRECISION NOT NULL,
|
|
y DOUBLE PRECISION NOT NULL,
|
|
heading_rad DOUBLE PRECISION NOT NULL,
|
|
speed_ms DOUBLE PRECISION NOT NULL,
|
|
curvature DOUBLE PRECISION NOT NULL,
|
|
PRIMARY KEY (track_id, seq)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS track_tags (
|
|
track_id TEXT NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
|
tag TEXT NOT NULL,
|
|
PRIMARY KEY (track_id, tag)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS track_tags_tag_idx ON track_tags (tag);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Cars
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
CREATE TABLE IF NOT EXISTS cars (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
owner_id TEXT NOT NULL DEFAULT 'system',
|
|
visibility TEXT NOT NULL DEFAULT 'system'
|
|
CHECK (visibility IN ('system', 'public', 'private')),
|
|
scale INT NOT NULL DEFAULT 27,
|
|
|
|
length_mm DOUBLE PRECISION NOT NULL,
|
|
width_mm DOUBLE PRECISION NOT NULL,
|
|
height_mm DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
weight_g DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
|
|
chassis_model TEXT NOT NULL DEFAULT '',
|
|
chassis_material TEXT NOT NULL DEFAULT '',
|
|
chassis_printed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
chassis_wheelbase_mm DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
chassis_track_mm DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
|
|
motor_kind TEXT NOT NULL DEFAULT 'brushed',
|
|
motor_class TEXT NOT NULL DEFAULT '',
|
|
motor_kv INT NOT NULL DEFAULT 0,
|
|
motor_power_w DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
|
|
battery_voltage_v DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
battery_capacity_mah INT NOT NULL DEFAULT 0,
|
|
battery_cells INT NOT NULL DEFAULT 0,
|
|
battery_chemistry TEXT NOT NULL DEFAULT 'li-po',
|
|
|
|
drive TEXT NOT NULL DEFAULT '2WD'
|
|
CHECK (drive IN ('2WD', '4WD')),
|
|
|
|
top_speed_ms DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
color_hex TEXT NOT NULL DEFAULT '#ff6b1a',
|
|
avatar_url TEXT NOT NULL DEFAULT '',
|
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
total_distance_m DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
total_races INT NOT NULL DEFAULT 0,
|
|
total_laps INT NOT NULL DEFAULT 0,
|
|
best_lap_ms BIGINT NOT NULL DEFAULT 0,
|
|
|
|
created_ms BIGINT NOT NULL,
|
|
updated_ms BIGINT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS cars_visibility_idx ON cars (visibility);
|
|
CREATE INDEX IF NOT EXISTS cars_owner_idx ON cars (owner_id); |