diff --git a/desktop/package.json b/desktop/package.json index bbe7f8a..5bf0ae6 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@nixamp/desktop", "productName": "nixamp", - "version": "0.17.0", + "version": "0.17.1", "private": true, "description": "nixamp as a desktop app: the PWA in a window, with the CLI bundled in.", "author": "Profullstack, Inc. ", diff --git a/package.json b/package.json index c4f4e54..d51e6ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nixamp", - "version": "0.17.0", + "version": "0.17.1", "description": "It really whips the terminal's ass. A Winamp-shaped audio player for your terminal.", "license": "MIT", "type": "module", diff --git a/src/live-api.ts b/src/live-api.ts index 3477a4f..26cc0ec 100644 --- a/src/live-api.ts +++ b/src/live-api.ts @@ -628,6 +628,7 @@ export async function handleLiveApi( json(response, error.status, { error: error.message }); return true; } + console.error(`live api ${request.method} ${path}:`, error instanceof Error ? error.message : error); json(response, 500, { error: "the live event service failed" }); return true; } diff --git a/src/live-events.ts b/src/live-events.ts index 41685e9..d43d6a6 100644 --- a/src/live-events.ts +++ b/src/live-events.ts @@ -173,19 +173,22 @@ const EVENT_SCHEMA = ` ON live_events (visibility, status, starts_at, updated_at DESC); CREATE INDEX IF NOT EXISTS live_events_owner ON live_events (owner_id, updated_at DESC); - CREATE INDEX IF NOT EXISTS live_events_kind - ON live_events (kind, status, starts_at); -- The repo has no migration runner, so a table that already exists is -- brought forward here. Every statement is idempotent, and the CHECKs are -- replaced by name rather than added twice: the originals were unnamed, so -- the ones carrying the old status list are found by what they say. + -- The columns come before any index on them: nixamp.com had live_events + -- from before 0.17.0, and an index on kind ahead of ADD COLUMN kind failed + -- there while every fresh database (tests, CI) sailed through. ALTER TABLE live_events ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'talk'; ALTER TABLE live_events ADD COLUMN IF NOT EXISTS doors_open_at TIMESTAMPTZ; ALTER TABLE live_events ADD COLUMN IF NOT EXISTS ticket_price_cents INTEGER NOT NULL DEFAULT 0; ALTER TABLE live_events ADD COLUMN IF NOT EXISTS ticket_currency TEXT NOT NULL DEFAULT 'USD'; ALTER TABLE live_events ADD COLUMN IF NOT EXISTS ticket_minutes INTEGER NOT NULL DEFAULT 1440; ALTER TABLE live_events ADD COLUMN IF NOT EXISTS pay_to TEXT; + CREATE INDEX IF NOT EXISTS live_events_kind + ON live_events (kind, status, starts_at); DO $$ DECLARE stale record; @@ -511,7 +514,15 @@ export class LiveEvents { constructor(private readonly db: Queryable) {} private async ensure(): Promise { - this.ready ??= this.db.query(EVENT_SCHEMA).then(() => undefined); + // A failed schema run is not remembered: the next request tries again + // rather than answering 500 until the process restarts. + this.ready ??= this.db.query(EVENT_SCHEMA).then( + () => undefined, + (error: unknown) => { + this.ready = null; + throw error; + }, + ); await this.ready; } diff --git a/test/live-events.test.ts b/test/live-events.test.ts index 576b4f9..5fdedf2 100644 --- a/test/live-events.test.ts +++ b/test/live-events.test.ts @@ -232,3 +232,26 @@ test("an artist invitation is a role the store accepts", async () => { }); assert.equal(invitation.role, "artist"); }); + +test("the schema adds every new column before it indexes one, and a failed run is retried", async () => { + const seen: string[] = []; + let failures = 1; + const database: Queryable = { + async query(text) { + seen.push(text); + if (text.includes("CREATE TABLE") && failures-- > 0) throw new Error("column \"kind\" does not exist"); + return { rows: [] }; + }, + }; + const events = new LiveEvents(database); + await assert.rejects(events.list(), /kind/); + await events.list(); + assert.equal(seen.filter((text) => text.includes("CREATE TABLE")).length, 2, "the schema runs again after a failure"); + const schema = seen[0]!; + for (const column of ["kind", "doors_open_at", "ticket_price_cents", "ticket_currency", "ticket_minutes", "pay_to"]) { + const added = schema.indexOf(`ADD COLUMN IF NOT EXISTS ${column}`); + assert.ok(added > 0, `${column} is brought forward`); + const indexed = schema.indexOf(`CREATE INDEX IF NOT EXISTS live_events_kind`); + assert.ok(indexed > added, `${column} exists before the kind index is built`); + } +}); diff --git a/web/package.json b/web/package.json index f504b15..5d813fd 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "@nixamp/web", - "version": "0.17.0", + "version": "0.17.1", "private": true, "description": "The nixamp PWA: a browser player, and a remote for nixamp serve.", "license": "MIT",