Add graphify, tests and supabase migrations & config

This commit is contained in:
2026-07-17 22:35:58 +04:00
parent 1069c93e6d
commit a5fd186320
15 changed files with 2399 additions and 6 deletions
@@ -0,0 +1,265 @@
-- 20260717000000_schema.sql — Consolidated schema for x0gp catalog, races, and drivers.
-- ---------------------------------------------------------------------------
-- Tracks & Waypoints
-- ---------------------------------------------------------------------------
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,
device_id INT
);
CREATE INDEX IF NOT EXISTS cars_visibility_idx ON cars (visibility);
CREATE INDEX IF NOT EXISTS cars_owner_idx ON cars (owner_id);
-- ---------------------------------------------------------------------------
-- Clans & Drivers
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS clans (
id TEXT PRIMARY KEY,
tag TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
avatar_url TEXT NOT NULL DEFAULT '',
created_ms BIGINT NOT NULL,
updated_ms BIGINT NOT NULL
);
CREATE INDEX IF NOT EXISTS clans_tag_idx ON clans (tag);
CREATE TABLE IF NOT EXISTS drivers (
id TEXT PRIMARY KEY,
nickname TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
avatar_url TEXT NOT NULL DEFAULT '',
clan_id TEXT REFERENCES clans(id) ON DELETE SET NULL,
created_ms BIGINT NOT NULL,
updated_ms BIGINT NOT NULL
);
CREATE INDEX IF NOT EXISTS drivers_nickname_idx ON drivers (nickname);
CREATE INDEX IF NOT EXISTS drivers_clan_idx ON drivers (clan_id);
-- ---------------------------------------------------------------------------
-- Races & Race Drivers
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS races (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
track_id TEXT NOT NULL DEFAULT '',
max_cars INT NOT NULL,
laps INT NOT NULL,
time_limit_s INT NOT NULL,
status TEXT NOT NULL DEFAULT 'lobby' CHECK (status IN ('lobby', 'countdown', 'racing', 'finished', 'cancelled')),
created_ms BIGINT NOT NULL,
started_ms BIGINT NOT NULL DEFAULT 0,
finished_ms BIGINT NOT NULL DEFAULT 0,
duration_ms BIGINT NOT NULL DEFAULT 0,
total_laps INT NOT NULL DEFAULT 0,
total_drivers INT NOT NULL DEFAULT 0,
updated_ms BIGINT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS races_status_finished_idx ON races (status, finished_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS races_started_idx ON races (status, started_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS races_track_idx ON races (track_id);
CREATE INDEX IF NOT EXISTS races_status_idx ON races (status);
CREATE TABLE IF NOT EXISTS race_drivers (
race_id TEXT NOT NULL REFERENCES races(id) ON DELETE CASCADE,
driver_id TEXT NOT NULL,
slot INT NOT NULL DEFAULT 0,
joined_ms BIGINT NOT NULL,
total_time_ms BIGINT NOT NULL DEFAULT 0,
best_lap_ms BIGINT NOT NULL DEFAULT 0,
position INT,
PRIMARY KEY (race_id, driver_id)
);
CREATE INDEX IF NOT EXISTS race_drivers_driver_idx ON race_drivers (driver_id);
CREATE INDEX IF NOT EXISTS race_drivers_podium_idx ON race_drivers (race_id, total_time_ms ASC, position ASC) WHERE total_time_ms > 0;
-- ---------------------------------------------------------------------------
-- Race Plans & Queue
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS race_plans (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
track_id TEXT NOT NULL DEFAULT 'default',
max_cars INT NOT NULL,
laps INT NOT NULL DEFAULT 0,
time_limit_s INT NOT NULL DEFAULT 0,
start_at_ms BIGINT NOT NULL,
interval_s INT NOT NULL DEFAULT 0, -- 0 = single-shot
count INT NOT NULL DEFAULT 0, -- 0 = repeat forever
enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_ms BIGINT NOT NULL,
updated_ms BIGINT NOT NULL,
next_fire_ms BIGINT NOT NULL, -- last materialised fire time
fires_done INT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS race_plans_start_idx ON race_plans (start_at_ms ASC, id ASC);
CREATE INDEX IF NOT EXISTS race_plans_enabled_idx ON race_plans (enabled, next_fire_ms);
CREATE TABLE IF NOT EXISTS race_queue (
driver_id TEXT NOT NULL,
race_id TEXT NOT NULL,
plan_id TEXT NOT NULL DEFAULT '',
enqueued_ms BIGINT NOT NULL,
PRIMARY KEY (driver_id, race_id)
);
CREATE INDEX IF NOT EXISTS race_queue_race_idx ON race_queue (race_id);
CREATE INDEX IF NOT EXISTS race_queue_driver_idx ON race_queue (driver_id, enqueued_ms);
-- ---------------------------------------------------------------------------
-- Lobby Drivers
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS lobby_drivers (
driver_id TEXT PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
nickname TEXT NOT NULL DEFAULT '',
avatar_url TEXT NOT NULL DEFAULT '',
clan_id TEXT NOT NULL DEFAULT '',
clan_tag TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'idle' CHECK (status IN ('idle', 'racing', 'offline')),
current_race_id TEXT NOT NULL DEFAULT '',
connected_ms BIGINT NOT NULL,
last_seen_ms BIGINT NOT NULL,
car_id TEXT
);
CREATE INDEX IF NOT EXISTS lobby_drivers_status_idx ON lobby_drivers (status);
-- ---------------------------------------------------------------------------
-- Track Calendar
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS track_calendar (
id SERIAL PRIMARY KEY,
track_id TEXT NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
start_at TIMESTAMP WITH TIME ZONE NOT NULL,
end_at TIMESTAMP WITH TIME ZONE NOT NULL,
CHECK (start_at < end_at)
);
CREATE INDEX IF NOT EXISTS track_calendar_dates_idx ON track_calendar (start_at, end_at);
-- ---------------------------------------------------------------------------
-- Go Migration Metadata (Bridges Go server startup migrations and Supabase)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS schema_migrations (
version TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO schema_migrations (version) VALUES
('001_init.sql'),
('002_seed.sql'),
('003_f1_seed.sql'),
('004_monaco_rescale.sql'),
('005_races.sql'),
('006_drop_host.sql'),
('007_podium.sql'),
('008_drivers_clans.sql'),
('009_live_persistence.sql'),
('010_unify_races.sql'),
('011_drop_driver_ids.sql'),
('012_normalize_race_results.sql'),
('013_driver_device.sql'),
('014_track_calendar.sql'),
('015_driver_car_id.sql')
ON CONFLICT (version) DO NOTHING;