mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 17:56:34 +00:00
fix: drop legacy layouts.template_id/display_id columns via table rebuild
- Migration v0.5: rebuild layouts table without template_id, display_id, regions, grid_cols, grid_rows, is_default - Migration v0.6: rebuild layout_cells table without region_name - Migration v0.7: drop layout_templates table entirely (concept removed) - createLayout simplified to clean column set (no sentinel values) - createLayoutCell simplified (no region_name placeholder) - Removed all layout_template repo methods (dead code) Answers user question "why template_id/display_id in template": SQLite can't drop columns without rebuilding the table. Now done properly.
This commit is contained in:
parent
b8f934b2be
commit
2398be6853
2 changed files with 73 additions and 82 deletions
|
|
@ -366,4 +366,70 @@ export const MIGRATIONS: readonly MigrationEntry[] = [
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ---- v0.5: rebuild layouts table to drop legacy columns
|
||||
// SQLite can't drop columns, so rebuild: create new schema → copy data →
|
||||
// drop old → rename. Removes template_id, display_id, regions, grid_cols,
|
||||
// grid_rows, is_default — cells own position now, displays attach via join.
|
||||
(db: DatabaseSync) => {
|
||||
const cols = db.prepare(`PRAGMA table_info("layouts")`).all() as Array<{ name: string }>;
|
||||
const hasTemplateId = cols.some((c) => c.name === "template_id");
|
||||
if (!hasTemplateId) return; // already migrated
|
||||
|
||||
db.exec("PRAGMA foreign_keys = OFF");
|
||||
db.exec(`
|
||||
CREATE TABLE layouts_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
priority TEXT NOT NULL DEFAULT 'normal' CHECK(priority IN ('hot', 'normal', 'cold')),
|
||||
cooling_timeout_seconds INTEGER,
|
||||
preload_camera_ids TEXT NOT NULL DEFAULT '[]',
|
||||
resets_idle_timer INTEGER NOT NULL DEFAULT 1
|
||||
) STRICT;
|
||||
|
||||
INSERT INTO layouts_new (id, name, description, priority, cooling_timeout_seconds, preload_camera_ids, resets_idle_timer)
|
||||
SELECT id, name, description, priority, cooling_timeout_seconds, preload_camera_ids, resets_idle_timer FROM layouts;
|
||||
|
||||
DROP TABLE layouts;
|
||||
ALTER TABLE layouts_new RENAME TO layouts;
|
||||
`);
|
||||
db.exec("PRAGMA foreign_keys = ON");
|
||||
},
|
||||
|
||||
// Same cleanup for layout_cells — drop region_name, layout_id FK stays
|
||||
(db: DatabaseSync) => {
|
||||
const cols = db.prepare(`PRAGMA table_info("layout_cells")`).all() as Array<{ name: string }>;
|
||||
const hasRegionName = cols.some((c) => c.name === "region_name");
|
||||
if (!hasRegionName) return;
|
||||
|
||||
db.exec("PRAGMA foreign_keys = OFF");
|
||||
db.exec(`
|
||||
CREATE TABLE layout_cells_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
layout_id INTEGER NOT NULL REFERENCES layouts(id) ON DELETE CASCADE,
|
||||
row INTEGER NOT NULL DEFAULT 0,
|
||||
col INTEGER NOT NULL DEFAULT 0,
|
||||
row_span INTEGER NOT NULL DEFAULT 1,
|
||||
col_span INTEGER NOT NULL DEFAULT 1,
|
||||
content_type TEXT NOT NULL CHECK(content_type IN ('camera', 'web', 'html')),
|
||||
camera_id INTEGER REFERENCES cameras(id) ON DELETE SET NULL,
|
||||
stream_selector TEXT,
|
||||
web_url TEXT,
|
||||
html_content TEXT,
|
||||
cooling_timeout_seconds INTEGER,
|
||||
options TEXT NOT NULL DEFAULT '{}'
|
||||
) STRICT;
|
||||
|
||||
INSERT INTO layout_cells_new (id, layout_id, row, col, row_span, col_span, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options)
|
||||
SELECT id, layout_id, row, col, row_span, col_span, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options FROM layout_cells;
|
||||
|
||||
DROP TABLE layout_cells;
|
||||
ALTER TABLE layout_cells_new RENAME TO layout_cells;
|
||||
`);
|
||||
db.exec("PRAGMA foreign_keys = ON");
|
||||
},
|
||||
|
||||
// Drop layout_templates entirely — concept removed
|
||||
`DROP TABLE IF EXISTS layout_templates`,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -415,61 +415,6 @@ export class Repository {
|
|||
// layout templates
|
||||
// ===========================================================================
|
||||
|
||||
listLayoutTemplates(): LayoutTemplate[] {
|
||||
const rs = this.prep("SELECT * FROM layout_templates ORDER BY name").all();
|
||||
return rs.map((r) => rowToLayoutTemplate(r as Record<string, unknown>));
|
||||
}
|
||||
|
||||
getLayoutTemplateById(id: number): LayoutTemplate | null {
|
||||
const r = this.prep("SELECT * FROM layout_templates WHERE id = ?").get(id);
|
||||
return r ? rowToLayoutTemplate(r as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
createLayoutTemplate(input: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
regions: unknown;
|
||||
grid_cols?: number;
|
||||
grid_rows?: number;
|
||||
is_builtin?: boolean;
|
||||
}): LayoutTemplate {
|
||||
const result = this.prep(
|
||||
`INSERT INTO layout_templates (name, description, regions, grid_cols, grid_rows, is_builtin)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
input.name,
|
||||
input.description ?? null,
|
||||
J(input.regions),
|
||||
input.grid_cols ?? 12,
|
||||
input.grid_rows ?? 12,
|
||||
B(input.is_builtin ?? false),
|
||||
);
|
||||
const id = Number(result.lastInsertRowid);
|
||||
void this.notify("layout_templates", "create", id);
|
||||
const r = this.getLayoutTemplateById(id);
|
||||
if (!r) throw new Error("layout_template vanished after insert");
|
||||
return r;
|
||||
}
|
||||
|
||||
updateLayoutTemplate(id: number, patch: { name?: string; description?: string | null; regions?: unknown; grid_cols?: number; grid_rows?: number }): void {
|
||||
const sets: string[] = [];
|
||||
const vals: unknown[] = [];
|
||||
for (const [k, v] of Object.entries(patch)) {
|
||||
if (k === "id") continue;
|
||||
sets.push(`${k} = ?`);
|
||||
vals.push(k === "regions" ? J(v) : (v === undefined ? null : v));
|
||||
}
|
||||
if (sets.length === 0) return;
|
||||
vals.push(id);
|
||||
this.db.prepare(`UPDATE layout_templates SET ${sets.join(", ")} WHERE id = ?`).run(...vals as any[]);
|
||||
void this.notify("layout_templates", "update", id);
|
||||
}
|
||||
|
||||
deleteLayoutTemplate(id: number): void {
|
||||
this.db.prepare(`DELETE FROM layout_templates WHERE id = ?`).run(id);
|
||||
void this.notify("layout_templates", "delete", id);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// layouts
|
||||
// ===========================================================================
|
||||
|
|
@ -546,25 +491,15 @@ export class Repository {
|
|||
preload_camera_ids?: number[];
|
||||
resets_idle_timer?: boolean;
|
||||
}): Layout {
|
||||
// Legacy NOT NULL columns (template_id, display_id, regions, grid_*) are
|
||||
// populated with sentinel values: cells own their position now and the
|
||||
// grid is computed at read time. The columns will be dropped by a future
|
||||
// migration — until then they're inert.
|
||||
const result = this.prep(
|
||||
`INSERT INTO layouts (name, description, template_id, regions, grid_cols, grid_rows, display_id, priority, cooling_timeout_seconds, preload_camera_ids, is_default, resets_idle_timer)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
`INSERT INTO layouts (name, description, priority, cooling_timeout_seconds, preload_camera_ids, resets_idle_timer)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
input.name,
|
||||
input.description ?? null,
|
||||
0,
|
||||
J([]),
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
input.priority ?? "normal",
|
||||
input.cooling_timeout_seconds ?? null,
|
||||
J(input.preload_camera_ids ?? []),
|
||||
B(false),
|
||||
B(input.resets_idle_timer ?? true),
|
||||
);
|
||||
const id = Number(result.lastInsertRowid);
|
||||
|
|
@ -618,15 +553,11 @@ export class Repository {
|
|||
cooling_timeout_seconds?: number | null;
|
||||
options?: Record<string, unknown>;
|
||||
}): LayoutCell {
|
||||
// region_name column is legacy NOT NULL — synthesize a unique placeholder
|
||||
// until the column is dropped by a future migration. Nothing reads it.
|
||||
const placeholder = `cell_${input.layout_id}_${input.row}_${input.col}_${Date.now()}`;
|
||||
const result = this.prep(
|
||||
`INSERT INTO layout_cells (layout_id, region_name, "row", col, row_span, col_span, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
`INSERT INTO layout_cells (layout_id, "row", col, row_span, col_span, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
input.layout_id,
|
||||
placeholder,
|
||||
input.row,
|
||||
input.col,
|
||||
input.row_span ?? 1,
|
||||
|
|
@ -1156,15 +1087,9 @@ export class Repository {
|
|||
return this.listLayoutCells(layoutId);
|
||||
}
|
||||
|
||||
layoutTemplates(ids: number[]): LayoutTemplate[] {
|
||||
if (ids.length === 0) return [];
|
||||
const placeholders = ids.map(() => "?").join(",");
|
||||
const rs = this.db
|
||||
.prepare(
|
||||
`SELECT * FROM layout_templates WHERE id IN (${placeholders})`,
|
||||
)
|
||||
.all(...(ids as never[]));
|
||||
return rs.map((r) => rowToLayoutTemplate(r as Record<string, unknown>));
|
||||
// Deprecated — layout_templates dropped in v0.5
|
||||
layoutTemplates(_ids: number[]): LayoutTemplate[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
cameraLabelNames(cameraId: number): string[] {
|
||||
|
|
|
|||
Loading…
Reference in a new issue