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) {