Add multi-player and remote bridge support
This commit is contained in:
+34
-4
@@ -1,6 +1,18 @@
|
||||
// Snapshot only: keep this file aligned with the current schema state.
|
||||
async function ensureSchema(pool, options) {
|
||||
await pool.query(`
|
||||
const schemaLockName = 'pulse_signage_schema_lock';
|
||||
const schemaConnection = await pool.getConnection();
|
||||
|
||||
try {
|
||||
pool = schemaConnection;
|
||||
|
||||
const [lockRows] = await pool.query('SELECT GET_LOCK(?, 120) AS lock_acquired', [schemaLockName]);
|
||||
if (!Number(lockRows && lockRows[0] && lockRows[0].lock_acquired)) {
|
||||
throw new Error('Unable to acquire the schema migration lock.');
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS c_canvas_sizes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
@@ -116,13 +128,24 @@ async function ensureSchema(pool, options) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS d_players (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
identifier VARCHAR(128) NOT NULL UNIQUE,
|
||||
public_base_url VARCHAR(512) NULL,
|
||||
internal_base_url VARCHAR(512) NULL,
|
||||
last_seen_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS d_screens (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
slug VARCHAR(255) NOT NULL UNIQUE,
|
||||
playlist_id INT NULL,
|
||||
player_id INT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by INT NULL,
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
@@ -360,8 +383,15 @@ async function ensureSchema(pool, options) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
const { runMigrations } = require('./migrations');
|
||||
await runMigrations(pool, options);
|
||||
const { runMigrations } = require('./migrations');
|
||||
await runMigrations(pool, options);
|
||||
} finally {
|
||||
await pool.query('SELECT RELEASE_LOCK(?)', [schemaLockName]).catch(function () {
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
schemaConnection.release();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
+139
-5
@@ -19,10 +19,6 @@ const VERSIONED_MIGRATIONS = [
|
||||
`);
|
||||
}
|
||||
|
||||
// Seed the singleton player row used by the current one-player model.
|
||||
// For multi-player support, this seed and the hardcoded screen_id/player_id mapping will need to be replaced.
|
||||
await pool.query(`INSERT IGNORE INTO d_players (device_id) VALUES ('1')`);
|
||||
|
||||
// Store the player pointer on screens so we can resolve the player without needing a player-side screen_id.
|
||||
// This is the singleton-player shortcut; a multi-player model should make this relational instead of hardcoded to '1'.
|
||||
if (!(await columnExists(pool, 'd_screens', 'player_id'))) {
|
||||
@@ -212,6 +208,93 @@ const VERSIONED_MIGRATIONS = [
|
||||
run: async function (pool) {
|
||||
await ensureColumn(pool, 'c_template_regions', 'animation_json', 'JSON NULL', 'lock_ratio');
|
||||
}
|
||||
},
|
||||
{
|
||||
version: '2.6.2',
|
||||
label: 'v2.6.2 player identity schema',
|
||||
run: async function (pool) {
|
||||
if (!(await columnExists(pool, 'd_players', 'device_id'))) {
|
||||
await dropForeignKeyIfExists(pool, 'd_screens', 'player_id');
|
||||
|
||||
if (!(await columnExists(pool, 'd_players', 'identifier'))) {
|
||||
await ensureColumn(pool, 'd_players', 'identifier', 'VARCHAR(128) NOT NULL UNIQUE', 'id');
|
||||
}
|
||||
if (!(await columnExists(pool, 'd_players', 'id'))) {
|
||||
await ensureColumn(pool, 'd_players', 'id', 'INT NOT NULL AUTO_INCREMENT', null);
|
||||
await pool.query('ALTER TABLE d_players ADD PRIMARY KEY (id)');
|
||||
} else if (!(await columnIsAutoIncrement(pool, 'd_players', 'id'))) {
|
||||
await pool.query('ALTER TABLE d_players MODIFY COLUMN id INT NOT NULL AUTO_INCREMENT');
|
||||
}
|
||||
await pool.query('ALTER TABLE d_screens MODIFY COLUMN player_id INT NULL AFTER playlist_id');
|
||||
return;
|
||||
}
|
||||
|
||||
await dropForeignKeyIfExists(pool, 'd_screens', 'player_id');
|
||||
|
||||
await pool.query('DROP TABLE IF EXISTS d_players_rebuild');
|
||||
await pool.query(`
|
||||
CREATE TABLE d_players_rebuild (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
identifier VARCHAR(128) NOT NULL UNIQUE,
|
||||
public_base_url VARCHAR(512) NULL,
|
||||
internal_base_url VARCHAR(512) NULL,
|
||||
last_seen_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
INSERT INTO d_players_rebuild (identifier, public_base_url, internal_base_url, last_seen_at, created_at, modified_at)
|
||||
SELECT DISTINCT
|
||||
device_id AS identifier,
|
||||
public_base_url,
|
||||
internal_base_url,
|
||||
last_seen_at,
|
||||
created_at,
|
||||
modified_at
|
||||
FROM d_players
|
||||
ORDER BY COALESCE(created_at, modified_at, device_id), device_id
|
||||
`);
|
||||
|
||||
await pool.query('DROP TEMPORARY TABLE IF EXISTS d_player_id_map');
|
||||
await pool.query(`
|
||||
CREATE TEMPORARY TABLE d_player_id_map AS
|
||||
SELECT old_players.device_id AS old_device_id, rebuilt_players.id AS new_player_id
|
||||
FROM d_players old_players
|
||||
JOIN d_players_rebuild rebuilt_players ON rebuilt_players.identifier = old_players.device_id
|
||||
`);
|
||||
|
||||
await pool.query(
|
||||
`UPDATE d_screens s
|
||||
JOIN d_player_id_map m ON m.old_device_id = CAST(s.player_id AS CHAR)
|
||||
SET s.player_id = m.new_player_id
|
||||
WHERE s.player_id IS NOT NULL`
|
||||
);
|
||||
|
||||
await pool.query('DROP TABLE d_players');
|
||||
await pool.query('RENAME TABLE d_players_rebuild TO d_players');
|
||||
|
||||
await pool.query('ALTER TABLE d_screens MODIFY COLUMN player_id INT NULL AFTER playlist_id');
|
||||
}
|
||||
},
|
||||
{
|
||||
version: '2.6.3',
|
||||
label: 'v2.6.3 screen-player fk removal',
|
||||
run: async function (pool) {
|
||||
await dropForeignKeyIfExists(pool, 'd_screens', 'player_id');
|
||||
if (await columnExists(pool, 'd_screens', 'player_id')) {
|
||||
await pool.query('ALTER TABLE d_screens MODIFY COLUMN player_id INT NULL AFTER playlist_id');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
version: '2.6.4',
|
||||
label: 'v2.6.4 drop screen player id',
|
||||
run: async function (pool) {
|
||||
await dropForeignKeyIfExists(pool, 'd_screens', 'player_id');
|
||||
await dropColumnIfExists(pool, 'd_screens', 'player_id');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -228,6 +311,20 @@ async function columnExists(pool, tableName, columnName) {
|
||||
return Number(rows && rows[0] && rows[0].column_count) > 0;
|
||||
}
|
||||
|
||||
async function columnIsAutoIncrement(pool, tableName, columnName) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT COUNT(*) AS auto_increment_count
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
AND EXTRA LIKE '%auto_increment%'`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
|
||||
return Number(rows && rows[0] && rows[0].auto_increment_count) > 0;
|
||||
}
|
||||
|
||||
async function ensureColumn(pool, tableName, columnName, columnDefinition, afterColumn) {
|
||||
if (await columnExists(pool, tableName, columnName)) {
|
||||
return;
|
||||
@@ -289,6 +386,32 @@ async function ensureForeignKey(pool, tableName, constraintName, columnName, ref
|
||||
}
|
||||
}
|
||||
|
||||
async function dropForeignKeyIfExists(pool, tableName, columnName) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT CONSTRAINT_NAME AS constraint_name
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
AND REFERENCED_TABLE_NAME IS NOT NULL
|
||||
LIMIT 1`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
|
||||
const constraintName = String(rows && rows[0] && rows[0].constraint_name || '').trim();
|
||||
if (!constraintName) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query('ALTER TABLE ' + tableName + ' DROP FOREIGN KEY ' + constraintName);
|
||||
} catch (error) {
|
||||
if (!error || (error.code !== 'ER_CANT_DROP_FIELD_OR_KEY' && error.errno !== 1091)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function dropColumnIfExists(pool, tableName, columnName) {
|
||||
if (await columnExists(pool, tableName, columnName)) {
|
||||
try {
|
||||
@@ -443,9 +566,20 @@ async function runMigrations(pool, options) {
|
||||
// Only run migrations that are newer than the installed schema version and not beyond the app version.
|
||||
const targetVersion = String(appVersion || '0.0.0').trim();
|
||||
const currentVersion = String(options && options.currentVersion || '0.0.0').trim();
|
||||
const legacyPlayerSchemaPresent = await columnExists(pool, 'd_players', 'device_id');
|
||||
const screenPlayerColumnPresent = await columnExists(pool, 'd_screens', 'player_id');
|
||||
let effectiveCurrentVersion = currentVersion;
|
||||
|
||||
if (!legacyPlayerSchemaPresent && compareVersions(effectiveCurrentVersion, '2.1.0') < 0) {
|
||||
effectiveCurrentVersion = '2.1.0';
|
||||
}
|
||||
|
||||
if (!screenPlayerColumnPresent && compareVersions(effectiveCurrentVersion, '2.6.3') < 0) {
|
||||
effectiveCurrentVersion = '2.6.3';
|
||||
}
|
||||
|
||||
for (const migration of VERSIONED_MIGRATIONS) {
|
||||
if (compareVersions(migration.version, currentVersion) > 0 && compareVersions(migration.version, targetVersion) <= 0) {
|
||||
if (compareVersions(migration.version, effectiveCurrentVersion) > 0 && compareVersions(migration.version, targetVersion) <= 0) {
|
||||
await migration.run(pool);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user