Fix schema migration audit columns

This commit is contained in:
2026-07-21 21:14:41 +01:00
parent 13e13d0d68
commit 8b479283e1
+82 -43
View File
@@ -32,6 +32,69 @@ async function addColumnIfMissing(pool, tableName, columnName, columnDefinition)
await pool.query(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${columnName}\` ${columnDefinition}`);
}
async function dropColumnIfPresent(pool, tableName, columnName) {
const [rows] = await pool.query(
`SELECT COUNT(*) AS column_count
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = ?
AND column_name = ?`,
[tableName, columnName]
);
if (!rows.length || Number(rows[0].column_count) === 0) {
return;
}
await pool.query(`ALTER TABLE \`${tableName}\` DROP COLUMN \`${columnName}\``);
}
async function addForeignKeyIfMissing(pool, tableName, columnName, constraintName, referencedTable, referencedColumn, onDeleteAction) {
const [rows] = await pool.query(
`SELECT COUNT(*) AS constraint_count
FROM information_schema.table_constraints
WHERE table_schema = DATABASE()
AND table_name = ?
AND constraint_name = ?`,
[tableName, constraintName]
);
if (rows.length && Number(rows[0].constraint_count) > 0) {
return;
}
await pool.query(
`ALTER TABLE \`${tableName}\`
ADD CONSTRAINT \`${constraintName}\`
FOREIGN KEY (\`${columnName}\`) REFERENCES \`${referencedTable}\`(\`${referencedColumn}\`)
ON DELETE ${onDeleteAction}
ON UPDATE CASCADE`
);
}
async function addUserAuditColumns(pool, tableName) {
await addColumnIfMissing(pool, tableName, 'created_by', 'INT NULL');
await addColumnIfMissing(pool, tableName, 'modified_by', 'INT NULL');
await pool.query(
`UPDATE \`${tableName}\` t
LEFT JOIN users created_user ON created_user.id = t.created_by
SET t.created_by = NULL
WHERE t.created_by IS NOT NULL
AND created_user.id IS NULL`
);
await pool.query(
`UPDATE \`${tableName}\` t
LEFT JOIN users modified_user ON modified_user.id = t.modified_by
SET t.modified_by = NULL
WHERE t.modified_by IS NOT NULL
AND modified_user.id IS NULL`
);
await addForeignKeyIfMissing(pool, tableName, 'created_by', `fk_${tableName}_created_by`, 'users', 'id', 'SET NULL');
await addForeignKeyIfMissing(pool, tableName, 'modified_by', `fk_${tableName}_modified_by`, 'users', 'id', 'SET NULL');
}
async function pruneStaleOnboardingDevices(pool) {
await pool.query(
`DELETE FROM player_onboarding_devices
@@ -54,7 +117,7 @@ async function getTableColumnNames(pool, tableName) {
}
function getPermissionKey(row) {
return String((row && (row.permission_key || row.perm_key)) || '').trim().toLowerCase();
return String((row && row.permission_key) || '').trim().toLowerCase();
}
function getLegacyPermissionTargets(permissionKey) {
@@ -81,9 +144,6 @@ function buildPermissionSeedColumns(columnNames) {
if (columnNames.has('permission_key')) {
columns.push('permission_key');
}
if (columnNames.has('perm_key')) {
columns.push('perm_key');
}
if (columnNames.has('name')) {
columns.push('name');
}
@@ -104,12 +164,10 @@ function buildPermissionSeedColumns(columnNames) {
async function backfillLegacyRbacSchema(pool) {
const permissionColumnNames = await getTableColumnNames(pool, 'permissions');
const hasPermissionKeyColumn = permissionColumnNames.has('permission_key');
const hasLegacyPermissionKeyColumn = permissionColumnNames.has('perm_key');
const [permissionRows] = await pool.query('SELECT id, permission_key, perm_key, name, section_name FROM permissions ORDER BY id ASC');
const [permissionRows] = await pool.query('SELECT id, permission_key, name, section_name FROM permissions ORDER BY id ASC');
const [rolePermissionRows] = await pool.query(
`SELECT rp.role_id, p.permission_key, p.perm_key
`SELECT rp.role_id, p.permission_key
FROM role_permissions rp
JOIN permissions p ON p.id = rp.permission_id`
);
@@ -176,9 +234,6 @@ async function backfillLegacyRbacSchema(pool) {
if (permissionColumnNames.has('permission_key')) {
seedValues.push(permission.key);
}
if (permissionColumnNames.has('perm_key')) {
seedValues.push(permission.key);
}
if (permissionColumnNames.has('name')) {
seedValues.push(permission.name);
}
@@ -205,9 +260,6 @@ async function backfillLegacyRbacSchema(pool) {
if (permissionColumnNames.has('description')) {
updateAssignments.push('description = VALUES(description)');
}
if (permissionColumnNames.has('perm_key')) {
updateAssignments.push('perm_key = VALUES(perm_key)');
}
if (permissionColumnNames.has('permission_key')) {
updateAssignments.push('permission_key = VALUES(permission_key)');
}
@@ -224,7 +276,7 @@ async function backfillLegacyRbacSchema(pool) {
await pool.query('DELETE FROM permissions WHERE id IN (?)', [legacyPermissionRowIds]);
}
const [currentPermissionRows] = await pool.query('SELECT id, permission_key, perm_key FROM permissions');
const [currentPermissionRows] = await pool.query('SELECT id, permission_key FROM permissions');
const permissionIdByKey = new Map();
for (const row of currentPermissionRows || []) {
const currentKey = getPermissionKey(row);
@@ -275,8 +327,7 @@ async function ensureSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'canvas_sizes', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'canvas_sizes');
await pool.query(`
CREATE TABLE IF NOT EXISTS playlists (
@@ -289,8 +340,7 @@ async function ensureSchema(pool) {
`);
await addColumnIfMissing(pool, 'playlists', 'fade_between_slides', 'TINYINT(1) NOT NULL DEFAULT 0');
await addColumnIfMissing(pool, 'playlists', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'playlists', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'playlists', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'playlists');
await pool.query(`
CREATE TABLE IF NOT EXISTS slide_templates (
@@ -308,8 +358,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'slide_templates', 'background_image_path', 'VARCHAR(512) NULL');
await addColumnIfMissing(pool, 'slide_templates', 'background_color', 'VARCHAR(32) NULL');
await addColumnIfMissing(pool, 'slide_templates', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'slide_templates', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'slide_templates', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'slide_templates');
await pool.query(`
INSERT IGNORE INTO canvas_sizes (name, width, height) VALUES
@@ -355,8 +404,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'slide_template_regions', 'font_family', 'VARCHAR(100) NULL');
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'slide_template_regions', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'slide_template_regions');
await pool.query(`
CREATE TABLE IF NOT EXISTS slides (
@@ -378,8 +426,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'slides', 'media_path', 'VARCHAR(512) NULL');
await addColumnIfMissing(pool, 'slides', 'media_type', 'VARCHAR(100) NULL');
await addColumnIfMissing(pool, 'slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'slides', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'slides', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'slides');
await pool.query(`
CREATE TABLE IF NOT EXISTS playlist_slides (
@@ -409,8 +456,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_end_time', 'TIME NULL');
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_days_json', 'JSON NULL');
await addColumnIfMissing(pool, 'playlist_slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'playlist_slides', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'playlist_slides', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'playlist_slides');
await pool.query(`
CREATE TABLE IF NOT EXISTS screens (
@@ -426,8 +472,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'screens', 'playlist_id', 'INT NULL');
await addColumnIfMissing(pool, 'screens', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'screens', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'screens', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'screens');
await pool.query(`
CREATE TABLE IF NOT EXISTS player_onboarding_devices (
@@ -442,6 +487,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'player_onboarding_devices', 'client_name', 'VARCHAR(255) NULL');
await addColumnIfMissing(pool, 'player_onboarding_devices', 'screen_id', 'INT NULL');
await addColumnIfMissing(pool, 'player_onboarding_devices', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addUserAuditColumns(pool, 'player_onboarding_devices');
await pool.query(`
CREATE TABLE IF NOT EXISTS users (
@@ -460,8 +506,7 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'users', 'password_salt', 'VARCHAR(64) NOT NULL');
await addColumnIfMissing(pool, 'users', 'password_iterations', 'INT NOT NULL');
await addColumnIfMissing(pool, 'users', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'users', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'users', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'users');
await pool.query(`
CREATE TABLE IF NOT EXISTS roles (
@@ -476,14 +521,12 @@ async function ensureSchema(pool) {
await addColumnIfMissing(pool, 'roles', 'role_key', 'VARCHAR(100) NULL');
await addColumnIfMissing(pool, 'roles', 'description', 'TEXT NULL');
await addColumnIfMissing(pool, 'roles', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'roles', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'roles', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'roles');
await pool.query(`
CREATE TABLE IF NOT EXISTS permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
permission_key VARCHAR(100) NOT NULL UNIQUE,
perm_key VARCHAR(100) NULL,
name VARCHAR(255) NOT NULL,
section_name VARCHAR(255) NOT NULL,
description TEXT NULL,
@@ -491,14 +534,13 @@ async function ensureSchema(pool) {
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await addColumnIfMissing(pool, 'permissions', 'perm_key', 'VARCHAR(100) NULL');
await addColumnIfMissing(pool, 'permissions', 'permission_key', 'VARCHAR(100) NULL');
await addColumnIfMissing(pool, 'permissions', 'name', 'VARCHAR(255) NULL');
await addColumnIfMissing(pool, 'permissions', 'section_name', 'VARCHAR(255) NULL');
await addColumnIfMissing(pool, 'permissions', 'description', 'TEXT NULL');
await addColumnIfMissing(pool, 'permissions', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'permissions', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'permissions', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'permissions');
await dropColumnIfPresent(pool, 'permissions', 'perm_key');
await pool.query(`
CREATE TABLE IF NOT EXISTS role_permissions (
@@ -512,8 +554,7 @@ async function ensureSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await addColumnIfMissing(pool, 'role_permissions', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'role_permissions', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'role_permissions', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'role_permissions');
await pool.query(`
CREATE TABLE IF NOT EXISTS user_roles (
@@ -527,8 +568,7 @@ async function ensureSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await addColumnIfMissing(pool, 'user_roles', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
await addColumnIfMissing(pool, 'user_roles', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'user_roles', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'user_roles');
await pool.query(`
CREATE TABLE IF NOT EXISTS auth_sessions (
@@ -540,8 +580,7 @@ async function ensureSchema(pool) {
CONSTRAINT fk_auth_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await addColumnIfMissing(pool, 'auth_sessions', 'created_by', 'INT NULL');
await addColumnIfMissing(pool, 'auth_sessions', 'modified_by', 'INT NULL');
await addUserAuditColumns(pool, 'auth_sessions');
const [userCountRows] = await pool.query('SELECT COUNT(*) AS user_count FROM users');
if (!userCountRows.length || Number(userCountRows[0].user_count) === 0) {