diff --git a/server/src/plugins/service-store/index.ts b/server/src/plugins/service-store/index.ts index d90c568..58d21ec 100644 --- a/server/src/plugins/service-store/index.ts +++ b/server/src/plugins/service-store/index.ts @@ -120,13 +120,28 @@ export class Plugin extends BSBService, typeof Event this.db.exec("PRAGMA foreign_keys = ON"); this.db.exec("PRAGMA busy_timeout = 10000"); - obs.log.info("running {n} migrations", { n: MIGRATIONS.length }); - for (const entry of MIGRATIONS) { - if (typeof entry === "string") { - this.db.exec(entry); - } else { - entry(this.db); + // Track schema version via SQLite's built-in user_version PRAGMA. + // Each migration entry runs exactly once across all server boots. + const row = this.db.prepare("PRAGMA user_version").get() as { user_version: number }; + const currentVersion = row.user_version; + const targetVersion = MIGRATIONS.length; + + if (currentVersion < targetVersion) { + obs.log.info("running migrations from {from} to {to}", { + from: currentVersion, + to: targetVersion, + }); + for (let i = currentVersion; i < targetVersion; i++) { + const entry = MIGRATIONS[i]; + if (typeof entry === "string") { + this.db.exec(entry); + } else if (typeof entry === "function") { + entry(this.db); + } } + this.db.exec(`PRAGMA user_version = ${targetVersion}`); + } else { + obs.log.info("schema up to date (version {v})", { v: currentVersion }); } this._repo = new Repository(this.db, async (table, op, id) => { diff --git a/server/src/plugins/service-store/migrations.ts b/server/src/plugins/service-store/migrations.ts index 0223cd5..e338703 100644 --- a/server/src/plugins/service-store/migrations.ts +++ b/server/src/plugins/service-store/migrations.ts @@ -270,6 +270,15 @@ export const MIGRATIONS: readonly MigrationEntry[] = [ // ---- v0.2: flatten layout_templates into layouts, display→kiosk inversion --- (db: DatabaseSync) => { + // Skip entirely if v0.5 rebuild already dropped template_id (idempotent re-run) + const cols = db.prepare(`PRAGMA table_info("layouts")`).all() as Array<{ name: string }>; + const hasTemplateId = cols.some((c) => c.name === "template_id"); + if (!hasTemplateId) { + // Just ensure displays.kiosk_id exists for fresh-but-post-v0.5 DBs + addColumnIfNotExists(db, "displays", "kiosk_id", "INTEGER REFERENCES kiosks(id) ON DELETE SET NULL"); + return; + } + addColumnIfNotExists(db, "layouts", "regions", "TEXT NOT NULL DEFAULT '[]'"); addColumnIfNotExists(db, "layouts", "grid_cols", "INTEGER NOT NULL DEFAULT 1"); addColumnIfNotExists(db, "layouts", "grid_rows", "INTEGER NOT NULL DEFAULT 1");