// 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 import ( "encoding/json" "fmt" "os" "strconv" "strings" ) type waypoint struct { X float64 `json:"x"` Y float64 `json:"y"` HeadingRad float64 `json:"heading_rad"` SpeedMs float64 `json:"speed_ms"` Curvature float64 `json:"curvature"` } type bounds struct { MinX float64 `json:"min_x"` MinY float64 `json:"min_y"` MaxX float64 `json:"max_x"` MaxY float64 `json:"max_y"` LengthM float64 `json:"length_m"` WidthM float64 `json:"width_m"` } type track struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` AuthorID string `json:"author_id"` Visibility string `json:"visibility"` Scale int `json:"scale"` LengthM float64 `json:"length_m"` WidthM float64 `json:"width_m"` LaneWidthM float64 `json:"lane_width_m"` Bounds bounds `json:"bounds"` Surface string `json:"surface"` Tags []string `json:"tags"` Centerline []waypoint `json:"centerline"` BestLapMs int64 `json:"best_lap_ms"` BestLapHolder string `json:"best_lap_holder"` CreatedMs int64 `json:"created_ms"` UpdatedMs int64 `json:"updated_ms"` } type chassis struct { Model string `json:"model"` Material string `json:"material"` Printed bool `json:"printed"` WheelbaseMm float64 `json:"wheelbase_mm"` TrackMm float64 `json:"track_mm"` } type motor struct { Kind string `json:"kind"` Class string `json:"class"` KV int `json:"kv"` PowerW float64 `json:"power_w"` } type battery struct { VoltageV float64 `json:"voltage_v"` CapacityMah int `json:"capacity_mah"` Cells int `json:"cells"` Chemistry string `json:"chemistry"` } type car struct { ID string `json:"id"` Name string `json:"name"` OwnerID string `json:"owner_id"` Visibility string `json:"visibility"` Scale int `json:"scale"` LengthMm float64 `json:"length_mm"` WidthMm float64 `json:"width_mm"` HeightMm float64 `json:"height_mm"` WeightG float64 `json:"weight_g"` Chassis chassis `json:"chassis"` Motor motor `json:"motor"` Battery battery `json:"battery"` Drive string `json:"drive"` TopSpeedMs float64 `json:"top_speed_ms"` ColorHex string `json:"color_hex"` AvatarURL string `json:"avatar_url"` Active bool `json:"active"` CreatedMs int64 `json:"created_ms"` UpdatedMs int64 `json:"updated_ms"` } type dump struct { Tracks []track `json:"tracks"` Cars []car `json:"cars"` } func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "usage: genseed ") os.Exit(2) } f, err := os.Open(os.Args[1]) if err != nil { panic(err) } defer f.Close() var d dump if err := json.NewDecoder(f).Decode(&d); err != nil { panic(err) } var b strings.Builder b.WriteString("-- 002_seed.sql — initial system catalog (tracks + cars).\n") b.WriteString("-- Generated from internal/catalog/seeddefs via scripts/genseed.\n") b.WriteString("-- Idempotent: safe to re-apply (ON CONFLICT DO NOTHING).\n\n") for _, t := range d.Tracks { fmt.Fprintf(&b, "INSERT INTO tracks (id, name, description, author_id, visibility, scale,\n") fmt.Fprintf(&b, " length_m, width_m, lane_width_m, surface, best_lap_ms, best_lap_holder,\n") fmt.Fprintf(&b, " bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y,\n") fmt.Fprintf(&b, " created_ms, updated_ms)\n") fmt.Fprintf(&b, "VALUES (%s, %s, %s, %s, %s, %d,\n", sqlStr(t.ID), sqlStr(t.Name), sqlStr(t.Description), sqlStr(t.AuthorID), sqlStr(t.Visibility), t.Scale) fmt.Fprintf(&b, " %s, %s, %s, %s, %d, %s,\n", sqlF(t.LengthM), sqlF(t.WidthM), sqlF(t.LaneWidthM), sqlStr(t.Surface), t.BestLapMs, sqlStr(t.BestLapHolder)) fmt.Fprintf(&b, " %s, %s, %s, %s,\n", sqlF(t.Bounds.MinX), sqlF(t.Bounds.MinY), sqlF(t.Bounds.MaxX), sqlF(t.Bounds.MaxY)) fmt.Fprintf(&b, " %d, %d)\n", t.CreatedMs, t.UpdatedMs) b.WriteString("ON CONFLICT (id) DO NOTHING;\n\n") for i, wp := range t.Centerline { fmt.Fprintf(&b, "INSERT INTO track_waypoints (track_id, seq, x, y, heading_rad, speed_ms, curvature)\n") fmt.Fprintf(&b, "VALUES (%s, %d, %s, %s, %s, %s, %s)\n", sqlStr(t.ID), i, sqlF(wp.X), sqlF(wp.Y), sqlF(wp.HeadingRad), sqlF(wp.SpeedMs), sqlF(wp.Curvature)) b.WriteString("ON CONFLICT (track_id, seq) DO NOTHING;\n") } b.WriteString("\n") for _, tag := range t.Tags { fmt.Fprintf(&b, "INSERT INTO track_tags (track_id, tag) VALUES (%s, %s)\n", sqlStr(t.ID), sqlStr(tag)) b.WriteString("ON CONFLICT DO NOTHING;\n") } b.WriteString("\n") } for _, c := range d.Cars { fmt.Fprintf(&b, "INSERT INTO cars (id, name, owner_id, visibility, scale,\n") fmt.Fprintf(&b, " length_mm, width_mm, height_mm, weight_g,\n") fmt.Fprintf(&b, " chassis_model, chassis_material, chassis_printed, chassis_wheelbase_mm, chassis_track_mm,\n") fmt.Fprintf(&b, " motor_kind, motor_class, motor_kv, motor_power_w,\n") fmt.Fprintf(&b, " battery_voltage_v, battery_capacity_mah, battery_cells, battery_chemistry,\n") fmt.Fprintf(&b, " drive, top_speed_ms, color_hex, avatar_url, active,\n") fmt.Fprintf(&b, " created_ms, updated_ms)\n") fmt.Fprintf(&b, "VALUES (%s, %s, %s, %s, %d,\n", sqlStr(c.ID), sqlStr(c.Name), sqlStr(c.OwnerID), sqlStr(c.Visibility), c.Scale) fmt.Fprintf(&b, " %s, %s, %s, %s,\n", sqlF(c.LengthMm), sqlF(c.WidthMm), sqlF(c.HeightMm), sqlF(c.WeightG)) fmt.Fprintf(&b, " %s, %s, %s, %s, %s,\n", sqlStr(c.Chassis.Model), sqlStr(c.Chassis.Material), sqlBool(c.Chassis.Printed), sqlF(c.Chassis.WheelbaseMm), sqlF(c.Chassis.TrackMm)) fmt.Fprintf(&b, " %s, %s, %d, %s,\n", sqlStr(c.Motor.Kind), sqlStr(c.Motor.Class), c.Motor.KV, sqlF(c.Motor.PowerW)) fmt.Fprintf(&b, " %s, %d, %d, %s,\n", sqlF(c.Battery.VoltageV), c.Battery.CapacityMah, c.Battery.Cells, sqlStr(c.Battery.Chemistry)) fmt.Fprintf(&b, " %s, %s, %s, %s, %s,\n", sqlStr(c.Drive), sqlF(c.TopSpeedMs), sqlStr(c.ColorHex), sqlStr(c.AvatarURL), sqlBool(c.Active)) fmt.Fprintf(&b, " %d, %d)\n", c.CreatedMs, c.UpdatedMs) b.WriteString("ON CONFLICT (id) DO NOTHING;\n\n") } os.Stdout.WriteString(b.String()) } func sqlStr(s string) string { return "'" + strings.ReplaceAll(s, "'", "''") + "'" } func sqlBool(b bool) string { if b { return "TRUE" } return "FALSE" } func sqlF(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) }