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); } }); };