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
+2
View File
@@ -11,3 +11,5 @@ server/poc-server.exe
.grepai/
*.blend1
*.blend2
node_modules/
+11
View File
@@ -43,6 +43,17 @@
- Go 1.26, бинарь `cmd/poc-server`. Сборка: `go build ./...` или `go build -o poc-server.exe ./cmd/poc-server`.
- HTTP+WebSocket, роутер `http.NewServeMux`. Запуск требует `DATABASE_URL` (см. `.env`).
- **База данных (Supabase)**:
- Используется self-hosted Supabase на порту `3402` (хост: `80.234.93.161:3402`).
- Расширение `timescaledb` отключено (закомментировано в `001_init.sql`), так как Supabase не поставляет его из коробки.
- Нативная папка `supabase/` содержит `config.toml`, объединенный дамп схемы `supabase/migrations/20260717000000_schema.sql` и каталог семян `supabase/seed.sql` (включает рескейл Монако).
- Объединенный дамп схемы автоматически заполняет таблицу `schema_migrations`, чтобы предотвратить ошибки повторного применения миграций Go-сервером на старте.
- **Абстракция данных (Store)**:
- Пакеты `clans` и `drivers` используют интерфейсы `Store` вместо прямого импорта `*PgStore`.
- Это позволяет писать юнит-тесты сервисов без подключения к реальной БД (через `memoryStore`).
- **Слепок структуры (Graphify)**:
- Утилита `scripts/graphify/main.go` собирает описание всех экспортируемых пакетов, структур, интерфейсов и функций в компактный файл [server/CODEBASE.md](file:///d:/x0gp/server/CODEBASE.md).
- Рекомендуется запускать `go run ./scripts/graphify/main.go` после значимых изменений API для поддержания карты проекта в актуальном состоянии и экономии токенов при чтении агентом.
- **Swagger UI**: `GET /swagger/index.html`, спека `GET /swagger/doc.json` (14 paths, 29 definitions).
- Генерация спеки: `swag init -g cmd/poc-server/main.go -o docs --parseInternal` из `D:/x0gp/server`. CLI ставится через `go install github.com/swaggo/swag/cmd/swag@v1.16.4`.
- Аннотации swag живут над хендлерами в `cmd/poc-server/main.go`, `catalog_handlers.go`, `races_handlers.go`. При добавлении нового REST-эндпоинта — добавить `@Router`/`@Param`/`@Success` и перегенерить `docs/`.
+736
View File
@@ -0,0 +1,736 @@
# x0gp Go Codebase Map (Generated)
This file contains a compiled map of all packages, structures, interfaces, and public functions in the `server` directory. It is optimized for token-efficiency so AI agents and humans can understand the codebase layout without reading all files.
## Package `config` (`internal/config`)
Package config loads runtime configuration from environment variables.
On startup Load() reads a .env file (if present) from the current
working directory and merges its values into the process environment.
Existing process env vars take precedence over .env so deployments
can override .env without modifying the file.
### Structs
- **struct Config** { HTTPAddr, TickRate, TickInterval, LogLevel, MaxClients, SnapshotRate, DevMode, DatabaseURL, UDPVideoAddr, ESPCmdPort }
### Functions
- func Load() (*Config, error)
---
## Package `postgres` (`internal/storage/postgres`)
Package postgres owns the persistence layer of the catalog.
It exposes a *pgxpool.Pool plus a small set of repository helpers used by
internal/catalog. Migrations are applied at startup from the SQL files in
the migrations/ subdirectory.
### Structs
- **struct PgStore**
- **struct Config** { URL, Host, Port, User, Password, Database, SSLMode, MaxConns, MinConns, ConnectTimeout }
### Functions
- func NewPgStore(*pgxpool.Pool) *PgStore
- func Open(context.Context, Config) (*pgxpool.Pool, error)
- func Migrate(context.Context, *pgxpool.Pool) error
### Methods
- func (*PgStore) Load(context.Context) ([]catalog.TrackMeta, []catalog.CarMeta, error)
- func (*PgStore) UpsertTrack(context.Context, catalog.TrackMeta) error
- func (*PgStore) DeleteTrack(context.Context, string) error
- func (*PgStore) UpsertCar(context.Context, catalog.CarMeta) error
- func (*PgStore) DeleteCar(context.Context, string) error
- func (*PgStore) UpdateCarStats(context.Context, string, func) error
- func (*PgStore) GetTrackCalendar(context.Context) ([]catalog.TrackCalendarEntry, error)
- func (*PgStore) CreateTrackCalendar(context.Context, catalog.TrackCalendarEntry) (catalog.TrackCalendarEntry, error)
- func (*PgStore) DeleteTrackCalendar(context.Context, int) error
- func (Config) DSN() (string, error)
---
## Package `seed` (`internal/races/seed`)
### Structs
- **struct DefaultCounts** { Finished, Live, Plans }
- **struct Options** { Counts, Seed, Reset, Now }
- **struct Summary** { FinishedInserted, LiveCreated, PlansInserted, QueueInserted, Duration }
- **struct Runner**
### Functions
- func NewRunner(*races.PgStore, *lobby.Service, *races.LiveStore, *clans.PgStore, *drivers.PgStore) *Runner
### Methods
- func (*Runner) Run(context.Context, Options) (Summary, error)
---
## Package `realtime` (`internal/realtime`)
Package realtime manages WebSocket connections: registration, fan-out of
race snapshots, and per-client send queues with backpressure handling.
### Structs
- **struct Client** { ID, Send, SessionID, Done, DriverID, LastUDPCommand }
- **struct Hub**
- **struct Stats** { Connections, DropsTotal }
### Functions
- func NewHub() *Hub
- func MustEnvelope(transport.MessageType, any) *transport.Envelope
### Methods
- func (*Client) Close()
- func (*Hub) Stats() Stats
- func (*Hub) Register(*Client)
- func (*Hub) Unregister(*Client)
- func (*Hub) Run(context.Context)
- func (*Hub) Stop()
- func (*Hub) Publish(*transport.Envelope)
---
## Package `catalog` (`internal/catalog`)
Package catalog owns the static configuration entities: tracks and
cars. Both are first-class resources that drivers browse, pick, and
(in later phases) author.
PoC: in-memory cache backed by a pluggable Store. The runtime uses
postgres.PgStore so tracks / cars survive restarts. The cache is the
source of truth for read paths; the Store is the source of truth for
durability. Mutations go through the Store, then refresh the cache.
Design constraints come from the user's apartment layout:
- largest room: 4.46 m × 3.19 m (446 × 319 cm)
- car scale: 1/27 (Compact)
- printer: Bambu A1 (220×220×250 mm build volume)
Tracks are sized to fit a 4.5 × 3.2 m bounding box with a safety
margin, which keeps them physically realizable in the apartment.
Cars are sized to a 1/27 touring envelope (~80 × 35 × 25 mm).
### Interfaces
- **interface Store** { Load, UpsertTrack, DeleteTrack, UpsertCar, DeleteCar, UpdateCarStats, GetTrackCalendar, CreateTrackCalendar, DeleteTrackCalendar }
### Structs
- **struct CreateTrackOptions** { ID, Name, Description, AuthorID, Visibility, Scale, LengthM, WidthM, LaneWidthM, Surface, Tags, Centerline }
- **struct UpdateTrackOptions** { Name, Description, Visibility, LengthM, WidthM, LaneWidthM, Surface, Tags, Centerline }
- **struct CreateCarOptions** { ID, Name, OwnerID, Visibility, Scale, LengthMm, WidthMm, HeightMm, WeightG, Chassis, Motor, Battery, Drive, TopSpeedMs, ColorHex, AvatarURL, Active, DeviceID }
- **struct UpdateCarOptions** { Name, Visibility, LengthMm, WidthMm, HeightMm, WeightG, TopSpeedMs, ColorHex, Active, Chassis, Motor, Battery, Drive, DeviceID }
- **struct Waypoint** { X, Y, HeadingRad, SpeedMs, Curvature }
- **struct Bounds** { MinX, MinY, MaxX, MaxY, LengthM, WidthM }
- **struct TrackMeta** { ID, Name, Description, AuthorID, Visibility, Scale, LengthM, WidthM, LaneWidthM, Bounds, Surface, Tags, Centerline, BestLapMs, BestLapHolder, CreatedMs, UpdatedMs }
- **struct TrackCalendarEntry** { ID, TrackID, StartAt, EndAt }
- **struct MotorSpec** { Kind, Class, KV, PowerW }
- **struct BatterySpec** { VoltageV, CapacityMah, Cells, Chemistry }
- **struct ChassisSpec** { Model, Material, Printed, WheelbaseMm, TrackMm }
- **struct CarMeta** { ID, Name, OwnerID, Visibility, Scale, LengthMm, WidthMm, HeightMm, WeightG, Chassis, Motor, Battery, Drive, DeviceID, TopSpeedMs, ColorHex, AvatarURL, Active, TotalDistanceM, TotalRaces, TotalLaps, BestLapMs, CreatedMs, UpdatedMs }
- **struct Snapshot** { GeneratedMs, Version, Tracks, Cars }
- **struct Stats** { TracksTotal, TracksSystem, TracksPublic, TracksPrivate, CarsTotal, CarsSystem, CarsPublic, CarsPrivate, CarsActive }
- **struct Event** { Kind, TrackID, CarID, Snapshot }
- **struct Service**
### Functions
- func NewService(context.Context, Store) (*Service, error)
### Methods
- func (CreateTrackOptions) Validate() error
- func (*Service) CreateTrack(context.Context, CreateTrackOptions) (TrackMeta, error)
- func (*Service) UpdateTrack(context.Context, string, UpdateTrackOptions) (TrackMeta, error)
- func (*Service) DeleteTrack(context.Context, string) error
- func (CreateCarOptions) Validate() error
- func (*Service) CreateCar(context.Context, CreateCarOptions) (CarMeta, error)
- func (*Service) UpdateCar(context.Context, string, UpdateCarOptions) (CarMeta, error)
- func (*Service) DeleteCar(context.Context, string) error
- func (*Service) SetCarStats(context.Context, string, func) error
- func (*Service) ListTracksByVisibility(Visibility) []TrackMeta
- func (*Service) ListCarsByVisibility(Visibility) []CarMeta
- func (*Service) ListCarsByOwner(string) []CarMeta
- func (*Service) GetTrackCalendar(context.Context) ([]TrackCalendarEntry, error)
- func (*Service) CreateTrackCalendar(context.Context, string, time.Time, time.Time) (TrackCalendarEntry, error)
- func (*Service) DeleteTrackCalendar(context.Context, int) error
- func (*memoryStore) Load(context.Context) ([]TrackMeta, []CarMeta, error)
- func (*memoryStore) UpsertTrack(context.Context, TrackMeta) error
- func (*memoryStore) DeleteTrack(context.Context, string) error
- func (*memoryStore) UpsertCar(context.Context, CarMeta) error
- func (*memoryStore) DeleteCar(context.Context, string) error
- func (*memoryStore) UpdateCarStats(context.Context, string, func) error
- func (*memoryStore) GetTrackCalendar(context.Context) ([]TrackCalendarEntry, error)
- func (*memoryStore) CreateTrackCalendar(context.Context, TrackCalendarEntry) (TrackCalendarEntry, error)
- func (*memoryStore) DeleteTrackCalendar(context.Context, int) error
- func (*Service) Subscribe() (?, func)
- func (*Service) Stop()
- func (*Service) Done() ?
- func (*Service) Snapshot() Snapshot
- func (*Service) Stats() Stats
- func (*Service) ListTracks() []TrackMeta
- func (*Service) ListCars() []CarMeta
- func (*Service) GetTrack(string) (TrackMeta, bool)
- func (*Service) GetCar(string) (CarMeta, bool)
---
## Package `seeddefs` (`internal/catalog/seeddefs`)
Package seeddefs holds the canonical seed definitions for tracks and
cars. It lives in its own package (no postgres dependencies) so both
the catalog tests and the scripts/genseed SQL generator can consume
the same source-of-truth data.
### Structs
- **struct Waypoint** { X, Y, HeadingRad, SpeedMs, Curvature }
- **struct Bounds** { MinX, MinY, MaxX, MaxY, LengthM, WidthM }
- **struct Track** { ID, Name, Description, AuthorID, Visibility, Scale, LengthM, WidthM, LaneWidthM, Surface, Tags, Centerline, Bounds, BestLapMs, BestLapHolder }
- **struct Chassis** { Model, Material, Printed, WheelbaseMm, TrackMm }
- **struct Motor** { Kind, Class, KV, PowerW }
- **struct Battery** { VoltageV, CapacityMah, Cells, Chemistry }
- **struct Car** { ID, Name, OwnerID, Visibility, Scale, LengthMm, WidthMm, HeightMm, WeightG, Chassis, Motor, Battery, Drive, TopSpeedMs, ColorHex, Active, TotalDistanceM, TotalRaces, TotalLaps, BestLapMs }
### Functions
- func DefaultTracks() []Track
- func DefaultCars() []Car
- func Monaco() Track
- func BarcelonaCatalunya() Track
- func AustriaRedBullRing() Track
- func GreatBritainSilverstone() Track
- func BelgiumSpa() Track
- func RedBullRB20() Car
- func FerrariSF24() Car
- func McLarenMCL38() Car
- func MercedesW15() Car
- func AstonMartinAMR24() Car
- func ComputeBounds([]Waypoint) Bounds
---
## Package `clans` (`internal/clans`)
Package clans owns persistence and business logic for clans.
A clan has a unique 3-letter tag (A-Z, uppercase, exactly 3 chars),
a name, and an optional avatar URL. Drivers reference a clan via a
nullable FK (see internal/drivers).
### Interfaces
- **interface Store** { Exec, Create, Get, GetByTag, List, Delete, Update, Count }
### Structs
- **struct Service**
- **struct CreateInput** { ID, Tag, Name, AvatarURL }
- **struct UpdateInput** { Name, AvatarURL }
- **struct Clan** { ID, Tag, Name, AvatarURL, CreatedMs, UpdatedMs }
- **struct PgStore**
### Functions
- func NewService(Store) *Service
- func IsValidTag(string) bool
- func NewPgStore(*pgxpool.Pool) *PgStore
### Methods
- func (*Service) Create(context.Context, CreateInput) (Clan, error)
- func (*Service) Get(context.Context, string) (Clan, error)
- func (*Service) GetByTag(context.Context, string) (Clan, error)
- func (*Service) List(context.Context, int, int) ([]Clan, error)
- func (*Service) Update(context.Context, string, UpdateInput) (Clan, error)
- func (*Service) Delete(context.Context, string) error
- func (*PgStore) Exec(context.Context, string) (int64, error)
- func (*PgStore) Create(context.Context, Clan) error
- func (*PgStore) Get(context.Context, string) (Clan, error)
- func (*PgStore) GetByTag(context.Context, string) (Clan, error)
- func (*PgStore) List(context.Context, int, int) ([]Clan, error)
- func (*PgStore) Delete(context.Context, string) error
- func (*PgStore) Update(context.Context, Clan) error
- func (*PgStore) Count(context.Context) (int, error)
---
## Package `control` (`internal/control`)
Package control owns the authoritative race state.
For PoC, the physics model is intentionally simple (no tire slip, no
collision): each car integrates position from (throttle, brake, steering).
Realistic physics, anti-cheat checks, and CV-based ground truth will be
added in MVP / production phases.
### Structs
- **struct Car** { ID, DriverID, DriverName, DeviceID, X, Y, Heading, Speed, Lap, Sector, LastLapMs, BestLapMs, DNF, AppliedSteering, AppliedThrottle, AppliedBrake }
- **struct Engine**
### Functions
- func NewEngine(int) *Engine
### Methods
- func (RacePhase) String() string
- func (*Car) ApplyInput(transport.InputState, float64)
- func (*Car) Snapshot() transport.CarInfo
- func (*Engine) Snapshots() ?
- func (*Engine) Phase() RacePhase
- func (*Engine) SetPhase(RacePhase)
- func (*Engine) AddCar(string, string, int, *int) (*Car, error)
- func (*Engine) RemoveCar(string)
- func (*Engine) GetCarByDriver(string) *Car
- func (*Engine) Run(context.Context)
- func (*Engine) Stop()
---
## Package `drivers` (`internal/drivers`)
Package drivers owns persistence and business logic for drivers
(racers). Each driver has a unique 3-letter nickname (A-Z,
uppercase, exactly 3 chars), a name, an optional avatar URL and an
optional clan reference.
### Interfaces
- **interface Store** { Exec, Create, Get, GetByNickname, List, Delete, Update, Count }
### Structs
- **struct Service**
- **struct CreateInput** { ID, Nickname, Name, AvatarURL, ClanID }
- **struct UpdateInput** { Name, AvatarURL, ClanID }
- **struct Driver** { ID, Nickname, Name, AvatarURL, ClanID, CreatedMs, UpdatedMs }
- **struct PgStore**
### Functions
- func NewService(Store) *Service
- func IsValidNickname(string) bool
- func NewPgStore(*pgxpool.Pool) *PgStore
### Methods
- func (*Service) Create(context.Context, CreateInput) (Driver, error)
- func (*Service) Get(context.Context, string) (Driver, error)
- func (*Service) GetByNickname(context.Context, string) (Driver, error)
- func (*Service) List(context.Context, int, int, string) ([]Driver, error)
- func (*Service) Update(context.Context, string, UpdateInput) (Driver, error)
- func (*Service) Delete(context.Context, string) error
- func (*PgStore) Exec(context.Context, string) (int64, error)
- func (*PgStore) Create(context.Context, Driver) error
- func (*PgStore) Get(context.Context, string) (Driver, error)
- func (*PgStore) GetByNickname(context.Context, string) (Driver, error)
- func (*PgStore) List(context.Context, int, int, string) ([]Driver, error)
- func (*PgStore) Update(context.Context, Driver) error
- func (*PgStore) Delete(context.Context, string) error
- func (*PgStore) Count(context.Context) (int, error)
---
## Package `lobby` (`internal/lobby`)
Package lobby owns the "outside any race" state of the server: which
races are open, which drivers are connected but idle, who is the host
of each race.
The lobby is the single source of truth for matchmaking and for
rendering the "what races are available right now" list. It does NOT
own per-race physics — that's the Engine (internal/control).
All operations are thread-safe. Mutations broadcast a snapshot event
to subscribers (see Service.Subscribe).
### Interfaces
- **interface Persistence** { UpsertRace, DeleteRace, AddDriver, RemoveDriver, SetRaceStatus, UpsertDriver, DeleteDriver }
### Structs
- **struct CreateRaceOptions** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS }
- **struct RaceMeta** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, DriverIDs, Status, CreatedMs, StartedMs }
- **struct DriverMeta** { ID, Name, Nickname, AvatarURL, ClanID, ClanTag, Status, RaceID, ConnectedMs, LastSeenMs, Country, AvatarHue, CarID }
- **struct Snapshot** { GeneratedMs, Races, Drivers, Version }
- **struct Stats** { RacesTotal, RacesOpen, RacesActive, DriversIdle, DriversTotal }
- **struct Service**
### Functions
- func NewService() *Service
### Methods
- func (*Service) CreateRace(CreateRaceOptions) (RaceMeta, error)
- func (*Service) AddRace(RaceMeta)
- func (*Service) AddDriver(string, string, string) (DriverMeta, error)
- func (*Service) RemoveDriver(string)
- func (*Service) Heartbeat(string)
- func (*Service) SetDriverProfile(string, string, string, string, string)
- func (*Service) AddDriverToRace(string, string) error
- func (*Service) RemoveDriverFromRace(string)
- func (*Service) SetRaceStatus(string, RaceStatus) error
- func (*Service) DeleteRace(string) error
- func (*Service) QuickPlay(string, int) (RaceMeta, error)
- func (*Service) GetRace(string) (RaceMeta, error)
- func (*Service) GetDriver(string) (DriverMeta, error)
- func (*Service) SelectCar(string, *string) error
- func (*Service) ListRaces() []RaceMeta
- func (*Service) ListDrivers() []DriverMeta
- func (*Service) SetPersistence(Persistence)
- func (*Service) SetRaceLifecycleHook(func, func)
- func (*Service) Subscribe() (?, func)
- func (*Service) Stop()
- func (*Service) Done() ?
- func (*Service) Snapshot() Snapshot
- func (*Service) Stats() Stats
---
## Package `stats` (`internal/stats`)
Package stats collects and exposes server and per-driver metrics.
PoC: in-memory only, atomic counters where possible, mutex around
driver and race records. The data lives for the lifetime of the
process. A future storage layer will swap this for Postgres/Redis.
Public surface is split into:
- lifecycle hooks (Connect/Disconnect/Race*/Lap*/Input*/RTT) —
called from cmd/poc-server and from internal/control and lobby
- Snapshot() — full server snapshot for /stats/detailed
- DriverProfile(id) — per-driver summary
- RecentLaps(n) — last N laps across all races
### Structs
- **struct ServerStats** { StartedMs, UptimeMs, CurrentClients, PeakClients, TotalConnections, TotalDisconnects, TotalRacesCreated, TotalRacesStarted, TotalRacesFinished, TotalLapsRecorded, TotalInputsRecv, TotalSnapshotsSent, SnapshotsDropped, AvgRTTMs, RecentRTTs }
- **struct DriverProfile** { ID, Name, TotalRacesStarted, TotalRacesFinished, TotalLaps, BestLapMs, LastLapMs, TotalDistanceM, TotalPlaytimeMs, Wins, Podiums, LastSeenMs }
- **struct LapRecord** { RaceID, DriverID, LapMs, Sector1Ms, Sector2Ms, Sector3Ms, Position, TSMs }
- **struct RaceResult** { RaceID, DriverID, FinishedAtMs, Position, TotalLaps, BestLapMs, TotalTimeMs, DNF }
- **struct Collector**
### Functions
- func NewCollector() *Collector
### Methods
- func (*Collector) Start()
- func (*Collector) OnConnect(string, string)
- func (*Collector) OnDisconnect(string, int64)
- func (*Collector) OnRaceCreated()
- func (*Collector) OnRaceStarted([]string)
- func (*Collector) OnRaceFinished(RaceResult)
- func (*Collector) OnLapRecorded(LapRecord)
- func (*Collector) OnInputProcessed()
- func (*Collector) OnSnapshotSent(uint64, uint64)
- func (*Collector) OnRTT(int64)
- func (*Collector) OnDistance(string, float64)
- func (*Collector) Server() ServerStats
- func (*Collector) Driver(string, string) DriverProfile
- func (*Collector) Drivers() []DriverProfile
- func (*Collector) RecentLaps(int) []LapRecord
- func (*Collector) RecentResults(int) []RaceResult
---
## Package `races` (`internal/races`)
Package races owns the cross-cutting "race list" surface: keyset-paginated
listing of live and finished races, upcoming (next-N) selection, the
driver-to-race queue and the recurring race-plan scheduler.
The package composes two storage backends:
- lobby.Service (in-memory): authoritative source for active races
(status = lobby | countdown | racing). Used directly for the live
page of /api/races, for the upcoming view and for the scheduler
materialisation.
- Postgres (pgxpool): authoritative for finished races (snapshotted
on SetRaceStatus(finished)), for race plans and for the queue.
Finished races are read with keyset pagination on
(finished_ms DESC, id DESC).
### Structs
- **struct Cursor** { Ms, ID }
- **struct LeaderboardEntry** { DriverID, Name, Nickname, AvatarURL, ClanID, ClanTag, Points, BestPos, BestTimeMs, Rank }
- **struct LeaderboardResult** { Items, Total, CurrentDriver }
- **struct TrackCalendarInfo** { ID, TrackID, TrackName, StartAt, EndAt }
- **struct TrackCalendarLeaderboard** { TrackID, TrackName, StartAt, EndAt, Status, Podium, CurrentDriver }
- **struct LiveStore**
- **struct Service**
- **struct ListFilter** { Statuses, TrackID, Cursor, Limit }
- **struct RaceItem** { Source, Meta, Podium }
- **struct ListResult** { Items, NextCursor, TotalCount }
- **struct UpcomingItem** { Meta, QueueLen, PlanID, StartAtMs }
- **struct JoinUpcomingResult** { Joined, Skipped }
- **struct SnapshotFinished** { Meta, FinishedMs, TotalLaps, WinnerDriverID, WinnerName, BestLapMs, Results }
- **struct CreatePlanInput** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, StartAtMs, IntervalS, Count, Enabled }
- **struct Scheduler**
- **struct PodiumEntry** { Position, DriverID, Name, TotalTimeMs, Nickname, AvatarUrl, ClanID }
- **struct DriverResult** { DriverID, Name, TotalTimeMs, BestLapMs, Position, Nickname, AvatarUrl, ClanID }
- **struct FinishedRace** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, DriverIDs, Status, CreatedMs, StartedMs, FinishedMs, DurationMs, TotalLaps, TotalDrivers, Results, WinnerDriverID, WinnerName, BestLapMs, Podium }
- **struct RacePlan** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, StartAtMs, IntervalS, Count, Enabled, CreatedMs, UpdatedMs, NextFireMs, FiresDone }
- **struct QueueEntry** { DriverID, RaceID, PlanID, EnqueuedMs }
- **struct PgStore**
### Functions
- func DecodeCursor(string) (Cursor, error)
- func NewLiveStore(*PgStore) *LiveStore
- func NewService(*PgStore, *lobby.Service) *Service
- func NewScheduler(*PgStore, *lobby.Service, time.Duration) *Scheduler
- func NewPgStore(*pgxpool.Pool) *PgStore
- func ParseStatusFilter(string) ([]StatusFilter, error)
### Methods
- func (Cursor) String() string
- func (Cursor) IsZero() bool
- func (Cursor) Encode() string
- func (*PgStore) GetTrackLeaderboard(context.Context, string, int, int, int) ([]LeaderboardEntry, int, error)
- func (*PgStore) GetTrackLeaderboardDriver(context.Context, string, int, string) (*LeaderboardEntry, error)
- func (*PgStore) GetOverallLeaderboard(context.Context, int, int, int) ([]LeaderboardEntry, int, error)
- func (*PgStore) GetOverallLeaderboardDriver(context.Context, int, string) (*LeaderboardEntry, error)
- func (*PgStore) GetDriverLeaderboardProfile(context.Context, string) (*LeaderboardEntry, error)
- func (*PgStore) GetCalendarLeaderboardTracks(context.Context) ([]TrackCalendarInfo, error)
- func (*LiveStore) UpsertRace(context.Context, lobby.RaceMeta) error
- func (*LiveStore) DeleteRace(context.Context, string) error
- func (*LiveStore) AddDriver(context.Context, string, string, int) error
- func (*LiveStore) RemoveDriver(context.Context, string, string) error
- func (*LiveStore) SetRaceStatus(context.Context, string, lobby.RaceStatus, int64) error
- func (*LiveStore) UpsertDriver(context.Context, lobby.DriverMeta) error
- func (*LiveStore) DeleteDriver(context.Context, string) error
- func (*LiveStore) ListAllRaces(context.Context) ([]lobby.RaceMeta, error)
- func (*LiveStore) ListAllDrivers(context.Context) ([]lobby.DriverMeta, error)
- func (*LiveStore) ListLivePaged(context.Context, []string, string, Cursor, int) ([]lobby.RaceMeta, error)
- func (*LiveStore) ListUpcoming(context.Context, int) ([]lobby.RaceMeta, error)
- func (*LiveStore) Exec(context.Context, string) (int64, error)
- func (*Service) SetLiveStore(*LiveStore)
- func (*Service) RestoreFromDB(context.Context) (int, int, error)
- func (*Service) SetMaxUpcoming(int)
- func (*Service) ListRaces(context.Context, ListFilter) (ListResult, error)
- func (*Service) Upcoming(context.Context, int) ([]UpcomingItem, error)
- func (*Service) JoinUpcoming(context.Context, string, int) (JoinUpcomingResult, error)
- func (*Service) LeaveQueue(context.Context, string, string) error
- func (*Service) ListQueue(context.Context, string) ([]QueueEntry, error)
- func (*Service) PersistFinished(context.Context, SnapshotFinished) error
- func (*Service) CreatePlan(context.Context, CreatePlanInput) (RacePlan, error)
- func (*Service) ListPlans(context.Context, Cursor, int) ([]RacePlan, error)
- func (*Service) CountPlans(context.Context) (int, error)
- func (*Service) DeletePlan(context.Context, string) error
- func (*Service) GetLeaderboard(context.Context, string, int, int, int, string) (*LeaderboardResult, error)
- func (*Service) GetCalendarLeaderboard(context.Context, int, string) ([]TrackCalendarLeaderboard, error)
- func (*Scheduler) Run(context.Context)
- func (*Scheduler) Stop()
- func (*Scheduler) Done() ?
- func (*PgStore) Exec(context.Context, string, ?) (int64, error)
- func (*PgStore) ListTrackIDs(context.Context) ([]string, error)
- func (*PgStore) UpsertLive(context.Context, lobby.RaceMeta) error
- func (*PgStore) DeleteLive(context.Context, string) error
- func (*PgStore) AddLiveDriver(context.Context, string, string, int) error
- func (*PgStore) RemoveLiveDriver(context.Context, string, string) error
- func (*PgStore) SetLiveStatus(context.Context, string, lobby.RaceStatus, int64) error
- func (*PgStore) ListLivePaged(context.Context, []string, string, Cursor, int) ([]lobby.RaceMeta, error)
- func (*PgStore) ListLiveUpcoming(context.Context, int) ([]lobby.RaceMeta, error)
- func (*PgStore) GetLive(context.Context, string) (lobby.RaceMeta, error)
- func (*PgStore) ListAllRaces(context.Context) ([]lobby.RaceMeta, error)
- func (*PgStore) UpsertLobbyDriver(context.Context, lobby.DriverMeta) error
- func (*PgStore) DeleteLobbyDriver(context.Context, string) error
- func (*PgStore) ListLobbyDrivers(context.Context) ([]lobby.DriverMeta, error)
- func (*PgStore) InsertFinished(context.Context, FinishedRace) error
- func (*PgStore) ListFinished(context.Context, string, Cursor, int) ([]FinishedRace, error)
- func (*PgStore) CountRaces(context.Context, []string, string) (int, error)
- func (*PgStore) CreatePlan(context.Context, RacePlan) error
- func (*PgStore) GetPlan(context.Context, string) (RacePlan, error)
- func (*PgStore) ListPlans(context.Context, Cursor, int) ([]RacePlan, error)
- func (*PgStore) CountPlans(context.Context) (int, error)
- func (*PgStore) DeletePlan(context.Context, string) error
- func (*PgStore) SetPlanEnabled(context.Context, string, bool) error
- func (*PgStore) AdvancePlan(context.Context, string, int64) error
- func (*PgStore) DuePlans(context.Context, int64, int) ([]RacePlan, error)
- func (*PgStore) Enqueue(context.Context, string, string, string) (QueueEntry, error)
- func (*PgStore) Dequeue(context.Context, string, string) error
- func (*PgStore) ListQueueByDriver(context.Context, string) ([]QueueEntry, error)
- func (*PgStore) ListQueueByRace(context.Context, string) ([]QueueEntry, error)
- func (*PgStore) CountQueueByRace(context.Context, string) (int, error)
- func (FinishedRace) ToLobbyMeta() lobby.RaceMeta
---
## Package `main` (`scripts/graphify`)
Package main is the graphify utility. It walks the codebase, parses Go AST,
and outputs a highly compact symbol summary to server/CODEBASE.md.
This is used for token savings and helping developers/AI agents quickly map out imports and structures.
### Structs
- **struct PackageSummary** { Name, Path, Doc, Structs, Interfaces, Functions, Methods }
---
## Package `main` (`.`)
Smoke test: ws client connects, exchanges a few messages, exits.
Run: go run ./smoke_test.go
---
## Package `transport` (`internal/transport`)
Package transport defines the wire format between clients and the server.
PoC: JSON for fast iteration. Specified by proto/client_server.proto for
the production binary protobuf format.
TODO PoC: replace JSON with protobuf-generated structs (see
../proto/client_server.proto).
### Structs
- **struct Envelope** { Type, Seq, TSMs, Payload }
- **struct ClientHello** { Version, Token, Locale, ClientID }
- **struct ServerHello** { SessionID, RaceTick, ServerVersion, Config }
- **struct JoinRace** { RaceID, CarSlot, CarName }
- **struct LeaveRace** { RaceID }
- **struct InputState** { Steering, Throttle, Brake, Gear, Buttons }
- **struct ChatMessage** { Text }
- **struct Ping** { ClientTSMs }
- **struct RaceSnapshot** { Tick, TSMs, Elapsed, Cars }
- **struct CarInfo** { ID, DriverName, X, Y, Heading, Speed, Lap, Sector, LastLapMs, BestLapMs, InputApplied, DNF }
- **struct InputAck** { Seq, AppliedAtMs, ServerTickMs }
- **struct Correction** { Tick, DeltaSteering, DeltaThrottle, Reason }
- **struct RaceEvent** { RaceID, Event, CarID, Sector, LapMs, TSMs }
- **struct Pong** { ClientTSMs, ServerTSMs }
- **struct ErrorMsg** { Code, Message }
- **struct LobbyRace** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, DriverIDs, Status, CreatedMs, StartedMs, Podium }
- **struct RacePodiumEntry** { Position, DriverID, Name, TotalTimeMs, Nickname, AvatarUrl, ClanID }
- **struct VersionResponse** { Version }
- **struct WebRTCConnectRequest** { SDP, Type, DriverID }
- **struct WebRTCConnectResponse** { SDP, Type }
- **struct LobbyDriver** { ID, Name, Status, RaceID, ConnectedMs, LastSeenMs, Country, AvatarHue }
- **struct LobbySnapshot** { GeneratedMs, Version, Races, Drivers }
- **struct LobbyCreate** { RaceID, Name, TrackID, MaxCars, Laps, TimeLimitS }
- **struct LobbyJoinRace** { RaceID, CarSlot, CarName }
- **struct LobbyLeaveRace** { RaceID }
- **struct LobbyKickDriver** { RaceID, DriverID }
- **struct LobbyDelete** { RaceID }
- **struct LobbyCreatedAck** { Ok, Race, Error, Driver }
- **struct LobbyJoinAck** { Ok, Race, Error }
- **struct TrackWaypoint** { X, Y, HeadingRad, SpeedMs, Curvature }
- **struct TrackBounds** { MinX, MinY, MaxX, MaxY, LengthM, WidthM }
- **struct TrackWire** { ID, Name, Description, AuthorID, Visibility, Scale, LengthM, WidthM, LaneWidthM, Bounds, Surface, Tags, Centerline, BestLapMs, BestLapHolder, CreatedMs, UpdatedMs, IsActive }
- **struct TrackListRequest** { Scope, Tag }
- **struct TrackGetRequest** { ID }
- **struct TrackCreateRequest** { Track }
- **struct TrackUpdateRequest** { ID, Patch }
- **struct TrackPatch** { Name, Description, Visibility, Scale, LengthM, WidthM, LaneWidthM, Surface, Tags, Centerline }
- **struct TrackDeleteRequest** { ID }
- **struct TrackSnapshot** { GeneratedMs, Version, Tracks }
- **struct TrackAck** { Ok, ID, Error, Track }
- **struct MotorWire** { Kind, Class, KV, PowerW }
- **struct BatteryWire** { VoltageV, CapacityMah, Cells, Chemistry }
- **struct ChassisWire** { Model, Material, Printed, WheelbaseMm, TrackMm }
- **struct CarWire** { ID, Name, OwnerID, Visibility, Scale, LengthMm, WidthMm, HeightMm, WeightG, Chassis, Motor, Battery, Drive, TopSpeedMs, ColorHex, AvatarURL, Active, TotalDistanceM, TotalRaces, TotalLaps, BestLapMs, CreatedMs, UpdatedMs, DeviceID }
- **struct CarListRequest** { Scope, OwnerID }
- **struct CarGetRequest** { ID }
- **struct CarCreateRequest** { Car }
- **struct CarUpdateRequest** { ID, Patch }
- **struct CarPatch** { Name, Visibility, Scale, LengthMm, WidthMm, HeightMm, WeightG, Chassis, Motor, Battery, Drive, TopSpeedMs, ColorHex, AvatarURL, Active, DeviceID }
- **struct CarDeleteRequest** { ID }
- **struct CarSnapshot** { GeneratedMs, Version, Cars }
- **struct CarAck** { Ok, ID, Error, Car }
- **struct CatalogSummary** { TracksTotal, TracksSystem, TracksPublic, TracksPrivate, CarsTotal, CarsSystem, CarsPublic, CarsPrivate, CarsActive, TSMs }
- **struct RaceListResponse** { Items, NextCursor, Count }
- **struct RaceUpcomingItem** { Meta, QueueLen, PlanID, StartAtMs }
- **struct RaceUpcomingResponse** { Items, Count }
- **struct RaceQueueEntry** { DriverID, RaceID, PlanID, EnqueuedMs }
- **struct RaceQueueJoinRequest** { DriverID, Limit }
- **struct RaceQueueJoinResponse** { Joined, Skipped }
- **struct RaceQueueListResponse** { Items, Count }
- **struct RacePlan** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, StartAtMs, IntervalS, Count, Enabled, CreatedMs, UpdatedMs, NextFireMs, FiresDone }
- **struct RacePlanCreateRequest** { ID, Name, TrackID, MaxCars, Laps, TimeLimitS, StartAtMs, IntervalS, Count, Enabled }
- **struct RacePlanListResponse** { Items, NextCursor, Count }
- **struct Driver** { ID, Nickname, Name, AvatarURL, ClanID, CreatedMs, UpdatedMs }
- **struct DriverCreateRequest** { ID, Nickname, Name, AvatarURL, ClanID }
- **struct DriverUpdateRequest** { Name, AvatarURL, ClanID }
- **struct DriverListResponse** { Items, Count }
- **struct Clan** { ID, Tag, Name, AvatarURL, CreatedMs, UpdatedMs }
- **struct ClanCreateRequest** { ID, Tag, Name, AvatarURL }
- **struct ClanUpdateRequest** { Name, AvatarURL }
- **struct ClanListResponse** { Items, Count }
- **struct StatsServer** { StartedMs, UptimeMs, CurrentClients, PeakClients, TotalConnections, TotalDisconnects, TotalRacesCreated, TotalRacesStarted, TotalRacesFinished, TotalLapsRecorded, TotalInputsRecv, TotalSnapshotsSent, SnapshotsDropped, AvgRTTMs }
- **struct StatsDriver** { ID, Name, TotalRacesStarted, TotalRacesFinished, TotalLaps, BestLapMs, LastLapMs, TotalDistanceM, TotalPlaytimeMs, Wins, Podiums, LastSeenMs }
- **struct StatsLap** { RaceID, DriverID, LapMs, Sector1Ms, Sector2Ms, Sector3Ms, Position, TSMs }
- **struct StatsRaceResult** { RaceID, DriverID, FinishedAtMs, Position, TotalLaps, BestLapMs, TotalTimeMs, DNF }
- **struct StatsSnapshot** { Server, Drivers, Laps, Results, TSMs }
- **struct StatsRequest** { Scope, TargetID }
- **struct RaceStanding** { Position, CarID, DriverID, DriverName, Lap, Sector, BestLapMs, LastLapMs, GapMs, DNF }
- **struct RaceStandingsMsg** { RaceID, Tick, TSMs, Lap, Elapsed, Rows }
- **struct LapRecordedMsg** { RaceID, DriverID, Lap, LapMs, Sector1Ms, Sector2Ms, Sector3Ms, TSMs, Position }
- **struct RaceResultMsg** { RaceID, DriverID, Position, TotalLaps, BestLapMs, TotalTimeMs, DNF, FinishedAtMs }
- **struct LeaderboardEntry** { DriverID, Nickname, Name, AvatarURL, ClanID, ClanTag, Points, BestPos, BestTimeMs, Rank }
- **struct LeaderboardResponse** { Items, Total, Limit, Offset, CurrentDriver }
- **struct TrackCalendarEntry** { ID, TrackID, StartAt, EndAt }
- **struct TrackCalendarCreateRequest** { TrackID, StartAt, EndAt }
- **struct TrackCalendarResponse** { Items }
- **struct TrackCalendarLeaderboard** { TrackID, TrackName, StartAt, EndAt, Status, Podium, CurrentDriver }
- **struct TrackCalendarLeaderboardResponse** { Items }
### Functions
- func Encode(*Envelope) ([]byte, error)
- func Decode([]byte) (*Envelope, error)
---
## Package `main` (`cmd/poc-server`)
HTTP handlers for the catalog (tracks + cars).
All HTTP routes live under /api/*:
GET /api/tracks list tracks (optional ?scope=system|public|private&tag=)
GET /api/tracks/{id} get a single track
POST /api/tracks create a track
PUT /api/tracks/{id} update a track
DELETE /api/tracks/{id} delete a track
GET /api/cars list cars (optional ?scope=...&owner_id=...)
GET /api/cars/{id} get a single car
POST /api/cars create a car
PUT /api/cars/{id} update a car
DELETE /api/cars/{id} delete a car
GET /api/catalog combined snapshot + summary
WebSocket access is wired up directly in main.go's readPump switch.
### Structs
- **struct Hub**
- **struct UDPVideoService**
- **struct WebRTCService**
### Functions
- func NewHub(*slog.Logger) *Hub
- func NewUDPVideoService(*slog.Logger, string, int) *UDPVideoService
- func NewWebRTCService(*slog.Logger, *control.Engine, *lobby.Service, *UDPVideoService, *catalog.Service) *WebRTCService
### Methods
- func (*catalogBroadcaster) Run()
- func (*catalogBroadcaster) Stop()
- func (*Hub) BroadcastSnapshot(transport.RaceSnapshot)
- func (*statusRecorder) WriteHeader(int)
- func (*statusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error)
- func (*UDPVideoService) Start(context.Context, *sync.WaitGroup)
- func (*UDPVideoService) SendControlPacket(uint8, []byte) error
- func (*UDPVideoService) SendCommand(uint8, string) error
- func (*UDPVideoService) StreamHandler() http.HandlerFunc
- func (*UDPVideoService) ControlHandler() http.HandlerFunc
- func (*WebRTCService) Start(context.Context, *sync.WaitGroup)
- func (*WebRTCService) ConnectHandler() http.HandlerFunc
- func (*WebRTCService) CloseSession(string)
- func (*WebRTCService) CloseAll()
---
## Package `main` (`scripts/genseed`)
Package main contains the genseed utility. It is built into a standalone
binary via `go build ./scripts/genseed` and is not part of the runtime.
Usage:
go run ./scripts/genseed seeds.json > 002_seed.sql
It reads the JSON dump produced by `DUMP_SEEDS=1 go test ./internal/catalog`
and emits ON CONFLICT-safe INSERTs for the catalog tables.
---
## Package `main` (`scripts/genseed-json`)
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.
---
+5
View File
@@ -84,4 +84,9 @@ compose-down:
clean:
rm -rf $(BIN_DIR)
.PHONY: graphify
graphify:
$(GO) run ./scripts/graphify/main.go
.DEFAULT_GOAL := build
+3 -3
View File
@@ -10,14 +10,14 @@ import (
"github.com/jackc/pgx/v5/pgconn"
)
// Service composes validation on top of PgStore.
// Service composes validation on top of Store.
type Service struct {
pg *PgStore
pg Store
now func() time.Time
}
// NewService wires a service.
func NewService(pg *PgStore) *Service {
func NewService(pg Store) *Service {
return &Service{pg: pg, now: time.Now}
}
+166
View File
@@ -0,0 +1,166 @@
package clans
import (
"context"
"errors"
"testing"
"time"
"github.com/jackc/pgx/v5/pgconn"
)
type memoryStore struct {
clans map[string]Clan
}
func newMemoryStore() *memoryStore {
return &memoryStore{clans: make(map[string]Clan)}
}
func (m *memoryStore) Exec(ctx context.Context, sql string) (int64, error) {
return 0, nil
}
func (m *memoryStore) Create(ctx context.Context, c Clan) error {
for _, existing := range m.clans {
if existing.Tag == c.Tag {
return &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint"}
}
}
m.clans[c.ID] = c
return nil
}
func (m *memoryStore) Get(ctx context.Context, id string) (Clan, error) {
c, ok := m.clans[id]
if !ok {
return Clan{}, ErrNotFound
}
return c, nil
}
func (m *memoryStore) GetByTag(ctx context.Context, tag string) (Clan, error) {
for _, c := range m.clans {
if c.Tag == tag {
return c, nil
}
}
return Clan{}, ErrNotFound
}
func (m *memoryStore) List(ctx context.Context, limit, offset int) ([]Clan, error) {
out := make([]Clan, 0)
for _, c := range m.clans {
out = append(out, c)
}
return out, nil
}
func (m *memoryStore) Delete(ctx context.Context, id string) error {
if _, ok := m.clans[id]; !ok {
return ErrNotFound
}
delete(m.clans, id)
return nil
}
func (m *memoryStore) Update(ctx context.Context, c Clan) error {
if _, ok := m.clans[c.ID]; !ok {
return ErrNotFound
}
m.clans[c.ID] = c
return nil
}
func (m *memoryStore) Count(ctx context.Context) (int, error) {
return len(m.clans), nil
}
func TestClansService(t *testing.T) {
store := newMemoryStore()
svc := NewService(store)
svc.now = func() time.Time {
return time.Unix(1700000000, 0)
}
ctx := context.Background()
// 1. Create a clan with valid inputs
c1, err := svc.Create(ctx, CreateInput{
ID: "clan-1",
Tag: "SPD",
Name: "Speed Demons",
AvatarURL: "https://example.com/spd.png",
})
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if c1.ID != "clan-1" || c1.Tag != "SPD" || c1.Name != "Speed Demons" {
t.Errorf("Unexpected created clan: %+v", c1)
}
// 2. Validate Tag must be 3 uppercase ASCII letters
_, err = svc.Create(ctx, CreateInput{Tag: "SPEED", Name: "toolongtag"})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for tag 'SPEED', got: %v", err)
}
_, err = svc.Create(ctx, CreateInput{Tag: "S1D", Name: "withnumbertag"})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for tag 'S1D', got: %v", err)
}
// 3. Validate empty Name is rejected
_, err = svc.Create(ctx, CreateInput{Tag: "AAA", Name: ""})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for empty name, got: %v", err)
}
// 4. Duplicate tag check
_, err = svc.Create(ctx, CreateInput{
ID: "clan-2",
Tag: "SPD",
Name: "Duplicate SPD Tag",
})
if !errors.Is(err, ErrAlreadyExists) {
t.Errorf("Expected ErrAlreadyExists for duplicate tag, got: %v", err)
}
// 5. Get clan
got, err := svc.Get(ctx, "clan-1")
if err != nil {
t.Fatalf("Get failed: %v", err)
}
if got.Tag != "SPD" {
t.Errorf("Expected SPD, got %s", got.Tag)
}
// 6. Get by tag
gotByTag, err := svc.GetByTag(ctx, "spd") // should normalize to SPD
if err != nil {
t.Fatalf("GetByTag failed: %v", err)
}
if gotByTag.ID != "clan-1" {
t.Errorf("Expected clan-1, got %s", gotByTag.ID)
}
// 7. Update clan
updated, err := svc.Update(ctx, "clan-1", UpdateInput{
Name: "Super Speed Demons",
AvatarURL: "https://example.com/super-spd.png",
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
if updated.Name != "Super Speed Demons" || updated.AvatarURL != "https://example.com/super-spd.png" {
t.Errorf("Unexpected updated clan fields: %+v", updated)
}
// 8. Delete clan
err = svc.Delete(ctx, "clan-1")
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
_, err = svc.Get(ctx, "clan-1")
if !errors.Is(err, ErrNotFound) {
t.Errorf("Expected ErrNotFound after deletion, got: %v", err)
}
}
+12
View File
@@ -39,6 +39,18 @@ type Clan struct {
UpdatedMs int64
}
// Store defines the persistence operations for clans.
type Store interface {
Exec(ctx context.Context, sql string) (int64, error)
Create(ctx context.Context, c Clan) error
Get(ctx context.Context, id string) (Clan, error)
GetByTag(ctx context.Context, tag string) (Clan, error)
List(ctx context.Context, limit, offset int) ([]Clan, error)
Delete(ctx context.Context, id string) error
Update(ctx context.Context, c Clan) error
Count(ctx context.Context) (int, error)
}
// PgStore is the Postgres-backed half of the package.
type PgStore struct {
pool *pgxpool.Pool
+3 -3
View File
@@ -10,14 +10,14 @@ import (
"github.com/jackc/pgx/v5/pgconn"
)
// Service composes validation on top of PgStore.
// Service composes validation on top of Store.
type Service struct {
pg *PgStore
pg Store
now func() time.Time
}
// NewService wires a service.
func NewService(pg *PgStore) *Service {
func NewService(pg Store) *Service {
return &Service{pg: pg, now: time.Now}
}
+167
View File
@@ -0,0 +1,167 @@
package drivers
import (
"context"
"errors"
"testing"
"time"
"github.com/jackc/pgx/v5/pgconn"
)
type memoryStore struct {
drivers map[string]Driver
}
func newMemoryStore() *memoryStore {
return &memoryStore{drivers: make(map[string]Driver)}
}
func (m *memoryStore) Exec(ctx context.Context, sql string) (int64, error) {
return 0, nil
}
func (m *memoryStore) Create(ctx context.Context, d Driver) error {
for _, existing := range m.drivers {
if existing.Nickname == d.Nickname {
return &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint"}
}
}
m.drivers[d.ID] = d
return nil
}
func (m *memoryStore) Get(ctx context.Context, id string) (Driver, error) {
d, ok := m.drivers[id]
if !ok {
return Driver{}, ErrNotFound
}
return d, nil
}
func (m *memoryStore) GetByNickname(ctx context.Context, nick string) (Driver, error) {
for _, d := range m.drivers {
if d.Nickname == nick {
return d, nil
}
}
return Driver{}, ErrNotFound
}
func (m *memoryStore) List(ctx context.Context, limit, offset int, clanID string) ([]Driver, error) {
out := make([]Driver, 0)
for _, d := range m.drivers {
if clanID == "" || d.ClanID == clanID {
out = append(out, d)
}
}
return out, nil
}
func (m *memoryStore) Delete(ctx context.Context, id string) error {
if _, ok := m.drivers[id]; !ok {
return ErrNotFound
}
delete(m.drivers, id)
return nil
}
func (m *memoryStore) Update(ctx context.Context, d Driver) error {
if _, ok := m.drivers[d.ID]; !ok {
return ErrNotFound
}
m.drivers[d.ID] = d
return nil
}
func (m *memoryStore) Count(ctx context.Context) (int, error) {
return len(m.drivers), nil
}
func TestDriversService(t *testing.T) {
store := newMemoryStore()
svc := NewService(store)
svc.now = func() time.Time {
return time.Unix(1700000000, 0)
}
ctx := context.Background()
// 1. Create a driver
d1, err := svc.Create(ctx, CreateInput{
ID: "driver-1",
Nickname: "HAM",
Name: "Lewis Hamilton",
AvatarURL: "https://example.com/ham.png",
ClanID: "clan-1",
})
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if d1.ID != "driver-1" || d1.Nickname != "HAM" || d1.Name != "Lewis Hamilton" || d1.ClanID != "clan-1" {
t.Errorf("Unexpected created driver: %+v", d1)
}
// 2. Validate Nickname must be 3 uppercase ASCII letters
_, err = svc.Create(ctx, CreateInput{Nickname: "HAMILTON", Name: "toolong"})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for tag 'HAMILTON', got: %v", err)
}
// 3. Validate empty Name is rejected
_, err = svc.Create(ctx, CreateInput{Nickname: "BOT", Name: ""})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for empty name, got: %v", err)
}
// 4. Duplicate nickname check
_, err = svc.Create(ctx, CreateInput{
ID: "driver-2",
Nickname: "HAM",
Name: "Another Lewis",
})
if !errors.Is(err, ErrAlreadyExists) {
t.Errorf("Expected ErrAlreadyExists for duplicate nickname, got: %v", err)
}
// 5. Get driver
got, err := svc.Get(ctx, "driver-1")
if err != nil {
t.Fatalf("Get failed: %v", err)
}
if got.Nickname != "HAM" {
t.Errorf("Expected HAM, got %s", got.Nickname)
}
// 6. Get by nickname
gotByNick, err := svc.GetByNickname(ctx, "ham") // should normalize to HAM
if err != nil {
t.Fatalf("GetByNickname failed: %v", err)
}
if gotByNick.ID != "driver-1" {
t.Errorf("Expected driver-1, got %s", gotByNick.ID)
}
// 7. Update driver
clanID := "clan-2"
updated, err := svc.Update(ctx, "driver-1", UpdateInput{
Name: "Lewis Hamilton MBE",
AvatarURL: "https://example.com/ham-mbe.png",
ClanID: &clanID,
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
if updated.Name != "Lewis Hamilton MBE" || updated.AvatarURL != "https://example.com/ham-mbe.png" || updated.ClanID != "clan-2" {
t.Errorf("Unexpected updated driver fields: %+v", updated)
}
// 8. Delete driver
err = svc.Delete(ctx, "driver-1")
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
_, err = svc.Get(ctx, "driver-1")
if !errors.Is(err, ErrNotFound) {
t.Errorf("Expected ErrNotFound after deletion, got: %v", err)
}
}
+12
View File
@@ -39,6 +39,18 @@ type Driver struct {
UpdatedMs int64
}
// Store defines the persistence operations for drivers.
type Store interface {
Exec(ctx context.Context, sql string) (int64, error)
Create(ctx context.Context, d Driver) error
Get(ctx context.Context, id string) (Driver, error)
GetByNickname(ctx context.Context, nick string) (Driver, error)
List(ctx context.Context, limit, offset int, clanID string) ([]Driver, error)
Delete(ctx context.Context, id string) error
Update(ctx context.Context, d Driver) error
Count(ctx context.Context) (int, error)
}
// PgStore is the Postgres-backed half of the package.
type PgStore struct {
pool *pgxpool.Pool
+250
View File
@@ -0,0 +1,250 @@
// Package main is the graphify utility. It walks the codebase, parses Go AST,
// and outputs a highly compact symbol summary to server/CODEBASE.md.
// This is used for token savings and helping developers/AI agents quickly map out imports and structures.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"strings"
)
type PackageSummary struct {
Name string
Path string
Doc string
Structs []string
Interfaces []string
Functions []string
Methods []string
}
func main() {
rootDir := "."
outputFile := "CODEBASE.md"
var buf bytes.Buffer
buf.WriteString("# x0gp Go Codebase Map (Generated)\n\n")
buf.WriteString("This file contains a compiled map of all packages, structures, interfaces, and public functions in the `server` directory. It is optimized for token-efficiency so AI agents and humans can understand the codebase layout without reading all files.\n\n")
pkgs := make(map[string]*PackageSummary)
fset := token.NewFileSet()
err := filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
// Skip hidden and build dirs
name := d.Name()
if (name != "." && strings.HasPrefix(name, ".")) || name == "vendor" || name == "bin" || name == "docs" || name == "web" {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(d.Name(), ".go") || strings.HasSuffix(d.Name(), "_test.go") {
return nil
}
dir := filepath.Dir(path)
pkgSum, exists := pkgs[dir]
if !exists {
pkgSum = &PackageSummary{
Path: dir,
}
pkgs[dir] = pkgSum
}
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return nil // ignore unparseable files defensively
}
pkgSum.Name = node.Name.Name
if node.Doc != nil && pkgSum.Doc == "" {
pkgSum.Doc = strings.TrimSpace(node.Doc.Text())
}
// Inspect AST for declarations
ast.Inspect(node, func(n ast.Node) bool {
switch decl := n.(type) {
case *ast.GenDecl:
for _, spec := range decl.Specs {
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
if !ast.IsExported(typeSpec.Name.Name) {
continue
}
switch typeType := typeSpec.Type.(type) {
case *ast.StructType:
structDesc := fmt.Sprintf("- **struct %s**", typeSpec.Name.Name)
if typeType.Fields != nil && len(typeType.Fields.List) > 0 {
fields := []string{}
for _, f := range typeType.Fields.List {
for _, id := range f.Names {
if ast.IsExported(id.Name) {
fields = append(fields, id.Name)
}
}
}
if len(fields) > 0 {
structDesc += fmt.Sprintf(" { %s }", strings.Join(fields, ", "))
}
}
pkgSum.Structs = append(pkgSum.Structs, structDesc)
case *ast.InterfaceType:
methods := []string{}
if typeType.Methods != nil {
for _, m := range typeType.Methods.List {
for _, id := range m.Names {
if ast.IsExported(id.Name) {
methods = append(methods, id.Name)
}
}
}
}
pkgSum.Interfaces = append(pkgSum.Interfaces, fmt.Sprintf("- **interface %s** { %s }", typeSpec.Name.Name, strings.Join(methods, ", ")))
}
}
}
case *ast.FuncDecl:
if !ast.IsExported(decl.Name.Name) {
return true
}
// Format function signature
params := []string{}
if decl.Type.Params != nil {
for _, p := range decl.Type.Params.List {
for range p.Names {
params = append(params, formatType(p.Type))
}
if len(p.Names) == 0 {
params = append(params, formatType(p.Type))
}
}
}
results := []string{}
if decl.Type.Results != nil {
for _, r := range decl.Type.Results.List {
for range r.Names {
results = append(results, formatType(r.Type))
}
if len(r.Names) == 0 {
results = append(results, formatType(r.Type))
}
}
}
sig := fmt.Sprintf("%s(%s)", decl.Name.Name, strings.Join(params, ", "))
if len(results) > 0 {
if len(results) == 1 {
sig += " " + results[0]
} else {
sig += " (" + strings.Join(results, ", ") + ")"
}
}
if decl.Recv != nil && len(decl.Recv.List) > 0 {
recvType := formatType(decl.Recv.List[0].Type)
pkgSum.Methods = append(pkgSum.Methods, fmt.Sprintf("- func (%s) %s", recvType, sig))
} else {
pkgSum.Functions = append(pkgSum.Functions, fmt.Sprintf("- func %s", sig))
}
}
return true
})
return nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error gathering package summaries: %v\n", err)
os.Exit(1)
}
// Write out gathered summaries sorted/grouped by path
// (we can sort keys or print them grouped by internal vs cmd)
paths := []string{}
for p := range pkgs {
paths = append(paths, p)
}
// Simple sort: cmd/ first, then internal/
// We want to present it nicely
for _, path := range paths {
sum := pkgs[path]
cleanPath := filepath.ToSlash(path)
buf.WriteString(fmt.Sprintf("## Package `%s` (`%s`)\n\n", sum.Name, cleanPath))
if sum.Doc != "" {
buf.WriteString(fmt.Sprintf("%s\n\n", sum.Doc))
}
if len(sum.Interfaces) > 0 {
buf.WriteString("### Interfaces\n")
for _, item := range sum.Interfaces {
buf.WriteString(item + "\n")
}
buf.WriteString("\n")
}
if len(sum.Structs) > 0 {
buf.WriteString("### Structs\n")
for _, item := range sum.Structs {
buf.WriteString(item + "\n")
}
buf.WriteString("\n")
}
if len(sum.Functions) > 0 {
buf.WriteString("### Functions\n")
for _, item := range sum.Functions {
buf.WriteString(item + "\n")
}
buf.WriteString("\n")
}
if len(sum.Methods) > 0 {
buf.WriteString("### Methods\n")
for _, item := range sum.Methods {
buf.WriteString(item + "\n")
}
buf.WriteString("\n")
}
buf.WriteString("---\n\n")
}
err = os.WriteFile(outputFile, buf.Bytes(), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error writing CODEBASE.md: %v\n", err)
os.Exit(1)
}
fmt.Printf("Generated %s successfully!\n", outputFile)
}
func formatType(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + formatType(t.X)
case *ast.SelectorExpr:
return formatType(t.X) + "." + t.Sel.Name
case *ast.ArrayType:
return "[]" + formatType(t.Elt)
case *ast.MapType:
return "map[" + formatType(t.Key) + "]" + formatType(t.Value)
case *ast.InterfaceType:
return "interface{}"
case *ast.FuncType:
return "func"
default:
return "?"
}
}
+8
View File
@@ -0,0 +1,8 @@
# Supabase
.branches
.temp
# dotenvx
.env.keys
.env.local
.env.*.local
+414
View File
@@ -0,0 +1,414 @@
# For detailed configuration reference documentation, visit:
# https://supabase.com/docs/guides/local-development/cli/config
# A string used to distinguish different Supabase projects on the same host. Defaults to the
# working directory name when running `supabase init`.
project_id = "x0gp"
[api]
enabled = true
# Port to use for the API URL.
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. `public` and `graphql_public` schemas are included by default.
schemas = ["public", "graphql_public"]
# Extra schemas to add to the search_path of every request.
extra_search_path = ["public", "extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
# for accidental or malicious requests.
max_rows = 1000
# Controls whether new tables, views, sequences and functions created in the `public` schema by
# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`)
# without explicit GRANTs. When unset, new entities are NOT auto-exposed, matching the new cloud
# default. Set to `true` to keep the legacy behaviour of auto-exposing new entities; this is
# deprecated and the field is removed on 2026-10-30 once the always-revoked behaviour is permanent.
# auto_expose_new_tables = true
[api.tls]
# Enable HTTPS endpoints locally using a self-signed certificate.
enabled = false
# Paths to self-signed certificate pair.
# cert_path = "../certs/my-cert.pem"
# key_path = "../certs/my-key.pem"
[db]
# Port to use for the local database URL.
port = 54322
# Port used by db diff command to initialize the shadow database.
shadow_port = 54320
# Maximum amount of time to wait for health check when starting the local database.
health_timeout = "2m"
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 17
[db.pooler]
enabled = false
# Port to use for the local connection pooler.
port = 54329
# Specifies when a server connection can be reused by other clients.
# Configure one of the supported pooler modes: `transaction`, `session`.
pool_mode = "transaction"
# How many server connections to allow per user/database pair.
default_pool_size = 20
# Maximum number of client connections allowed.
max_client_conn = 100
# [db.vault]
# secret_key = "env(SECRET_VALUE)"
[db.migrations]
# If disabled, migrations will be skipped during a db push or reset.
enabled = true
# Specifies an ordered list of schema files, directories, or glob patterns that describe your database.
# Supports paths relative to supabase directory: "./schemas/*.sql", "./database".
schema_paths = []
[db.seed]
# If enabled, seeds the database after migrations during a db reset.
enabled = true
# Specifies an ordered list of seed files to load during db reset.
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
sql_paths = ["./seed.sql"]
[db.network_restrictions]
# Enable management of network restrictions.
enabled = false
# List of IPv4 CIDR blocks allowed to connect to the database.
# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
allowed_cidrs = ["0.0.0.0/0"]
# List of IPv6 CIDR blocks allowed to connect to the database.
# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
allowed_cidrs_v6 = ["::/0"]
# Uncomment to reject non-secure connections to the database.
# [db.ssl_enforcement]
# enabled = true
[realtime]
enabled = true
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
# ip_version = "IPv6"
# The maximum length in bytes of HTTP request headers. (default: 4096)
# max_header_length = 4096
[studio]
enabled = true
# Port to use for Supabase Studio.
port = 54323
# External URL of the API server that frontend connects to.
api_url = "http://127.0.0.1"
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
openai_api_key = "env(OPENAI_API_KEY)"
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
# are monitored, and you can view the emails that would have been sent from the web interface.
[local_smtp]
enabled = true
# Port to use for the email testing server web interface.
port = 54324
# Uncomment to expose additional ports for testing user applications that send emails.
# smtp_port = 54325
# pop3_port = 54326
# admin_email = "admin@email.com"
# sender_name = "Admin"
[storage]
enabled = true
# The maximum file size allowed (e.g. "5MB", "500KB").
file_size_limit = "50MiB"
# Uncomment to configure local storage buckets
# [storage.buckets.images]
# public = false
# file_size_limit = "50MiB"
# allowed_mime_types = ["image/png", "image/jpeg"]
# objects_path = "./images"
# Allow connections via S3 compatible clients
[storage.s3_protocol]
enabled = true
# Image transformation API is available to Supabase Pro plan.
# [storage.image_transformation]
# enabled = true
# Store analytical data in S3 for running ETL jobs over Iceberg Catalog
# This feature is only available on the hosted platform.
[storage.analytics]
enabled = false
max_namespaces = 5
max_tables = 10
max_catalogs = 2
# Analytics Buckets is available to Supabase Pro plan.
# [storage.analytics.buckets.my-warehouse]
# Store vector embeddings in S3 for large and durable datasets
[storage.vector]
enabled = true
max_buckets = 10
max_indexes = 5
# Vector Buckets is available to Supabase Pro plan.
# [storage.vector.buckets.documents-openai]
[auth]
enabled = true
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://127.0.0.1:3000"
# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended.
# external_url = ""
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://127.0.0.1:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
jwt_expiry = 3600
# JWT issuer URL. If not set, defaults to auth.external_url.
# jwt_issuer = ""
# Path to JWT signing key. DO NOT commit your signing keys file to git.
# signing_keys_path = "./signing_keys.json"
# If disabled, the refresh token will never expire.
enable_refresh_token_rotation = true
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
# Requires enable_refresh_token_rotation = true.
refresh_token_reuse_interval = 10
# Allow/disallow new user signups to your project.
enable_signup = true
# Allow/disallow anonymous sign-ins to your project.
enable_anonymous_sign_ins = false
# Allow/disallow testing manual linking of accounts
enable_manual_linking = false
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
minimum_password_length = 6
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
password_requirements = ""
# Configure passkey sign-ins.
# [auth.passkey]
# enabled = false
# Configure WebAuthn relying party settings (required when passkey is enabled).
# [auth.webauthn]
# rp_display_name = "Supabase"
# rp_id = "localhost"
# rp_origins = ["http://127.0.0.1:3000"]
[auth.rate_limit]
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
email_sent = 2
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
sms_sent = 30
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
anonymous_users = 30
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
token_refresh = 150
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
sign_in_sign_ups = 30
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
token_verifications = 30
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
web3 = 30
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
# [auth.captcha]
# enabled = true
# provider = "hcaptcha"
# secret = ""
[auth.email]
# Allow/disallow new user signups via email to your project.
enable_signup = true
# If enabled, a user will be required to confirm any email change on both the old, and new email
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
secure_password_change = false
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
max_frequency = "1s"
# Number of characters used in the email OTP.
otp_length = 6
# Number of seconds before the email OTP expires (defaults to 1 hour).
otp_expiry = 3600
# Use a production-ready SMTP server
# [auth.email.smtp]
# enabled = true
# host = "smtp.sendgrid.net"
# port = 587
# user = "apikey"
# pass = "env(SENDGRID_API_KEY)"
# admin_email = "admin@email.com"
# sender_name = "Admin"
# Uncomment to customize email template
# [auth.email.template.invite]
# subject = "You have been invited"
# content_path = "./supabase/templates/invite.html"
# Uncomment to customize notification email template
# [auth.email.notification.password_changed]
# enabled = true
# subject = "Your password has been changed"
# content_path = "./templates/password_changed_notification.html"
[auth.sms]
# Allow/disallow new user signups via SMS to your project.
enable_signup = false
# If enabled, users need to confirm their phone number before signing in.
enable_confirmations = false
# Template for sending OTP to users
template = "Your code is {{ `{{ .Code }}` }}"
# Controls the minimum amount of time that must pass before sending another sms otp.
max_frequency = "5s"
# Use pre-defined map of phone number to OTP for testing.
# [auth.sms.test_otp]
# 4152127777 = "123456"
# Configure logged in session timeouts.
# [auth.sessions]
# Force log out after the specified duration.
# timebox = "24h"
# Force log out if the user has been inactive longer than the specified duration.
# inactivity_timeout = "8h"
# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
# [auth.hook.before_user_created]
# enabled = true
# uri = "pg-functions://postgres/auth/before-user-created-hook"
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
# [auth.hook.custom_access_token]
# enabled = true
# uri = "pg-functions://<database>/<schema>/<hook_name>"
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
[auth.sms.twilio]
enabled = false
account_sid = ""
message_service_sid = ""
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
# Multi-factor-authentication is available to Supabase Pro plan.
[auth.mfa]
# Control how many MFA factors can be enrolled at once per user.
max_enrolled_factors = 10
# Control MFA via App Authenticator (TOTP)
[auth.mfa.totp]
enroll_enabled = false
verify_enabled = false
# Configure MFA via Phone Messaging
[auth.mfa.phone]
enroll_enabled = false
verify_enabled = false
otp_length = 6
template = "Your code is {{ `{{ .Code }}` }}"
max_frequency = "5s"
# Configure MFA via WebAuthn
# [auth.mfa.web_authn]
# enroll_enabled = true
# verify_enabled = true
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
# `twitter`, `x`, `slack`, `spotify`, `workos`, `zoom`.
[auth.external.apple]
enabled = false
client_id = ""
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
# Overrides the default auth callback URL derived from auth.external_url.
redirect_uri = ""
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
# or any other third-party OIDC providers.
url = ""
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
skip_nonce_check = false
# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address.
email_optional = false
# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
[auth.web3.solana]
enabled = false
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
[auth.third_party.firebase]
enabled = false
# project_id = "my-firebase-project"
# Use Auth0 as a third-party provider alongside Supabase Auth.
[auth.third_party.auth0]
enabled = false
# tenant = "my-auth0-tenant"
# tenant_region = "us"
# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
[auth.third_party.aws_cognito]
enabled = false
# user_pool_id = "my-user-pool-id"
# user_pool_region = "us-east-1"
# Use Clerk as a third-party provider alongside Supabase Auth.
[auth.third_party.clerk]
enabled = false
# Obtain from https://clerk.com/setup/supabase
# domain = "example.clerk.accounts.dev"
# OAuth server configuration
[auth.oauth_server]
# Enable OAuth server functionality
enabled = false
# Path for OAuth consent flow UI
authorization_url_path = "/oauth/consent"
# Allow dynamic client registration
allow_dynamic_registration = false
[edge_runtime]
enabled = true
# Supported request policies: `oneshot`, `per_worker`.
# `per_worker` (default) — enables hot reload during local development.
# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks).
policy = "per_worker"
# Port to attach the Chrome inspector for debugging edge functions.
inspector_port = 8083
# The Deno major version to use.
deno_version = 2
# [edge_runtime.secrets]
# secret_key = "env(SECRET_VALUE)"
[analytics]
enabled = true
port = 54327
# Configure one of the supported backends: `postgres`, `bigquery`.
backend = "postgres"
# Experimental features may be deprecated any time
[experimental]
# Configures Postgres storage engine to use OrioleDB (S3)
orioledb_version = ""
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
s3_host = "env(S3_HOST)"
# Configures S3 bucket region, eg. us-east-1
s3_region = "env(S3_REGION)"
# Configures AWS_ACCESS_KEY_ID for S3 bucket
s3_access_key = "env(S3_ACCESS_KEY)"
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
s3_secret_key = "env(S3_SECRET_KEY)"
# pg-delta is the schema diff engine for db diff / db pull / db remote commit.
# Set enabled = false to fall back to the legacy migra engine.
[experimental.pgdelta]
enabled = true
# Directory under `supabase/` where declarative files are written.
# declarative_schema_path = "./database"
# JSON string passed through to pg-delta SQL formatting.
# format_options = "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}"
@@ -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;
+345
View File
@@ -0,0 +1,345 @@
-- seed.sql — Initial system catalog (tracks + cars) for Supabase.
-- ---------------------------------------------------------------------------
-- Monaco (Rescaled)
-- ---------------------------------------------------------------------------
INSERT INTO tracks (id, name, description, author_id, visibility, scale,
length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,
bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,
created_ms, updated_ms)
VALUES ('monaco', 'Monaco', 'Circuit de Monaco. Narrow street circuit, tight hairpins, no room for error.', 'system', 'system', 27,
21.87, 6.46, 0.18, 'tile', 0, 'Verstappen',
-3.231, -2.684, 3.231, 2.684,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 0, -3.2064, 1.0454, -1.4710, 0.632, -0.1988) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 1, -3.1387, 0.5857, -1.3926, 0.632, -0.1787) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 2, -3.0418, 0.1312, -1.3043, 0.632, -0.2544) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 3, -2.8946, -0.3084, -1.1557, 0.632, -0.4431) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 4, -2.6693, -0.7141, -0.8629, 0.632, -1.0875) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 5, -2.3517, -0.9426, -0.1919, 0.632, -1.2844) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 6, -1.8935, -0.8648, 0.1182, 0.632, -0.2237) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 7, -1.4302, -0.8332, 0.0660, 0.632, 0.0475) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 8, -0.9667, -0.8035, 0.0743, 0.632, 0.0756) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 9, -0.5052, -0.7644, -0.0045, 0.632, 0.0806) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 10, -0.0425, -0.8077, -0.0000, 0.632, -0.0425) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 11, 0.4197, -0.7644, 0.0351, 0.632, 0.3650) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 12, 0.8809, -0.7753, -0.3862, 0.632, 1.3887) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 13, 1.1985, -1.0811, -1.2440, 0.632, 1.4550) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 14, 1.1372, -1.5315, -1.5878, 0.632, -0.2144) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 15, 1.1839, -1.9468, -1.0284, 0.632, -1.2681) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 16, 1.5551, -2.2249, -0.6162, 0.632, -0.4763) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 17, 1.9414, -2.4832, -0.5226, 0.632, -0.1756) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 18, 2.3526, -2.6842, -0.4546, 0.632, -0.1494) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 19, 2.3526, -2.6842, 1.2476, 0.632, -0.0225) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 20, 2.4622, -2.3570, 1.2545, 0.632, 0.1775) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 21, 2.5976, -1.9356, 0.5751, 0.632, 34.1925) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 22, 2.6154, -2.2577, -1.0415, 0.632, -0.8400) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 23, 2.9239, -2.4933, 0.0053, 0.632, -3.1975) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 24, 3.2311, -2.2544, 1.4159, 0.632, -2.7456) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 25, 3.0259, -1.8399, 2.1127, 0.632, -0.6488) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 26, 2.7546, -1.4629, 2.2528, 0.632, -0.2769) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 27, 2.4413, -1.1199, 2.3708, 0.632, -0.3044) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 28, 2.0895, -0.8170, 2.5300, 0.632, -0.3825) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 29, 1.6852, -0.5897, 2.7255, 0.632, -0.4019) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 30, 1.2447, -0.4437, 2.9031, 0.632, -0.3538) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 31, 0.7862, -0.3711, 3.0547, 0.632, -0.3044) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 32, 0.3218, -0.3632, -3.0973, 0.632, -0.0975) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 33, -0.1396, -0.4121, -3.1387, 0.632, -0.0394) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 34, -0.5818, -0.3659, -3.0594, 0.632, -0.2375) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 35, -1.0290, -0.4854, -2.9319, 0.632, -0.0950) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 36, -1.4880, -0.5588, -2.9664, 0.632, -0.2500) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 37, -1.9441, -0.6474, 3.1052, 0.632, 0.8825) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 38, -2.3631, -0.5269, 2.4971, 0.632, 1.3600) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 39, -2.6181, -0.1409, 1.9547, 0.632, 0.9481) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 40, -2.7014, 0.3108, 1.5706, 0.632, 0.3788) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 41, -2.6179, 0.7633, 1.6025, 0.632, -0.3425) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 42, -2.7299, 1.2092, 1.8788, 0.632, -0.0725) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 43, -2.8930, 1.6280, 1.6659, 0.632, 0.7706) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 44, -2.8134, 2.0840, 1.2045, 0.632, 0.2162) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 45, -2.5685, 2.4739, 1.5682, 0.632, -2.4475) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 46, -2.8118, 2.6842, -3.0638, 0.632, -4.0544) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 47, -3.1281, 2.4303, -2.0706, 0.632, -1.2500) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 48, -3.2004, 1.9728, -1.6822, 0.632, -0.4706) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 49, -3.2311, 1.5093, -1.5774, 0.632, -0.2275) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('monaco', 50, -3.2064, 1.0454, -1.4710, 0.632, -0.1988) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('monaco', 'f1') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('monaco', 'street') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('monaco', 'tight') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('monaco', 'slow') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('monaco', 'monaco') ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Barcelona-Catalunya
-- ---------------------------------------------------------------------------
INSERT INTO tracks (id, name, description, author_id, visibility, scale,
length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,
bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,
created_ms, updated_ms)
VALUES ('barcelona-catalunya', 'Barcelona-Catalunya', 'Circuit de Barcelona-Catalunya. Mix of high-speed corners and technical chicanes.', 'system', 'system', 27,
3.75, 2.30, 0.18, 'tile', 0, 'Verstappen',
0.3, -2.7, 4.05, -0.4,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 0, 0.3, -0.4, -0, 4.5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 1, 1.4, -0.4, -0, 4.5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 2, 1.7, -0.65, -1.5707963267948966, 2.2, 1.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 3, 1.5, -0.95, -3.141592653589793, 2, 2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 4, 1.2, -1.1, -3.4415926535897934, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 5, 0.95, -1, -4.0415926535897935, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 6, 0.8, -1.2, 1.5707963267948966, 2.4, 1.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 7, 1.05, -1.5, -0, 3.2, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 8, 1.3, -1.65, -1.5707963267948966, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 9, 1.15, -1.85, -3.141592653589793, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 10, 1.4, -2, -2.641592653589793, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 11, 1.7, -2, -2.141592653589793, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 12, 1.95, -2.2, -1.5707963267948966, 2.8, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 13, 1.65, -2.5, -0.3, 2.8, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 14, 2.2, -2.7, -0.7, 3.2, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 15, 2.8, -2.7, -1.1, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 16, 3.1, -2.5, -1.9707963267948967, 2.2, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 17, 2.95, -2.2, -3.541592653589793, 2.2, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 18, 3.3, -1.9, -0, 4, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 19, 4, -1.9, -0, 4, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 20, 4.05, -1.6, 1.5707963267948966, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 21, 3.8, -1.4, -3.141592653589793, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 22, 3.5, -1.5, -3.9269908169872414, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 23, 3.1, -1.25, 1.9707963267948967, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 24, 2.7, -1, 2.4707963267948965, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 25, 2.3, -0.7, -3.541592653589793, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 26, 1.6, -0.5, -3.3415926535897933, 3.6, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('barcelona-catalunya', 27, 0.9, -0.42, -0, 4, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('barcelona-catalunya', 'f1') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('barcelona-catalunya', 'balanced') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('barcelona-catalunya', 'testing') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('barcelona-catalunya', 'barcelona') ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Austria (Red Bull Ring)
-- ---------------------------------------------------------------------------
INSERT INTO tracks (id, name, description, author_id, visibility, scale,
length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,
bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,
created_ms, updated_ms)
VALUES ('austria-redbull-ring', 'Austria (Red Bull Ring)', 'Red Bull Ring. Short, fast, three straights separated by hairpins.', 'system', 'system', 27,
3.55, 2.0, 0.18, 'wood', 0, 'Verstappen',
0.3, -2.4, 3.85, -0.4,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 0, 0.3, -0.4, -0, 4.8, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 1, 1.6, -0.4, -0, 4.8, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 2, 1.85, -0.65, -1.5707963267948966, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 3, 1.6, -0.95, -3.141592653589793, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 4, 1.3, -1, -3.641592653589793, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 5, 1.05, -1.2, 1.5707963267948966, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 6, 1.4, -1.4, -0, 3.2, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 7, 1.85, -1.4, -1.7707963267948965, 3.2, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 8, 2.1, -1.65, -3.141592653589793, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 9, 2, -1.85, 1.5707963267948966, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 10, 2.25, -2.05, -0, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 11, 2.4, -2, -1.5707963267948966, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 12, 2.4, -2.2, -3.141592653589793, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 13, 2.2, -2.3, 1.5707963267948966, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 14, 2.55, -2.4, 0.2, 3.4, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 15, 3, -2.4, -0, 4, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 16, 3.5, -2.4, -0, 4.5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 17, 3.85, -2.15, -1.7707963267948965, 3.4, 0.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 18, 3.6, -1.8, -3.141592653589793, 3, 0.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 19, 3.25, -1.85, -3.741592653589793, 3, 0.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 20, 3, -1.6, 1.9707963267948967, 3.2, 0.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 21, 2.6, -1.4, 3.141592653589793, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 22, 2, -1.2, -2.641592653589793, 3.6, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 23, 1.4, -0.8, -2.9415926535897934, 3.8, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('austria-redbull-ring', 24, 0.8, -0.5, -0, 4.2, 0.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('austria-redbull-ring', 'f1') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('austria-redbull-ring', 'short') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('austria-redbull-ring', 'fast') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('austria-redbull-ring', 'austria') ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Great Britain (Silverstone)
-- ---------------------------------------------------------------------------
INSERT INTO tracks (id, name, description, author_id, visibility, scale,
length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,
bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,
created_ms, updated_ms)
VALUES ('great-britain-silverstone', 'Great Britain (Silverstone)', 'Silverstone Circuit. Fast, flowing corners; home of British motorsport.', 'system', 'system', 27,
3.55, 2.4, 0.18, 'carpet', 0, 'Hamilton',
0.3, -2.65, 3.85, -0.25,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 0, 0.3, -0.3, -0, 5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 1, 2.4, -0.3, -0, 5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 2, 2.8, -0.45, -1.5707963267948966, 3, 0.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 3, 2.6, -0.8, -3.141592653589793, 3.4, 0.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 4, 2.95, -1.05, -2.0707963267948966, 3.2, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 5, 3.3, -1.3, -0, 2.2, 1.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 6, 3.6, -1.15, 1.5707963267948966, 2.2, 1.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 7, 3.6, -0.8, 3.141592653589793, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 8, 3.85, -0.55, 1.5707963267948966, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 9, 3.55, -0.25, -0, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 10, 3.25, -0.25, -1.5707963267948966, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 11, 3.05, -0.55, -1.8707963267948966, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 12, 2.85, -0.45, -3.141592653589793, 4, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 13, 1.8, -0.3, -0, 4.6, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 14, 1.2, -0.45, 1.7707963267948965, 3.8, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 15, 1, -0.85, 3.541592653589793, 3.6, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 16, 0.7, -1.15, -1.8707963267948966, 2.4, 1.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 17, 0.5, -0.9, -0, 2.6, 0.9) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 18, 0.7, -0.65, 1.5707963267948966, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 19, 0.45, -0.45, 3.541592653589793, 2.6, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 20, 0.75, -1, -1.1707963267948966, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 21, 0.5, -1.5, 3.541592653589793, 2.6, 0.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 22, 0.3, -1.15, 1.5707963267948966, 2.4, 1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 23, 0.7, -1.6, -0.7853981633974483, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 24, 2, -1.95, -1.9707963267948967, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 25, 3, -2.4, -3.3415926535897933, 3.4, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 26, 2.4, -2.65, 1.8707963267948966, 4, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 27, 1.4, -2.6, 3.141592653589793, 4.4, 0.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 28, 0.5, -2.3, 2.8415926535897933, 4.8, 0.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('great-britain-silverstone', 29, 0.3, -1.7, 1.2707963267948965, 5, 0.1) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('great-britain-silverstone', 'f1') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('great-britain-silverstone', 'fast') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('great-britain-silverstone', 'flowing') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('great-britain-silverstone', 'silverstone') ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Belgium (Spa-Francorchamps)
-- ---------------------------------------------------------------------------
INSERT INTO tracks (id, name, description, author_id, visibility, scale,
length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,
bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,
created_ms, updated_ms)
VALUES ('belgium-spa', 'Belgium (Spa-Francorchamps)', 'Spa-Francorchamps. Long, fast, legendary Eau Rouge / Raidillon.', 'system', 'system', 27,
3.85, 2.25, 0.18, 'carpet', 0, 'Verstappen',
0.4, -2.75, 4.25, -0.5,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 0, 0.4, -0.5, -0, 4.8, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 1, 1.6, -0.5, -0, 4.8, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 2, 1.95, -0.6, -0.3, 4.2, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 3, 2.2, -0.95, -0.9, 4, 0.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 4, 2.1, -1.3, -3.541592653589793, 4, 0.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 5, 2.7, -1.5, -0, 5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 6, 3.5, -1.5, -0, 5, 0) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 7, 3.85, -1.3, 1.5707963267948966, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 8, 3.6, -1.1, -3.4415926535897934, 2.6, 1.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 9, 3.3, -1.25, -4.0415926535897935, 2.6, 1.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 10, 3.5, -1.55, -1.5707963267948966, 3.6, 0.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 11, 3.25, -1.85, -0, 2.2, 1.6) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 12, 3.05, -1.7, 1.5707963267948966, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 13, 3.25, -1.5, -0, 3, 0.7) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 14, 3.7, -1.65, -1.8707963267948966, 3.4, 0.5) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 15, 4.1, -2, -2.1707963267948966, 4, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 16, 4.25, -2.4, -3.541592653589793, 4.6, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 17, 4, -2.7, 1.8707963267948966, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 18, 3.7, -2.55, -3.141592653589793, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 19, 3.55, -2.75, -3.8415926535897933, 2.4, 1.4) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 20, 3.05, -2.7, 1.5707963267948966, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 21, 2.85, -2.45, -0, 2, 1.8) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 22, 2, -2.2, -2.8415926535897933, 3.6, 0.3) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 23, 1.2, -1.6, -2.541592653589793, 4, 0.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature) VALUES ('belgium-spa', 24, 0.6, -1.05, -2.8415926535897933, 4.4, 0.2) ON CONFLICT (track_id, seq) DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('belgium-spa', 'f1') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('belgium-spa', 'long') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('belgium-spa', 'fast') ON CONFLICT DO NOTHING;
INSERT INTO track_tags (track_id, tag) VALUES ('belgium-spa', 'spa') ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Cars Catalog
-- ---------------------------------------------------------------------------
INSERT INTO cars (id, name, owner_id, visibility, scale,
length_mm, width_mm, height_mm, weight_g,
chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,
motor_kind, motor_class, motor_kv, motor_power_w,
battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,
drive, top_speed_ms, color_hex, avatar_url, active,
created_ms, updated_ms)
VALUES ('f1-2024-redbull-rb20', 'Oracle Red Bull Racing RB20', 'system', 'system', 24,
238, 83, 40, 33,
'RB20 carbon monocoque', 'carbon-fibre', FALSE, 150, 75,
'turbo-hybrid', 'F1 PU', 0, 746000,
0, 0, 0, 'li-ion MGU-K',
'2WD', 95, '#1e3a8a', '', TRUE,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO cars (id, name, owner_id, visibility, scale,
length_mm, width_mm, height_mm, weight_g,
chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,
motor_kind, motor_class, motor_kv, motor_power_w,
battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,
drive, top_speed_ms, color_hex, avatar_url, active,
created_ms, updated_ms)
VALUES ('f1-2024-ferrari-sf24', 'Scuderia Ferrari SF-24', 'system', 'system', 24,
238, 83, 40, 33,
'SF-24 carbon monocoque', 'carbon-fibre', FALSE, 150, 75,
'turbo-hybrid', 'F1 PU 066/12', 0, 746000,
0, 0, 0, 'li-ion MGU-K',
'2WD', 94, '#dc2626', '', TRUE,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO cars (id, name, owner_id, visibility, scale,
length_mm, width_mm, height_mm, weight_g,
chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,
motor_kind, motor_class, motor_kv, motor_power_w,
battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,
drive, top_speed_ms, color_hex, avatar_url, active,
created_ms, updated_ms)
VALUES ('f1-2024-mclaren-mcl38', 'McLaren MCL38', 'system', 'system', 24,
238, 83, 40, 33,
'MCL38 carbon monocoque', 'carbon-fibre', FALSE, 150, 75,
'turbo-hybrid', 'F1 PU Mercedes-derived', 0, 746000,
0, 0, 0, 'li-ion MGU-K',
'2WD', 94, '#ea580c', '', TRUE,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO cars (id, name, owner_id, visibility, scale,
length_mm, width_mm, height_mm, weight_g,
chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,
motor_kind, motor_class, motor_kv, motor_power_w,
battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,
drive, top_speed_ms, color_hex, avatar_url, active,
created_ms, updated_ms)
VALUES ('f1-2024-mercedes-w15', 'Mercedes-AMG W15', 'system', 'system', 24,
238, 83, 40, 33,
'W15 carbon monocoque', 'carbon-fibre', FALSE, 150, 75,
'turbo-hybrid', 'F1 PU M15', 0, 746000,
0, 0, 0, 'li-ion MGU-K',
'2WD', 94, '#06b6d4', '', TRUE,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;
INSERT INTO cars (id, name, owner_id, visibility, scale,
length_mm, width_mm, height_mm, weight_g,
chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,
motor_kind, motor_class, motor_kv, motor_power_w,
battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,
drive, top_speed_ms, color_hex, avatar_url, active,
created_ms, updated_ms)
VALUES ('f1-2024-astonmartin-amr24', 'Aston Martin Aramco AMR24', 'system', 'system', 24,
238, 83, 40, 33,
'AMR24 carbon monocoque', 'carbon-fibre', FALSE, 150, 75,
'turbo-hybrid', 'F1 PU Mercedes-derived', 0, 746000,
0, 0, 0, 'li-ion MGU-K',
'2WD', 94, '#16a34a', '', TRUE,
1700000000000, 1700000000000)
ON CONFLICT (id) DO NOTHING;