diff --git a/frontend/src/components/RaceHubHeader.tsx b/frontend/src/components/RaceHubHeader.tsx index d3c7249..f832840 100644 --- a/frontend/src/components/RaceHubHeader.tsx +++ b/frontend/src/components/RaceHubHeader.tsx @@ -10,6 +10,7 @@ interface Props { function SourceBadge({ source }: { source: RaceHub['source'] }) { if (source === 'local') return Local if (source === 'partial') return Partial + if (source === 'cancelled') return Cancelled return No data } diff --git a/frontend/src/components/SourceBadge.tsx b/frontend/src/components/SourceBadge.tsx index fd04550..8b8169f 100644 --- a/frontend/src/components/SourceBadge.tsx +++ b/frontend/src/components/SourceBadge.tsx @@ -1,4 +1,4 @@ -type Source = 'local' | 'partial' | 'none' +type Source = 'local' | 'partial' | 'none' | 'cancelled' interface Props { source: Source @@ -11,6 +11,8 @@ export function SourceBadge({ source, label }: Props) { return {label ?? 'Local'} case 'partial': return {label ?? 'Partial'} + case 'cancelled': + return {label ?? 'Cancelled'} default: return {label ?? 'None'} } @@ -22,6 +24,8 @@ export function weekendStatusLabel(source: Source): string { return 'Full' case 'partial': return 'Partial' + case 'cancelled': + return 'Cancelled' default: return 'Missing' } diff --git a/frontend/src/lib/coverage.ts b/frontend/src/lib/coverage.ts index 63d5896..39e3dd0 100644 --- a/frontend/src/lib/coverage.ts +++ b/frontend/src/lib/coverage.ts @@ -51,6 +51,7 @@ export function countWeekendStats(weekends: (Weekend | undefined)[]) { let full = 0 let partial = 0 let missing = 0 + let cancelled = 0 for (const weekend of weekends) { if (!weekend || weekend.sessions.length === 0) { @@ -64,15 +65,19 @@ export function countWeekendStats(weekends: (Weekend | undefined)[]) { case 'partial': partial++ break + case 'cancelled': + cancelled++ + break default: missing++ } } - return { full, partial, missing, total: weekends.length } + return { full, partial, cancelled, missing, total: weekends.length } } export function sessionIconClass(session: WeekendSession): string { + if (session.source === 'cancelled') return 'si-cancelled' if (session.source === 'none') return 'si-missing' if (isSessionComplete(session.datasets)) return 'si-full' return 'si-partial' diff --git a/frontend/src/pages/DataLibraryPage.tsx b/frontend/src/pages/DataLibraryPage.tsx index 7ee1f12..7a283e5 100644 --- a/frontend/src/pages/DataLibraryPage.tsx +++ b/frontend/src/pages/DataLibraryPage.tsx @@ -137,6 +137,9 @@ export function DataLibraryPage() { {stats.partial} partial + + {stats.cancelled} cancelled + {stats.missing} missing @@ -177,6 +180,10 @@ export function DataLibraryPage() { Partial {stats.partial} +
+ Cancelled + {stats.cancelled} +
Missing {stats.missing} @@ -294,7 +301,9 @@ export function DataLibraryPage() { )} - {weekend && weekend.sessions.length > 0 + {weekend?.source === 'cancelled' + ? 'cancelled' + : weekend && weekend.sessions.length > 0 ? `${weekend.sessions.filter((s) => s.source === 'local').length}/${weekend.sessions.length} full` : '—'} diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 7274c48..0c266bc 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -361,6 +361,7 @@ a { color: inherit; text-decoration: none; } } .badge-local { background: rgba(57,199,58,.12); color: var(--green); border: 1px solid rgba(57,199,58,.25); } .badge-partial { background: rgba(255,214,0,.12); color: var(--yellow); border: 1px solid rgba(255,214,0,.25); } +.badge-cancelled { background: rgba(255,107,53,.12); color: #ff8a5c; border: 1px solid rgba(255,107,53,.28); } .badge-none { background: rgba(80,80,80,.12); color: var(--text-3); border: 1px solid var(--border); } /* ── Dataset strip ── */ @@ -1178,6 +1179,7 @@ a { color: inherit; text-decoration: none; } } .dl-banner-stats em.dl-stat-full { color: var(--green); } .dl-banner-stats em.dl-stat-partial { color: var(--yellow); } +.dl-banner-stats em.dl-stat-cancelled { color: #ff8a5c; } .dl-footer-link { display: flex; @@ -1259,6 +1261,7 @@ a { color: inherit; text-decoration: none; } } .dl-stat-full { color: var(--green); } .dl-stat-partial { color: var(--yellow); } +.dl-stat-cancelled { color: #ff8a5c; } .dl-content { display: flex; @@ -1328,6 +1331,7 @@ a { color: inherit; text-decoration: none; } } .session-icon.si-full { background: rgba(57,199,58,0.2); color: var(--green); } .session-icon.si-partial { background: rgba(255,214,0,0.2); color: var(--yellow); } +.session-icon.si-cancelled { background: rgba(255,107,53,0.18); color: #ff8a5c; } .session-icon.si-missing { background: rgba(50,50,50,0.5); color: var(--text-3); } .dl-detail-wrap { @@ -4440,4 +4444,3 @@ a { color: inherit; text-decoration: none; } background: #f82f34; transform: translateY(-1px); } - diff --git a/frontend/src/test/coverage.test.ts b/frontend/src/test/coverage.test.ts index a5900e4..3d8d271 100644 --- a/frontend/src/test/coverage.test.ts +++ b/frontend/src/test/coverage.test.ts @@ -59,11 +59,18 @@ describe('coverage helpers', () => { meeting: {} as Weekend['meeting'], sessions: [{ session: {} as Weekend['sessions'][0]['session'], source: 'partial', datasets: {} }], } - expect(countWeekendStats([local, partial, undefined])).toEqual({ + const cancelled: Weekend = { + source: 'cancelled', + meeting_key: 3, + meeting: {} as Weekend['meeting'], + sessions: [{ session: {} as Weekend['sessions'][0]['session'], source: 'cancelled', datasets: {} }], + } + expect(countWeekendStats([local, partial, cancelled, undefined])).toEqual({ full: 1, partial: 1, + cancelled: 1, missing: 1, - total: 3, + total: 4, }) }) }) diff --git a/frontend/src/types.ts b/frontend/src/types.ts index c3d5222..aef8b32 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -16,6 +16,7 @@ export interface Meeting { date_start: string date_end: string year: number + is_cancelled?: boolean } export interface Session { @@ -26,6 +27,7 @@ export interface Session { date_start: string date_end: string gmt_offset: string + is_cancelled?: boolean } export interface Driver { @@ -73,7 +75,7 @@ export interface EnrichedGrid { } export interface RaceHub { - source: 'local' | 'partial' | 'none' + source: 'local' | 'partial' | 'none' | 'cancelled' session_key: number datasets: Record meeting?: Meeting @@ -158,12 +160,12 @@ export interface Lap { export interface WeekendSession { session: Session - source: 'local' | 'partial' | 'none' + source: 'local' | 'partial' | 'none' | 'cancelled' datasets: Record } export interface Weekend { - source: 'local' | 'partial' | 'none' + source: 'local' | 'partial' | 'none' | 'cancelled' meeting_key: number meeting: Meeting sessions: WeekendSession[] diff --git a/internal/ingest/ingest.go b/internal/ingest/ingest.go index c93f39f..7780c66 100644 --- a/internal/ingest/ingest.go +++ b/internal/ingest/ingest.go @@ -430,6 +430,27 @@ func (s *Service) ingestSessionDatasets(sess models.Session) (Summary, error) { coverage = make(map[string]store.CoverageEntry) } + if sess.IsCancelled { + s.opts.Progress.Step("session %d (%s) is cancelled; skipping Race Hub datasets", sessionKey, sess.SessionName) + if !s.opts.DryRun { + for _, dataset := range []string{ + "drivers", + "session_result", + "starting_grid", + "stints", + "pit_stops", + "positions", + "race_control", + "weather", + "laps", + } { + _ = s.store.UpsertCoverage(sessionKey, dataset, "skipped", 0, "session cancelled") + } + } + summary.Status = statusForCancelled(s.opts.DryRun) + return summary, nil + } + // 1. Ingest drivers if cov, ok := coverage["drivers"]; ok && cov.Status == "complete" && !s.opts.Force { s.opts.Progress.Step("drivers already complete for session %d, skipping", sessionKey) @@ -919,6 +940,13 @@ func statusForErrors(dryRun bool, errs []string) string { return "completed" } +func statusForCancelled(dryRun bool) string { + if dryRun { + return "dry_run" + } + return "cancelled" +} + type fetchFunc[T any] func() (FetchResult, T, error) func fetchWithRetry[T any](s *Service, fn fetchFunc[T]) (FetchResult, T, error) { diff --git a/internal/ingest/openf1.go b/internal/ingest/openf1.go index 98362a9..3e649e1 100644 --- a/internal/ingest/openf1.go +++ b/internal/ingest/openf1.go @@ -325,6 +325,7 @@ func meetingToStore(m models.Meeting) store.Meeting { DateStart: m.DateStart, DateEnd: m.DateEnd, Year: m.Year, + IsCancelled: m.IsCancelled, } } @@ -338,6 +339,7 @@ func sessionToStore(s models.Session) store.Session { DateStart: s.DateStart, DateEnd: s.DateEnd, GMTOffset: s.GMTOffset, + IsCancelled: s.IsCancelled, } } diff --git a/internal/models/types.go b/internal/models/types.go index ad90d5b..f1fddbb 100644 --- a/internal/models/types.go +++ b/internal/models/types.go @@ -24,6 +24,8 @@ type Meeting struct { DateStart string `json:"date_start"` DateEnd string `json:"date_end"` Year int `json:"year"` + + IsCancelled bool `json:"is_cancelled"` } type Session struct { @@ -38,6 +40,8 @@ type Session struct { DateStart string `json:"date_start"` DateEnd string `json:"date_end"` GMTOffset string `json:"gmt_offset"` + + IsCancelled bool `json:"is_cancelled"` } // TyreCompound represents the type of tyre compound used. @@ -169,7 +173,7 @@ type Pit struct { LaneDuration float64 `json:"lane_duration"` // pit lane time (entry to exit) LapNumber int `json:"lap_number"` MeetingKey int `json:"meeting_key"` - PitDuration float64 `json:"pit_duration"` // deprecated, use StopDuration + PitDuration float64 `json:"pit_duration"` // deprecated, use StopDuration SessionKey int `json:"session_key"` StopDuration float64 `json:"stop_duration"` // stationary time only } @@ -186,7 +190,7 @@ type Interval struct { Date string `json:"date"` DriverNumber int `json:"driver_number"` GapToLeader *float64 `json:"gap_to_leader"` // null when leading - Interval *float64 `json:"interval"` // null when leading + Interval *float64 `json:"interval"` // null when leading MeetingKey int `json:"meeting_key"` SessionKey int `json:"session_key"` } @@ -228,7 +232,7 @@ type Weather struct { } type CarData struct { - Brake int `json:"brake"` // 0-100 + Brake int `json:"brake"` // 0-100 Date string `json:"date"` DriverNumber int `json:"driver_number"` DRS int `json:"drs"` // 0=off, 8=eligible, 10=open diff --git a/internal/query/convert.go b/internal/query/convert.go index 56ddfa9..e84e190 100644 --- a/internal/query/convert.go +++ b/internal/query/convert.go @@ -23,6 +23,8 @@ func meetingToModel(m store.Meeting) models.Meeting { DateStart: m.DateStart, DateEnd: m.DateEnd, Year: m.Year, + + IsCancelled: m.IsCancelled, } } @@ -36,6 +38,8 @@ func sessionToModel(s store.Session) models.Session { DateStart: s.DateStart, DateEnd: s.DateEnd, GMTOffset: s.GMTOffset, + + IsCancelled: s.IsCancelled, } } diff --git a/internal/query/datasets.go b/internal/query/datasets.go index e4bc96d..bfe075e 100644 --- a/internal/query/datasets.go +++ b/internal/query/datasets.go @@ -55,3 +55,23 @@ func datasetsFromCounts(meetingAvailable, sessionAvailable bool, counts store.Se } return ds } + +func cancelledDatasets() map[string]DatasetInfo { + ds := emptyDatasetMap() + ds["meeting"] = availableLocal(1) + ds["session"] = availableLocal(1) + for _, key := range []string{ + "drivers", + "results", + "starting_grid", + "stints", + "pit_stops", + "positions", + "race_control", + "weather", + "laps", + } { + ds[key] = skippedNA() + } + return ds +} diff --git a/internal/query/metadata.go b/internal/query/metadata.go index 735fa50..aec1e5d 100644 --- a/internal/query/metadata.go +++ b/internal/query/metadata.go @@ -10,9 +10,10 @@ const ( DataSourceNone = "none" DataSourceOpenF1 = "openf1" - ResponseSourceLocal = "local" - ResponseSourceNone = "none" - ResponseSourcePartial = "partial" + ResponseSourceLocal = "local" + ResponseSourceNone = "none" + ResponseSourcePartial = "partial" + ResponseSourceCancelled = "cancelled" ) // DatasetInfo describes availability of a single dataset. diff --git a/internal/query/navigation.go b/internal/query/navigation.go index b3697db..b2994bf 100644 --- a/internal/query/navigation.go +++ b/internal/query/navigation.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/AmanTahiliani/box-box/internal/models" + "github.com/AmanTahiliani/box-box/internal/store" ) // ErrMeetingNotFound is returned when a meeting is not in the local store. @@ -65,21 +66,35 @@ func (s *Service) GetWeekend(meetingKey int) (Weekend, error) { } datasets := datasetsFromCounts(true, true, counts) sessionModel := sessionToModel(sess) + if sess.IsCancelled { + datasets = cancelledDatasets() + } if datasets["starting_grid"].Status == DatasetStatusMissing && !isGridExpected(sessionModel.SessionType, sessionModel.SessionName) { datasets["starting_grid"] = skippedNA() } out.Sessions = append(out.Sessions, WeekendSession{ Session: sessionModel, - Source: responseSource(datasets), + Source: sessionSource(sess, datasets), Datasets: datasets, }) } - out.Source = weekendSource(out.Sessions) + if meeting.IsCancelled { + out.Source = ResponseSourceCancelled + } else { + out.Source = weekendSource(out.Sessions) + } out.DefaultSessionKey = pickDefaultSession(out.Sessions) return out, nil } +func sessionSource(sess store.Session, datasets map[string]DatasetInfo) string { + if sess.IsCancelled { + return ResponseSourceCancelled + } + return responseSource(datasets) +} + func weekendSource(sessions []WeekendSession) string { if len(sessions) == 0 { return ResponseSourceNone diff --git a/internal/query/racehub.go b/internal/query/racehub.go index 688a5ae..1924599 100644 --- a/internal/query/racehub.go +++ b/internal/query/racehub.go @@ -106,6 +106,12 @@ func (s *Service) GetRaceHub(sessionKey int) (RaceHub, error) { hub.Datasets["meeting"] = availableLocal(1) } + if sess.IsCancelled || (hub.Meeting != nil && hub.Meeting.IsCancelled) { + hub.Source = ResponseSourceCancelled + hub.Datasets = cancelledDatasets() + return hub, nil + } + driverLinks, err := s.store.ListSessionDrivers(sessionKey) if err != nil { return RaceHub{}, err diff --git a/internal/store/coverage_test.go b/internal/store/coverage_test.go index 0d35d74..d620f9d 100644 --- a/internal/store/coverage_test.go +++ b/internal/store/coverage_test.go @@ -12,8 +12,8 @@ func TestCoverageCRUD(t *testing.T) { if err != nil { t.Fatalf("SchemaVersion() error = %v", err) } - if version != 6 { - t.Fatalf("SchemaVersion() = %d, want 6", version) + if version != 7 { + t.Fatalf("SchemaVersion() = %d, want 7", version) } // Verify session_coverage table exists diff --git a/internal/store/meetings.go b/internal/store/meetings.go index 2e32dbd..a705a20 100644 --- a/internal/store/meetings.go +++ b/internal/store/meetings.go @@ -16,8 +16,8 @@ func (s *Store) UpsertMeeting(m Meeting) error { INSERT INTO meetings ( meeting_key, meeting_name, meeting_official_name, location, country_code, country_name, circuit_key, circuit_short_name, - gmt_offset, date_start, date_end, year, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + gmt_offset, date_start, date_end, year, is_cancelled, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(meeting_key) DO UPDATE SET meeting_name = excluded.meeting_name, meeting_official_name = excluded.meeting_official_name, @@ -30,6 +30,7 @@ func (s *Store) UpsertMeeting(m Meeting) error { date_start = excluded.date_start, date_end = excluded.date_end, year = excluded.year, + is_cancelled = excluded.is_cancelled, updated_at = excluded.updated_at `, m.MeetingKey, @@ -44,6 +45,7 @@ func (s *Store) UpsertMeeting(m Meeting) error { nullString(m.DateStart), nullString(m.DateEnd), m.Year, + boolInt(m.IsCancelled), m.UpdatedAt.Unix(), ) if err != nil { @@ -56,6 +58,7 @@ func (s *Store) UpsertMeeting(m Meeting) error { func (s *Store) GetMeeting(meetingKey int) (Meeting, error) { var m Meeting var updatedAt int64 + var isCancelled int var officialName, location, countryCode, countryName sql.NullString var circuitKey sql.NullInt64 var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString @@ -63,7 +66,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) { err := s.db.QueryRow(` SELECT meeting_key, meeting_name, meeting_official_name, location, country_code, country_name, circuit_key, circuit_short_name, - gmt_offset, date_start, date_end, year, updated_at + gmt_offset, date_start, date_end, year, is_cancelled, updated_at FROM meetings WHERE meeting_key = ? `, meetingKey).Scan( @@ -79,6 +82,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) { &dateStart, &dateEnd, &m.Year, + &isCancelled, &updatedAt, ) if err != nil { @@ -96,6 +100,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) { m.GMTOffset = gmtOffset.String m.DateStart = dateStart.String m.DateEnd = dateEnd.String + m.IsCancelled = isCancelled != 0 m.UpdatedAt = time.Unix(updatedAt, 0) return m, nil } @@ -129,7 +134,7 @@ func (s *Store) ListMeetingsByYear(year int) ([]Meeting, error) { rows, err := s.db.Query(` SELECT meeting_key, meeting_name, meeting_official_name, location, country_code, country_name, circuit_key, circuit_short_name, - gmt_offset, date_start, date_end, year, updated_at + gmt_offset, date_start, date_end, year, is_cancelled, updated_at FROM meetings WHERE year = ? ORDER BY date_start ASC, meeting_key ASC @@ -151,8 +156,8 @@ func (s *Store) UpsertSession(sess Session) error { _, err := s.db.Exec(` INSERT INTO sessions ( session_key, meeting_key, session_name, session_type, - circuit_key, date_start, date_end, gmt_offset, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(session_key) DO UPDATE SET meeting_key = excluded.meeting_key, session_name = excluded.session_name, @@ -161,6 +166,7 @@ func (s *Store) UpsertSession(sess Session) error { date_start = excluded.date_start, date_end = excluded.date_end, gmt_offset = excluded.gmt_offset, + is_cancelled = excluded.is_cancelled, updated_at = excluded.updated_at `, sess.SessionKey, @@ -171,6 +177,7 @@ func (s *Store) UpsertSession(sess Session) error { nullString(sess.DateStart), nullString(sess.DateEnd), nullString(sess.GMTOffset), + boolInt(sess.IsCancelled), sess.UpdatedAt.Unix(), ) if err != nil { @@ -183,12 +190,13 @@ func (s *Store) UpsertSession(sess Session) error { func (s *Store) GetSession(sessionKey int) (Session, error) { var sess Session var updatedAt int64 + var isCancelled int var circuitKey sql.NullInt64 var dateStart, dateEnd, gmtOffset sql.NullString err := s.db.QueryRow(` SELECT session_key, meeting_key, session_name, session_type, - circuit_key, date_start, date_end, gmt_offset, updated_at + circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at FROM sessions WHERE session_key = ? `, sessionKey).Scan( @@ -200,6 +208,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) { &dateStart, &dateEnd, &gmtOffset, + &isCancelled, &updatedAt, ) if err != nil { @@ -212,6 +221,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) { sess.DateStart = dateStart.String sess.DateEnd = dateEnd.String sess.GMTOffset = gmtOffset.String + sess.IsCancelled = isCancelled != 0 sess.UpdatedAt = time.Unix(updatedAt, 0) return sess, nil } @@ -220,7 +230,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) { func (s *Store) ListSessionsByMeeting(meetingKey int) ([]Session, error) { rows, err := s.db.Query(` SELECT session_key, meeting_key, session_name, session_type, - circuit_key, date_start, date_end, gmt_offset, updated_at + circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at FROM sessions WHERE meeting_key = ? ORDER BY date_start ASC, session_key ASC @@ -238,6 +248,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) { for rows.Next() { var m Meeting var updatedAt int64 + var isCancelled int var officialName, location, countryCode, countryName sql.NullString var circuitKey sql.NullInt64 var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString @@ -255,6 +266,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) { &dateStart, &dateEnd, &m.Year, + &isCancelled, &updatedAt, ); err != nil { return nil, err @@ -271,6 +283,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) { m.GMTOffset = gmtOffset.String m.DateStart = dateStart.String m.DateEnd = dateEnd.String + m.IsCancelled = isCancelled != 0 m.UpdatedAt = time.Unix(updatedAt, 0) out = append(out, m) } @@ -282,6 +295,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) { for rows.Next() { var sess Session var updatedAt int64 + var isCancelled int var circuitKey sql.NullInt64 var dateStart, dateEnd, gmtOffset sql.NullString @@ -294,6 +308,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) { &dateStart, &dateEnd, &gmtOffset, + &isCancelled, &updatedAt, ); err != nil { return nil, err @@ -305,6 +320,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) { sess.DateStart = dateStart.String sess.DateEnd = dateEnd.String sess.GMTOffset = gmtOffset.String + sess.IsCancelled = isCancelled != 0 sess.UpdatedAt = time.Unix(updatedAt, 0) out = append(out, sess) } diff --git a/internal/store/migrations/007_cancelled_schedule.sql b/internal/store/migrations/007_cancelled_schedule.sql new file mode 100644 index 0000000..8d5521d --- /dev/null +++ b/internal/store/migrations/007_cancelled_schedule.sql @@ -0,0 +1,3 @@ +ALTER TABLE meetings ADD COLUMN is_cancelled INTEGER NOT NULL DEFAULT 0; + +ALTER TABLE sessions ADD COLUMN is_cancelled INTEGER NOT NULL DEFAULT 0; diff --git a/internal/store/models.go b/internal/store/models.go index 9528929..a597d57 100644 --- a/internal/store/models.go +++ b/internal/store/models.go @@ -68,6 +68,7 @@ type Meeting struct { DateStart string DateEnd string Year int + IsCancelled bool UpdatedAt time.Time } @@ -81,6 +82,7 @@ type Session struct { DateStart string DateEnd string GMTOffset string + IsCancelled bool UpdatedAt time.Time } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 7af0906..c3daa0b 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -28,8 +28,8 @@ func TestOpenAppliesMigrations(t *testing.T) { if err != nil { t.Fatalf("SchemaVersion() error = %v", err) } - if version != 6 { - t.Fatalf("SchemaVersion() = %d, want 6", version) + if version != 7 { + t.Fatalf("SchemaVersion() = %d, want 7", version) } tables := []string{ @@ -105,6 +105,18 @@ func TestMigrationsAreIdempotent(t *testing.T) { if count != 1 { t.Fatalf("schema_migrations v5 count = %d, want 1", count) } + if err := s.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version = 6`).Scan(&count); err != nil { + t.Fatalf("count schema_migrations v6: %v", err) + } + if count != 1 { + t.Fatalf("schema_migrations v6 count = %d, want 1", count) + } + if err := s.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version = 7`).Scan(&count); err != nil { + t.Fatalf("count schema_migrations v7: %v", err) + } + if count != 1 { + t.Fatalf("schema_migrations v7 count = %d, want 1", count) + } } func TestRawPayloadInsertAndRead(t *testing.T) {