mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Add local-first refactor foundation
This commit is contained in:
302
internal/store/results.go
Normal file
302
internal/store/results.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UpsertDriver inserts or updates a driver by driver_number.
|
||||
func (s *Store) UpsertDriver(d Driver) error {
|
||||
if d.UpdatedAt.IsZero() {
|
||||
d.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO drivers (
|
||||
driver_number, broadcast_name, first_name, full_name, last_name,
|
||||
name_acronym, headshot_url, team_name, team_colour, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(driver_number) DO UPDATE SET
|
||||
broadcast_name = excluded.broadcast_name,
|
||||
first_name = excluded.first_name,
|
||||
full_name = excluded.full_name,
|
||||
last_name = excluded.last_name,
|
||||
name_acronym = excluded.name_acronym,
|
||||
headshot_url = excluded.headshot_url,
|
||||
team_name = excluded.team_name,
|
||||
team_colour = excluded.team_colour,
|
||||
updated_at = excluded.updated_at
|
||||
`,
|
||||
d.DriverNumber,
|
||||
nullString(d.BroadcastName),
|
||||
nullString(d.FirstName),
|
||||
d.FullName,
|
||||
nullString(d.LastName),
|
||||
nullString(d.NameAcronym),
|
||||
nullString(d.HeadshotURL),
|
||||
nullString(d.TeamName),
|
||||
nullString(d.TeamColour),
|
||||
d.UpdatedAt.Unix(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upsert driver: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver returns a driver by number.
|
||||
func (s *Store) GetDriver(driverNumber int) (Driver, error) {
|
||||
var d Driver
|
||||
var updatedAt int64
|
||||
var broadcastName, firstName, lastName, nameAcronym sql.NullString
|
||||
var headshotURL, teamName, teamColour sql.NullString
|
||||
|
||||
err := s.db.QueryRow(`
|
||||
SELECT driver_number, broadcast_name, first_name, full_name, last_name,
|
||||
name_acronym, headshot_url, team_name, team_colour, updated_at
|
||||
FROM drivers
|
||||
WHERE driver_number = ?
|
||||
`, driverNumber).Scan(
|
||||
&d.DriverNumber,
|
||||
&broadcastName,
|
||||
&firstName,
|
||||
&d.FullName,
|
||||
&lastName,
|
||||
&nameAcronym,
|
||||
&headshotURL,
|
||||
&teamName,
|
||||
&teamColour,
|
||||
&updatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return Driver{}, err
|
||||
}
|
||||
|
||||
d.BroadcastName = broadcastName.String
|
||||
d.FirstName = firstName.String
|
||||
d.LastName = lastName.String
|
||||
d.NameAcronym = nameAcronym.String
|
||||
d.HeadshotURL = headshotURL.String
|
||||
d.TeamName = teamName.String
|
||||
d.TeamColour = teamColour.String
|
||||
d.UpdatedAt = time.Unix(updatedAt, 0)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// UpsertSessionDriver links a driver to a session.
|
||||
func (s *Store) UpsertSessionDriver(sd SessionDriver) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO session_drivers (
|
||||
session_key, driver_number, meeting_key, team_name, team_colour
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_key, driver_number) DO UPDATE SET
|
||||
meeting_key = excluded.meeting_key,
|
||||
team_name = excluded.team_name,
|
||||
team_colour = excluded.team_colour
|
||||
`,
|
||||
sd.SessionKey,
|
||||
sd.DriverNumber,
|
||||
sd.MeetingKey,
|
||||
nullString(sd.TeamName),
|
||||
nullString(sd.TeamColour),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upsert session driver: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListSessionDrivers returns drivers entered for a session ordered by number.
|
||||
func (s *Store) ListSessionDrivers(sessionKey int) ([]SessionDriver, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT session_key, driver_number, meeting_key, team_name, team_colour
|
||||
FROM session_drivers
|
||||
WHERE session_key = ?
|
||||
ORDER BY driver_number ASC
|
||||
`, sessionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []SessionDriver
|
||||
for rows.Next() {
|
||||
var sd SessionDriver
|
||||
var teamName, teamColour sql.NullString
|
||||
if err := rows.Scan(
|
||||
&sd.SessionKey,
|
||||
&sd.DriverNumber,
|
||||
&sd.MeetingKey,
|
||||
&teamName,
|
||||
&teamColour,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sd.TeamName = teamName.String
|
||||
sd.TeamColour = teamColour.String
|
||||
out = append(out, sd)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// UpsertSessionResult inserts or updates a session classification row.
|
||||
func (s *Store) UpsertSessionResult(r SessionResult) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO session_results (
|
||||
session_key, driver_number, meeting_key, position, points,
|
||||
number_of_laps, duration_json, gap_to_leader_json, dnf, dns, dsq
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_key, driver_number) DO UPDATE SET
|
||||
meeting_key = excluded.meeting_key,
|
||||
position = excluded.position,
|
||||
points = excluded.points,
|
||||
number_of_laps = excluded.number_of_laps,
|
||||
duration_json = excluded.duration_json,
|
||||
gap_to_leader_json = excluded.gap_to_leader_json,
|
||||
dnf = excluded.dnf,
|
||||
dns = excluded.dns,
|
||||
dsq = excluded.dsq
|
||||
`,
|
||||
r.SessionKey,
|
||||
r.DriverNumber,
|
||||
r.MeetingKey,
|
||||
r.Position,
|
||||
r.Points,
|
||||
nullableZeroInt(r.NumberOfLaps),
|
||||
nullString(r.DurationJSON),
|
||||
nullString(r.GapToLeaderJSON),
|
||||
boolInt(r.DNF),
|
||||
boolInt(r.DNS),
|
||||
boolInt(r.DSQ),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upsert session result: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListSessionResults returns classification rows ordered by finishing position.
|
||||
func (s *Store) ListSessionResults(sessionKey int) ([]SessionResult, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT session_key, driver_number, meeting_key, position, points,
|
||||
number_of_laps, duration_json, gap_to_leader_json, dnf, dns, dsq
|
||||
FROM session_results
|
||||
WHERE session_key = ?
|
||||
ORDER BY position ASC, driver_number ASC
|
||||
`, sessionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return scanSessionResults(rows)
|
||||
}
|
||||
|
||||
// UpsertStartingGridEntry inserts or updates a starting grid row.
|
||||
func (s *Store) UpsertStartingGridEntry(g StartingGridEntry) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO starting_grid (
|
||||
session_key, driver_number, meeting_key, position, lap_duration
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_key, driver_number) DO UPDATE SET
|
||||
meeting_key = excluded.meeting_key,
|
||||
position = excluded.position,
|
||||
lap_duration = excluded.lap_duration
|
||||
`,
|
||||
g.SessionKey,
|
||||
g.DriverNumber,
|
||||
g.MeetingKey,
|
||||
g.Position,
|
||||
nullableZeroFloat(g.LapDuration),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upsert starting grid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListStartingGrid returns grid rows ordered by position.
|
||||
func (s *Store) ListStartingGrid(sessionKey int) ([]StartingGridEntry, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT session_key, driver_number, meeting_key, position, lap_duration
|
||||
FROM starting_grid
|
||||
WHERE session_key = ?
|
||||
ORDER BY position ASC, driver_number ASC
|
||||
`, sessionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []StartingGridEntry
|
||||
for rows.Next() {
|
||||
var g StartingGridEntry
|
||||
var lapDuration sql.NullFloat64
|
||||
if err := rows.Scan(
|
||||
&g.SessionKey,
|
||||
&g.DriverNumber,
|
||||
&g.MeetingKey,
|
||||
&g.Position,
|
||||
&lapDuration,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lapDuration.Valid {
|
||||
g.LapDuration = lapDuration.Float64
|
||||
}
|
||||
out = append(out, g)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func scanSessionResults(rows *sql.Rows) ([]SessionResult, error) {
|
||||
var out []SessionResult
|
||||
for rows.Next() {
|
||||
var r SessionResult
|
||||
var numberOfLaps sql.NullInt64
|
||||
var durationJSON, gapJSON sql.NullString
|
||||
var dnf, dns, dsq int
|
||||
|
||||
if err := rows.Scan(
|
||||
&r.SessionKey,
|
||||
&r.DriverNumber,
|
||||
&r.MeetingKey,
|
||||
&r.Position,
|
||||
&r.Points,
|
||||
&numberOfLaps,
|
||||
&durationJSON,
|
||||
&gapJSON,
|
||||
&dnf,
|
||||
&dns,
|
||||
&dsq,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if numberOfLaps.Valid {
|
||||
r.NumberOfLaps = int(numberOfLaps.Int64)
|
||||
}
|
||||
r.DurationJSON = durationJSON.String
|
||||
r.GapToLeaderJSON = gapJSON.String
|
||||
r.DNF = dnf != 0
|
||||
r.DNS = dns != 0
|
||||
r.DSQ = dsq != 0
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func boolInt(v bool) int {
|
||||
if v {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func nullableZeroFloat(v float64) any {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user