const dashboardDateFormatter = new Intl.DateTimeFormat('en-US', { month: 'short', day: '2-digit', year: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit' }); function normalizeUploadRoot(uploadDir) { return require('path').resolve(String(uploadDir || '').trim()); } function formatDashboardDate(value) { if (!value) { return ''; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return ''; } return dashboardDateFormatter.format(date); } function buildDashboardPayload(state) { return JSON.stringify({ type: 'dashboard-state', state: state }); } function readArrayField(body, keys) { const searchKeys = Array.isArray(keys) ? keys : [keys]; for (let i = 0; i < searchKeys.length; i += 1) { const key = searchKeys[i]; const value = body && Object.prototype.hasOwnProperty.call(body, key) ? body[key] : undefined; if (Array.isArray(value)) { return value.filter(function (item) { return item !== undefined && item !== null && String(item).trim() !== ''; }).map(function (item) { return String(item); }); } if (value !== undefined && value !== null && String(value).trim() !== '') { return [String(value)]; } } return []; } function parseDateTimeLocal(value) { if (!value) { return null; } const date = new Date(String(value)); return Number.isNaN(date.getTime()) ? null : date; } function parseTimeLocal(value) { const raw = String(value || '').trim(); if (!raw) { return null; } if (!/^\d{2}:\d{2}(:\d{2})?$/.test(raw)) { return null; } return raw.length === 5 ? raw + ':00' : raw; } function normalizeScheduleMode(value) { const mode = String(value || 'always'); if (mode === 'dates' || mode === 'times') { return mode; } return 'always'; } function getAuditUserId(req) { return req && req.currentUser ? Number(req.currentUser.id) : null; } function getCanvasSignature(width, height) { const normalizedWidth = Number(width); const normalizedHeight = Number(height); if (!Number.isFinite(normalizedWidth) || !Number.isFinite(normalizedHeight)) { return null; } return normalizedWidth + 'x' + normalizedHeight; } async function fetchPlaylistCanvasSignature(pool, playlistId) { const [rows] = await pool.query( `SELECT DISTINCT cs.width AS canvas_width, cs.height AS canvas_height FROM playlist_slides ps JOIN slides sl ON sl.id = ps.slide_id LEFT JOIN slide_templates st ON st.id = sl.template_id LEFT JOIN canvas_sizes cs ON cs.id = st.canvas_size_id WHERE ps.playlist_id = ? AND cs.width IS NOT NULL AND cs.height IS NOT NULL`, [playlistId] ); const signatures = Array.from(new Set(rows.map(function (row) { return getCanvasSignature(row.canvas_width, row.canvas_height); }).filter(Boolean))); if (!signatures.length) { return null; } return signatures.length === 1 ? signatures[0] : 'mismatch'; } async function fetchScreensByPlaylistId(connection, playlistId) { const [rows] = await connection.query( 'SELECT slug FROM screens WHERE playlist_id = ? AND slug IS NOT NULL', [playlistId] ); return rows.map(function (row) { return row.slug; }); } async function fetchScreensBySlideId(connection, slideId) { const [rows] = await connection.query( `SELECT DISTINCT s.slug FROM screens s JOIN playlist_slides ps ON ps.playlist_id = s.playlist_id WHERE ps.slide_id = ? AND s.slug IS NOT NULL`, [slideId] ); return rows.map(function (row) { return row.slug; }); } async function fetchScreensByTemplateId(connection, templateId) { const [rows] = await connection.query( `SELECT DISTINCT s.slug FROM screens s JOIN playlist_slides ps ON ps.playlist_id = s.playlist_id JOIN slides sl ON sl.id = ps.slide_id WHERE sl.template_id = ? AND s.slug IS NOT NULL`, [templateId] ); return rows.map(function (row) { return row.slug; }); } async function fetchOrderedPlaylistSlides(connection, playlistId) { const [rows] = await connection.query( 'SELECT id, position FROM playlist_slides WHERE playlist_id = ? ORDER BY position ASC, id ASC', [playlistId] ); return rows; } function redirectAfterSave(req, res, defaultUrl, options) { const safeOptions = options || {}; const action = String((req && req.body && req.body.action) || req.query.action || '').toLowerCase(); if (action === 'close') { return res.redirect(safeOptions.closeUrl || defaultUrl); } if (action === 'new') { return res.redirect(safeOptions.newUrl || defaultUrl); } const message = safeOptions.message || ''; if (message) { const joiner = defaultUrl.indexOf('?') === -1 ? '?' : '&'; return res.redirect(defaultUrl + joiner + 'message=' + encodeURIComponent(message)); } return res.redirect(defaultUrl); } module.exports = { normalizeUploadRoot: normalizeUploadRoot, formatDashboardDate: formatDashboardDate, buildDashboardPayload: buildDashboardPayload, readArrayField: readArrayField, parseDateTimeLocal: parseDateTimeLocal, parseTimeLocal: parseTimeLocal, normalizeScheduleMode: normalizeScheduleMode, getAuditUserId: getAuditUserId, getCanvasSignature: getCanvasSignature, fetchPlaylistCanvasSignature: fetchPlaylistCanvasSignature, fetchScreensByPlaylistId: fetchScreensByPlaylistId, fetchScreensBySlideId: fetchScreensBySlideId, fetchScreensByTemplateId: fetchScreensByTemplateId, fetchOrderedPlaylistSlides: fetchOrderedPlaylistSlides, redirectAfterSave: redirectAfterSave };