Files
pulse-signage/src/web/routes/admin/manage.js
T
2026-07-25 02:29:19 +01:00

608 lines
28 KiB
JavaScript

module.exports = function registerAdminManageRoutes(app, deps) {
const pool = deps.pool;
const common = deps.common;
const pages = deps.pages;
const fetchOrderedPlaylistSlides = deps.fetchOrderedPlaylistSlides;
const fetchScreensByPlaylistId = deps.fetchScreensByPlaylistId;
const fetchPlaylistCanvasSignature = deps.fetchPlaylistCanvasSignature;
const getCanvasSignature = deps.getCanvasSignature;
const normalizeScheduleMode = deps.normalizeScheduleMode;
const parseDateTimeLocal = deps.parseDateTimeLocal;
const parseTimeLocal = deps.parseTimeLocal;
const readArrayField = deps.readArrayField;
const getAuditUserId = deps.getAuditUserId;
const redirectAfterSave = deps.redirectAfterSave;
const notifyPlayerScreens = deps.notifyPlayerScreens;
const broadcastDashboardState = deps.broadcastDashboardState;
const getScreenDeleteBlockMessage = deps.getScreenDeleteBlockMessage;
const getScreenConnections = deps.getScreenConnections;
const getPlaylistDeleteBlockMessage = deps.getPlaylistDeleteBlockMessage;
const forwardPlayerCommand = deps.forwardPlayerCommand;
const PLAYER_PUBLIC_BASE_URL = deps.playerPublicBaseUrl;
const requirePermission = deps.requirePermission;
async function fetchAllScreenSlugs() {
const [rows] = await pool.query('SELECT slug FROM screens ORDER BY slug ASC');
return (rows || [])
.map(function (row) {
return String(row && row.slug ? row.slug : '').trim();
})
.filter(Boolean);
}
app.post('/commands', requirePermission('dashboard.allow'), async function (req, res, next) {
try {
const command = String((req.body && req.body.command) || req.query.command || '').trim().toLowerCase();
const blackoutValue = req.body && Object.prototype.hasOwnProperty.call(req.body, 'blackout')
? req.body.blackout
: req.query.blackout;
if (!command) {
return res.status(400).json({ error: 'Command is required' });
}
if (command !== 'reload' && command !== 'blackout') {
return res.status(400).json({ error: 'Unsupported command' });
}
const slugs = await fetchAllScreenSlugs();
if (!slugs.length) {
await broadcastDashboardState();
return res.json({ ok: true, command: command, sent: 0 });
}
const payload = command === 'blackout'
? {
command: 'blackout',
blackout: blackoutValue === true || blackoutValue === 'true' || blackoutValue === '1' ? true : false
}
: 'reload';
const sentCount = await notifyPlayerScreens(slugs, payload);
await broadcastDashboardState();
return res.json({
ok: true,
command: command,
sent: sentCount,
blackout: command === 'blackout' ? Boolean(payload.blackout) : undefined
});
} catch (error) {
next(error);
}
});
app.post('/playlists', requirePermission('playlists.create'), async function (req, res, next) {
try {
const name = String(req.body.name || '').trim();
const fadeBetweenSlides = req.body.fade_between_slides ? 1 : 0;
if (!name) {
return res.status(400).send('Playlist name is required.');
}
if (await common.fetchDuplicateName(pool, 'playlists', name)) {
return res.status(400).send('A playlist with that name already exists.');
}
const actorId = getAuditUserId(req);
const [result] = await pool.query('INSERT INTO playlists (name, fade_between_slides, created_by, modified_by) VALUES (?, ?, ?, ?)', [name, fadeBetweenSlides, actorId, actorId]);
redirectAfterSave(req, res, '/playlists?edit=' + result.insertId, {
closeUrl: '/playlists',
newUrl: '/playlists/new',
message: 'Playlist created.'
});
} catch (error) {
next(error);
}
});
app.post('/playlists/:id', requirePermission('playlists.update'), async function (req, res, next) {
const connection = await pool.getConnection();
try {
const name = String(req.body.name || '').trim();
const fadeBetweenSlides = req.body.fade_between_slides ? 1 : 0;
if (!name) {
return res.status(400).send('Playlist name is required.');
}
const playlist = await common.fetchPlaylistById(connection, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
if (await common.fetchDuplicateName(pool, 'playlists', name, playlist.id)) {
return res.status(400).send('A playlist with that name already exists.');
}
const affectedScreens = await fetchScreensByPlaylistId(connection, playlist.id);
const slideIds = readArrayField(req.body, ['slide_id[]', 'slide_id']);
const durations = readArrayField(req.body, ['duration_seconds[]', 'duration_seconds']);
const scheduleModes = readArrayField(req.body, ['schedule_mode[]', 'schedule_mode']);
const scheduleStartDateTimes = readArrayField(req.body, ['schedule_start_datetime[]', 'schedule_start_datetime']);
const scheduleEndDateTimes = readArrayField(req.body, ['schedule_end_datetime[]', 'schedule_end_datetime']);
const scheduleStartTimes = readArrayField(req.body, ['schedule_start_time[]', 'schedule_start_time']);
const scheduleEndTimes = readArrayField(req.body, ['schedule_end_time[]', 'schedule_end_time']);
const scheduleDaysJsonValues = readArrayField(req.body, ['schedule_days_json[]', 'schedule_days_json']);
if (durations.length && durations.length !== slideIds.length) {
return res.status(400).send('Playlist slide data is invalid.');
}
if (scheduleModes.length && scheduleModes.length !== slideIds.length) {
return res.status(400).send('Playlist schedule data is invalid.');
}
const normalizedSlides = [];
const seenSlideIds = new Set();
for (let i = 0; i < slideIds.length; i += 1) {
const slideId = Number(slideIds[i]);
if (!Number.isInteger(slideId) || slideId <= 0) {
return res.status(400).send('Invalid slide selection.');
}
if (seenSlideIds.has(slideId)) {
return res.status(400).send('A slide can only be added to a playlist once.');
}
seenSlideIds.add(slideId);
const durationRaw = Number(durations[i]);
const durationSeconds = Number.isFinite(durationRaw) ? Math.max(1, Math.trunc(durationRaw)) : 10;
const scheduleMode = normalizeScheduleMode(scheduleModes[i]);
let scheduleStartDatetime = null;
let scheduleEndDatetime = null;
let scheduleStartTime = null;
let scheduleEndTime = null;
let scheduleDaysJson = null;
if (scheduleMode === 'dates') {
scheduleStartDatetime = parseDateTimeLocal(scheduleStartDateTimes[i]);
scheduleEndDatetime = parseDateTimeLocal(scheduleEndDateTimes[i]);
if (!scheduleStartDatetime || !scheduleEndDatetime) {
return res.status(400).send('Start and end datetimes are required for date scheduling.');
}
if (scheduleEndDatetime < scheduleStartDatetime) {
return res.status(400).send('End datetime must be after start datetime.');
}
} else if (scheduleMode === 'times') {
scheduleStartTime = parseTimeLocal(scheduleStartTimes[i]);
scheduleEndTime = parseTimeLocal(scheduleEndTimes[i]);
if (!scheduleStartTime || !scheduleEndTime) {
return res.status(400).send('Start and end times are required for time scheduling.');
}
let scheduleDays = [];
try {
const parsedDays = JSON.parse(String(scheduleDaysJsonValues[i] || '[]'));
scheduleDays = Array.isArray(parsedDays) ? parsedDays : [];
} catch (_error) {
scheduleDays = [];
}
scheduleDays = scheduleDays
.map(function (value) { return Number(value); })
.filter(function (value) { return Number.isInteger(value) && value >= 0 && value <= 6; });
if (!scheduleDays.length) {
return res.status(400).send('Select at least one day for time scheduling.');
}
scheduleDaysJson = JSON.stringify(Array.from(new Set(scheduleDays)).sort());
}
normalizedSlides.push({
slideId,
position: i,
durationSeconds,
scheduleMode,
scheduleStartDatetime,
scheduleEndDatetime,
scheduleStartTime,
scheduleEndTime,
scheduleDaysJson
});
}
if (normalizedSlides.length) {
const [slides] = await connection.query(
`SELECT sl.id, cs.width AS canvas_width, cs.height AS canvas_height
FROM slides sl
LEFT JOIN slide_templates st ON st.id = sl.template_id
LEFT JOIN canvas_sizes cs ON cs.id = st.canvas_size_id
WHERE sl.id IN (?)`,
[normalizedSlides.map(function (item) { return item.slideId; })]
);
if (slides.length !== normalizedSlides.length) {
return res.status(400).send('One or more selected slides no longer exist.');
}
const signatures = Array.from(new Set(
slides
.map(function (slide) { return getCanvasSignature(slide.canvas_width, slide.canvas_height); })
.filter(Boolean)
));
if (signatures.length > 1) {
return res.status(400).send('All playlist slides must share the same canvas size.');
}
}
const actorId = getAuditUserId(req);
await connection.beginTransaction();
await connection.query('UPDATE playlists SET name = ?, fade_between_slides = ?, modified_by = ? WHERE id = ?', [name, fadeBetweenSlides, actorId, playlist.id]);
await connection.query('DELETE FROM playlist_slides WHERE playlist_id = ?', [playlist.id]);
for (let i = 0; i < normalizedSlides.length; i += 1) {
const item = normalizedSlides[i];
await connection.query(
'INSERT INTO playlist_slides (playlist_id, slide_id, position, duration_seconds, schedule_mode, schedule_start_datetime, schedule_end_datetime, schedule_start_time, schedule_end_time, schedule_days_json, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
playlist.id,
item.slideId,
item.position,
item.durationSeconds,
item.scheduleMode,
item.scheduleStartDatetime,
item.scheduleEndDatetime,
item.scheduleStartTime,
item.scheduleEndTime,
item.scheduleDaysJson,
actorId,
actorId
]
);
}
await connection.commit();
await notifyPlayerScreens(affectedScreens, 'refresh');
await broadcastDashboardState();
redirectAfterSave(req, res, '/playlists?edit=' + playlist.id, {
closeUrl: '/playlists',
newUrl: '/playlists/new',
message: 'Playlist updated.'
});
} catch (error) {
try {
await connection.rollback();
} catch (_rollbackError) {
// Ignore rollback failures and surface the original error.
}
next(error);
} finally {
connection.release();
}
});
app.post('/playlists/:id/delete', requirePermission('playlists.delete'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const blockMessage = await getPlaylistDeleteBlockMessage(pool, playlist);
if (blockMessage) {
return res.redirect('/playlists?message=' + encodeURIComponent(blockMessage));
}
await pool.query('DELETE FROM playlists WHERE id = ?', [playlist.id]);
res.redirect('/playlists?message=' + encodeURIComponent('Playlist deleted.'));
} catch (error) {
next(error);
}
});
app.post('/playlists/:id/slides', requirePermission('playlists.update'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const slideId = Number(req.body.slide_id);
if (!slideId) {
return res.status(400).send('Slide is required.');
}
const playlistCanvasSignature = await fetchPlaylistCanvasSignature(pool, playlist.id);
if (playlistCanvasSignature === 'mismatch') {
return res.status(400).send('This playlist already contains slides with different canvas sizes.');
}
const slide = await common.fetchSlideById(pool, slideId);
if (!slide) {
return res.status(404).send('Slide not found');
}
const slideCanvasSignature = getCanvasSignature(slide.canvas_width, slide.canvas_height);
if (playlistCanvasSignature && slideCanvasSignature !== playlistCanvasSignature) {
return res.status(400).send('The slide canvas size must match the existing playlist items.');
}
const durationSeconds = Math.max(1, Number(req.body.duration_seconds || 10));
const [positionRows] = await pool.query('SELECT COALESCE(MAX(position), -1) AS max_position FROM playlist_slides WHERE playlist_id = ?', [playlist.id]);
const nextPosition = Number(positionRows[0].max_position) + 1;
const actorId = getAuditUserId(req);
await pool.query('INSERT INTO playlist_slides (playlist_id, slide_id, position, duration_seconds, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?)', [playlist.id, slideId, nextPosition, durationSeconds, actorId, actorId]);
await notifyPlayerScreens(await fetchScreensByPlaylistId(pool, playlist.id), 'refresh');
await broadcastDashboardState();
res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide added to playlist.'));
} catch (error) {
next(error);
}
});
app.post('/playlists/:id/slides/:playlistSlideId', requirePermission('playlists.update'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const playlistSlideId = Number(req.params.playlistSlideId);
const durationSeconds = Math.max(1, Number(req.body.duration_seconds || 10));
const actorId = getAuditUserId(req);
const [result] = await pool.query(
'UPDATE playlist_slides SET duration_seconds = ?, modified_by = ? WHERE id = ? AND playlist_id = ?',
[durationSeconds, actorId, playlistSlideId, playlist.id]
);
if (!result.affectedRows) {
return res.status(404).send('Playlist slide not found');
}
await notifyPlayerScreens(await fetchScreensByPlaylistId(pool, playlist.id), 'refresh');
await broadcastDashboardState();
res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide duration updated.'));
} catch (error) {
next(error);
}
});
app.post('/playlists/:id/slides/:playlistSlideId/move', requirePermission('playlists.update'), async function (req, res, next) {
const connection = await pool.getConnection();
try {
const playlist = await common.fetchPlaylistById(connection, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const playlistSlideId = Number(req.params.playlistSlideId);
const direction = String(req.body.direction || '').toLowerCase();
if (direction !== 'up' && direction !== 'down') {
return res.status(400).send('Invalid move direction.');
}
await connection.beginTransaction();
const orderedSlides = await fetchOrderedPlaylistSlides(connection, playlist.id);
const currentIndex = orderedSlides.findIndex(function (item) {
return Number(item.id) === playlistSlideId;
});
if (currentIndex === -1) {
await connection.rollback();
return res.status(404).send('Playlist slide not found');
}
const swapIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
if (swapIndex < 0 || swapIndex >= orderedSlides.length) {
await connection.rollback();
return res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide is already at the ' + (direction === 'up' ? 'top' : 'bottom') + '.'));
}
const currentSlide = orderedSlides[currentIndex];
const swapSlide = orderedSlides[swapIndex];
const actorId = getAuditUserId(req);
await connection.query('UPDATE playlist_slides SET position = ?, modified_by = ? WHERE id = ? AND playlist_id = ?', [swapSlide.position, actorId, currentSlide.id, playlist.id]);
await connection.query('UPDATE playlist_slides SET position = ?, modified_by = ? WHERE id = ? AND playlist_id = ?', [currentSlide.position, actorId, swapSlide.id, playlist.id]);
await connection.commit();
await notifyPlayerScreens(await fetchScreensByPlaylistId(connection, playlist.id), 'refresh');
await broadcastDashboardState();
res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide order updated.'));
} catch (error) {
await connection.rollback();
next(error);
} finally {
connection.release();
}
});
app.get('/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.update'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const data = await common.fetchAdminData(pool);
let playlistSlide = (data.playlistSlides || []).find(function (item) {
return item.id === Number(req.params.playlistSlideId) && item.playlist_id === playlist.id;
});
if (!playlistSlide && Number(req.params.playlistSlideId) !== 0) {
return res.status(404).send('Playlist slide not found');
}
const scheduleMode = typeof req.query.schedule_mode === 'string' && req.query.schedule_mode ? String(req.query.schedule_mode) : String(playlistSlide && playlistSlide.schedule_mode || 'always');
const scheduleStartDatetime = typeof req.query.schedule_start_datetime === 'string' ? String(req.query.schedule_start_datetime) : (playlistSlide && playlistSlide.schedule_start_datetime) || null;
const scheduleEndDatetime = typeof req.query.schedule_end_datetime === 'string' ? String(req.query.schedule_end_datetime) : (playlistSlide && playlistSlide.schedule_end_datetime) || null;
const scheduleStartTime = typeof req.query.schedule_start_time === 'string' ? String(req.query.schedule_start_time) : (playlistSlide && playlistSlide.schedule_start_time) || null;
const scheduleEndTime = typeof req.query.schedule_end_time === 'string' ? String(req.query.schedule_end_time) : (playlistSlide && playlistSlide.schedule_end_time) || null;
const scheduleDaysJson = typeof req.query.schedule_days_json === 'string' ? String(req.query.schedule_days_json) : (playlistSlide && playlistSlide.schedule_days_json) || '[]';
const scheduleDays = common.parseJsonSafe(scheduleDaysJson) || [];
playlistSlide = Object.assign({}, playlistSlide || {}, {
id: playlistSlide ? playlistSlide.id : 0,
schedule_mode: scheduleMode,
schedule_days_json: scheduleDaysJson,
schedule_days: scheduleDays,
schedule_start_datetime: scheduleStartDatetime,
schedule_end_datetime: scheduleEndDatetime,
schedule_start_time: scheduleStartTime,
schedule_end_time: scheduleEndTime
});
return res.send(pages.renderPlaylistSlideConfigPage(playlist, playlistSlide, req.query.message ? String(req.query.message) : '', req.query.row_key ? String(req.query.row_key) : '', req.currentUser));
} catch (error) {
next(error);
}
});
app.post('/playlists/:id/slides/:playlistSlideId/config', requirePermission('playlists.update'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
const rowKey = String(req.body.row_key || '').trim();
if (rowKey) {
return res.status(400).send('Schedule changes from the playlist editor are staged until you click Save changes.');
}
const playlistSlideId = Number(req.params.playlistSlideId);
let scheduleMode = normalizeScheduleMode(req.body.schedule_mode);
let scheduleStartDatetime = null;
let scheduleEndDatetime = null;
let scheduleStartTime = null;
let scheduleEndTime = null;
let scheduleDaysJson = null;
const hasDateRange = Boolean(req.body.schedule_start_datetime && req.body.schedule_end_datetime);
const hasTimeRange = Boolean(req.body.schedule_start_time && req.body.schedule_end_time);
const hasSelectedDays = Boolean(readArrayField(req.body, ['schedule_days', 'schedule_days[]']).length);
if (scheduleMode === 'dates' && !hasDateRange) {
scheduleMode = 'always';
} else if (scheduleMode === 'times' && (!hasTimeRange || !hasSelectedDays)) {
scheduleMode = 'always';
}
if (scheduleMode === 'dates') {
scheduleStartDatetime = parseDateTimeLocal(req.body.schedule_start_datetime);
scheduleEndDatetime = parseDateTimeLocal(req.body.schedule_end_datetime);
if (!scheduleStartDatetime || !scheduleEndDatetime) {
scheduleMode = 'always';
scheduleStartDatetime = null;
scheduleEndDatetime = null;
} else if (scheduleEndDatetime < scheduleStartDatetime) {
return res.status(400).send('End datetime must be after start datetime.');
}
} else if (scheduleMode === 'times') {
scheduleStartTime = parseTimeLocal(req.body.schedule_start_time);
scheduleEndTime = parseTimeLocal(req.body.schedule_end_time);
const scheduleDays = readArrayField(req.body, ['schedule_days', 'schedule_days[]']).map(function (value) {
return Number(value);
}).filter(function (value) {
return Number.isInteger(value) && value >= 0 && value <= 6;
});
if (!scheduleStartTime || !scheduleEndTime) {
scheduleMode = 'always';
scheduleStartTime = null;
scheduleEndTime = null;
scheduleDaysJson = null;
} else if (!scheduleDays.length) {
scheduleMode = 'always';
scheduleStartTime = null;
scheduleEndTime = null;
scheduleDaysJson = null;
} else {
if (scheduleEndTime < scheduleStartTime) {
return res.status(400).send('End time must be after start time.');
}
scheduleDaysJson = JSON.stringify(Array.from(new Set(scheduleDays)).sort());
}
}
const actorId = getAuditUserId(req);
const [result] = await pool.query(
'UPDATE playlist_slides SET schedule_mode = ?, schedule_start_datetime = ?, schedule_end_datetime = ?, schedule_start_time = ?, schedule_end_time = ?, schedule_days_json = ?, modified_by = ? WHERE id = ? AND playlist_id = ?',
[scheduleMode, scheduleStartDatetime, scheduleEndDatetime, scheduleStartTime, scheduleEndTime, scheduleDaysJson, actorId, playlistSlideId, playlist.id]
);
if (!result.affectedRows) {
return res.status(404).send('Playlist slide not found');
}
await notifyPlayerScreens(await fetchScreensByPlaylistId(pool, playlist.id), 'refresh');
await broadcastDashboardState();
res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide timings updated.'));
} catch (error) {
next(error);
}
});
app.post('/playlists/:id/slides/:playlistSlideId/delete', requirePermission('playlists.update'), async function (req, res, next) {
try {
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
if (!playlist) {
return res.status(404).send('Playlist not found');
}
await pool.query('DELETE FROM playlist_slides WHERE id = ? AND playlist_id = ?', [Number(req.params.playlistSlideId), playlist.id]);
await notifyPlayerScreens(await fetchScreensByPlaylistId(pool, playlist.id), 'refresh');
await broadcastDashboardState();
res.redirect('/playlists?edit=' + playlist.id + '&message=' + encodeURIComponent('Slide removed.'));
} catch (error) {
next(error);
}
});
app.get('/screens/new', requirePermission('screens.create'), async function (req, res, next) {
try {
const data = await common.fetchScreenEditData(pool);
res.send(pages.renderScreenFormPage(data, req.query.message ? String(req.query.message) : '', req.currentUser));
} catch (error) {
next(error);
}
});
app.post('/screens', requirePermission('screens.create'), async function (req, res, next) {
try {
const name = String(req.body.name || '').trim();
if (!name) {
return res.status(400).send('Screen name is required.');
}
if (await common.fetchDuplicateName(pool, 'screens', name)) {
return res.status(400).send('A screen with that name already exists.');
}
const slugInput = String(req.body.slug || '').trim();
const playlistId = req.body.playlist_id ? Number(req.body.playlist_id) : null;
const slug = await common.uniqueScreenSlug(pool, common.slugify(slugInput || name));
const actorId = getAuditUserId(req);
const [result] = await pool.query('INSERT INTO screens (name, slug, playlist_id, created_by, modified_by) VALUES (?, ?, ?, ?, ?)', [name, slug, playlistId, actorId, actorId]);
redirectAfterSave(req, res, '/screens?edit=' + result.insertId, {
closeUrl: '/screens',
newUrl: '/screens/new',
message: 'Screen created.'
});
} catch (error) {
next(error);
}
});
app.post('/screens/:id', requirePermission('screens.update'), async function (req, res, next) {
try {
const name = String(req.body.name || '').trim();
if (!name) {
return res.status(400).send('Screen name is required.');
}
const screen = await common.fetchScreenById(pool, Number(req.params.id));
if (!screen) {
return res.status(404).send('Screen not found');
}
if (await common.fetchDuplicateName(pool, 'screens', name, screen.id)) {
return res.status(400).send('A screen with that name already exists.');
}
const slugInput = String(req.body.slug || '').trim();
const playlistId = req.body.playlist_id ? Number(req.body.playlist_id) : null;
const previousPlaylistId = screen.playlist_id;
const slug = await common.uniqueScreenSlug(pool, common.slugify(slugInput || name), screen.id);
const previousSlug = String(screen.slug || '').trim();
await pool.query('UPDATE screens SET name = ?, slug = ?, playlist_id = ?, modified_by = ? WHERE id = ?', [name, slug, playlistId, getAuditUserId(req), screen.id]);
if (previousPlaylistId !== playlistId && previousSlug) {
await notifyPlayerScreens([previousSlug], 'refresh');
}
if (previousSlug && previousSlug !== slug) {
await forwardPlayerCommand(previousSlug, {
command: 'redirect',
url: `${PLAYER_PUBLIC_BASE_URL}/screen/${encodeURIComponent(slug)}`
});
}
redirectAfterSave(req, res, '/screens?edit=' + screen.id, {
closeUrl: '/screens',
newUrl: '/screens/new',
message: 'Screen updated.'
});
} catch (error) {
next(error);
}
});
app.post('/screens/:id/delete', requirePermission('screens.delete'), async function (req, res, next) {
try {
const screen = await common.fetchScreenById(pool, Number(req.params.id));
if (!screen) {
return res.status(404).send('Screen not found');
}
const blockMessage = await getScreenDeleteBlockMessage(pool, screen, getScreenConnections);
if (blockMessage) {
return res.redirect('/screens?message=' + encodeURIComponent(blockMessage));
}
await pool.query('DELETE FROM screens WHERE id = ?', [screen.id]);
res.redirect('/screens?message=' + encodeURIComponent('Screen deleted.'));
} catch (error) {
next(error);
}
});
};