Release v2.4.2
This commit is contained in:
+234
-1
@@ -108,6 +108,71 @@ const VERSIONED_MIGRATIONS = [
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
version: '2.4.0',
|
||||
label: 'v2.4.0 playlist schedule rules schema',
|
||||
run: async function (pool) {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS c_playlist_slide_schedule_rules (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
playlist_slide_id INT NOT NULL,
|
||||
position INT NOT NULL DEFAULT 0,
|
||||
start_datetime DATETIME NULL,
|
||||
end_datetime DATETIME NULL,
|
||||
start_time TIME NULL,
|
||||
end_time TIME NULL,
|
||||
schedule_days_json JSON 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,
|
||||
modified_by INT NULL,
|
||||
CONSTRAINT fk_playlist_slide_schedule_rules_slide FOREIGN KEY (playlist_slide_id) REFERENCES c_playlist_slides(id) ON DELETE CASCADE,
|
||||
INDEX idx_playlist_slide_schedule_rules_slide_position (playlist_slide_id, position)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
const legacyColumnNames = [
|
||||
'schedule_mode',
|
||||
'schedule_start_datetime',
|
||||
'schedule_end_datetime',
|
||||
'schedule_start_time',
|
||||
'schedule_end_time',
|
||||
'schedule_days_json',
|
||||
'schedule_rules_json'
|
||||
];
|
||||
const existingLegacyColumns = [];
|
||||
for (const columnName of legacyColumnNames) {
|
||||
if (await columnExists(pool, 'c_playlist_slides', columnName)) {
|
||||
existingLegacyColumns.push(columnName);
|
||||
}
|
||||
}
|
||||
|
||||
const selectColumns = ['id'].concat(existingLegacyColumns).join(', ');
|
||||
const [legacyRows] = await pool.query('SELECT ' + selectColumns + ' FROM c_playlist_slides');
|
||||
|
||||
for (const row of legacyRows) {
|
||||
const rules = normalizeLegacyScheduleRules(row);
|
||||
if (rules.length) {
|
||||
for (let index = 0; index < rules.length; index += 1) {
|
||||
const rule = rules[index];
|
||||
await pool.query(
|
||||
'INSERT INTO c_playlist_slide_schedule_rules (playlist_slide_id, position, start_datetime, end_datetime, start_time, end_time, schedule_days_json, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL)',
|
||||
[row.id, index, rule.start_datetime || null, rule.end_datetime || null, rule.start_time || null, rule.end_time || null, rule.schedule_days_json ? JSON.stringify(rule.schedule_days_json) : null]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_rules_json');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_mode');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_start_datetime');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_end_datetime');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_start_time');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_end_time');
|
||||
await dropColumnIfExists(pool, 'c_playlist_slides', 'schedule_days_json');
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS d_announcement_screens (
|
||||
announcement_id INT NOT NULL,
|
||||
@@ -123,7 +188,35 @@ const VERSIONED_MIGRATIONS = [
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
version: '2.4.2',
|
||||
label: 'v2.4.2 playlist canvas id schema',
|
||||
run: async function (pool) {
|
||||
await ensureColumn(pool, 'c_playlists', 'canvas_id', 'INT NULL', 'skip_unavailable_rtmp');
|
||||
await ensureForeignKey(pool, 'c_playlists', 'fk_playlists_canvas', 'canvas_id', 'c_canvas_sizes', 'id', 'SET NULL');
|
||||
await ensureColumn(pool, 'c_playlist_slides', 'disable_audio', 'TINYINT(1) NOT NULL DEFAULT 1', 'use_video_duration');
|
||||
await pool.query(`
|
||||
UPDATE c_playlists p
|
||||
JOIN (
|
||||
SELECT ps.playlist_id,
|
||||
COUNT(DISTINCT CONCAT(cs.width, 'x', cs.height)) AS signature_count,
|
||||
MIN(CONCAT(cs.width, 'x', cs.height)) AS canvas_signature
|
||||
FROM c_playlist_slides ps
|
||||
JOIN c_slides sl ON sl.id = ps.slide_id
|
||||
LEFT JOIN c_templates st ON st.id = sl.template_id
|
||||
LEFT JOIN c_canvas_sizes cs ON cs.id = st.canvas_size_id
|
||||
WHERE cs.width IS NOT NULL
|
||||
AND cs.height IS NOT NULL
|
||||
GROUP BY ps.playlist_id
|
||||
) x ON x.playlist_id = p.id
|
||||
JOIN c_canvas_sizes cs ON CONCAT(cs.width, 'x', cs.height) = x.canvas_signature
|
||||
SET p.canvas_id = cs.id
|
||||
WHERE (p.canvas_id IS NULL)
|
||||
AND x.signature_count = 1
|
||||
`);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
async function columnExists(pool, tableName, columnName) {
|
||||
@@ -148,6 +241,146 @@ async function ensureColumn(pool, tableName, columnName, columnDefinition, after
|
||||
await pool.query('ALTER TABLE ' + tableName + ' ADD COLUMN ' + columnName + ' ' + columnDefinition + afterClause);
|
||||
}
|
||||
|
||||
async function ensureForeignKey(pool, tableName, constraintName, columnName, referencedTable, referencedColumn, onDeleteAction) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT COUNT(*) AS fk_count
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND CONSTRAINT_NAME = ?`,
|
||||
[tableName, constraintName]
|
||||
);
|
||||
|
||||
if (Number(rows && rows[0] && rows[0].fk_count) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await pool.query('ALTER TABLE ' + tableName + ' ADD CONSTRAINT ' + constraintName + ' FOREIGN KEY (' + columnName + ') REFERENCES ' + referencedTable + '(' + referencedColumn + ') ON DELETE ' + onDeleteAction);
|
||||
}
|
||||
|
||||
async function dropColumnIfExists(pool, tableName, columnName) {
|
||||
if (await columnExists(pool, tableName, columnName)) {
|
||||
await pool.query('ALTER TABLE ' + tableName + ' DROP COLUMN ' + columnName);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLegacyScheduleRules(row) {
|
||||
const rules = [];
|
||||
if (!row) {
|
||||
return rules;
|
||||
}
|
||||
|
||||
if (row.schedule_rules_json) {
|
||||
try {
|
||||
const parsed = JSON.parse(row.schedule_rules_json);
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.forEach(function (rule) {
|
||||
const normalized = normalizeScheduleRule(rule);
|
||||
if (normalized) {
|
||||
rules.push(normalized);
|
||||
}
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
} catch (_error) {
|
||||
// fall through to legacy columns
|
||||
}
|
||||
}
|
||||
|
||||
const mode = String(row.schedule_mode || 'always').trim().toLowerCase();
|
||||
if (mode === 'dates' && row.schedule_start_datetime && row.schedule_end_datetime) {
|
||||
rules.push({
|
||||
start_datetime: formatMigrationDateTime(row.schedule_start_datetime),
|
||||
end_datetime: formatMigrationDateTime(row.schedule_end_datetime)
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
|
||||
if (mode === 'times' && row.schedule_start_time && row.schedule_end_time) {
|
||||
const days = normalizeMigrationDays(row.schedule_days_json);
|
||||
const rule = {
|
||||
start_time: formatMigrationTime(row.schedule_start_time),
|
||||
end_time: formatMigrationTime(row.schedule_end_time)
|
||||
};
|
||||
if (days.length) {
|
||||
rule.schedule_days_json = days;
|
||||
}
|
||||
rules.push(rule);
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
function normalizeScheduleRule(rule) {
|
||||
if (!rule || typeof rule !== 'object') {
|
||||
return null;
|
||||
}
|
||||
const normalized = {};
|
||||
if (rule.start_datetime) {
|
||||
normalized.start_datetime = String(rule.start_datetime);
|
||||
}
|
||||
if (rule.end_datetime) {
|
||||
normalized.end_datetime = String(rule.end_datetime);
|
||||
}
|
||||
if (rule.start_time) {
|
||||
normalized.start_time = String(rule.start_time).slice(0, 5);
|
||||
}
|
||||
if (rule.end_time) {
|
||||
normalized.end_time = String(rule.end_time).slice(0, 5);
|
||||
}
|
||||
if (Array.isArray(rule.schedule_days_json)) {
|
||||
normalized.schedule_days_json = rule.schedule_days_json;
|
||||
} else if (Array.isArray(rule.days)) {
|
||||
normalized.schedule_days_json = rule.days;
|
||||
}
|
||||
return Object.keys(normalized).length ? normalized : null;
|
||||
}
|
||||
|
||||
function formatMigrationDateTime(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return year + '-' + month + '-' + day + 'T' + hours + ':' + minutes;
|
||||
}
|
||||
|
||||
function formatMigrationTime(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return String(value).slice(0, 5);
|
||||
}
|
||||
|
||||
function normalizeMigrationDays(value) {
|
||||
let parsed = [];
|
||||
if (Array.isArray(value)) {
|
||||
parsed = value;
|
||||
} else if (value) {
|
||||
try {
|
||||
const raw = typeof value === 'string' ? JSON.parse(value) : value;
|
||||
parsed = Array.isArray(raw) ? raw : [];
|
||||
} catch (_error) {
|
||||
parsed = [];
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(new Set(parsed.map(function (day) {
|
||||
return Number(day);
|
||||
}).filter(function (day) {
|
||||
return Number.isInteger(day) && day >= 0 && day <= 6;
|
||||
}))).sort(function (left, right) {
|
||||
return left - right;
|
||||
});
|
||||
}
|
||||
|
||||
function compareVersions(leftVersion, rightVersion) {
|
||||
const leftParts = String(leftVersion || '0.0.0').split('.').map(function (value) {
|
||||
return Number(value) || 0;
|
||||
|
||||
Reference in New Issue
Block a user