// Shared helpers for dashboard date formatting, uploads, and query parsing. 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 fetchPlaylistCanvasId(pool, playlistId) { const [playlistRows] = await pool.query('SELECT canvas_id FROM c_playlists WHERE id = ?', [playlistId]); const storedCanvasId = Number(playlistRows && playlistRows[0] && playlistRows[0].canvas_id); if (Number.isInteger(storedCanvasId) && storedCanvasId > 0) { return storedCanvasId; } const [rows] = await pool.query( `SELECT DISTINCT st.canvas_size_id AS canvas_id 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 WHERE ps.playlist_id = ? AND st.canvas_size_id IS NOT NULL`, [playlistId] ); const canvasIds = Array.from(new Set(rows.map(function (row) { return Number(row.canvas_id); }).filter(function (value) { return Number.isInteger(value) && value > 0; }))); if (!canvasIds.length) { return null; } return canvasIds.length === 1 ? canvasIds[0] : 'mismatch'; } async function fetchScreensByPlaylistId(connection, playlistId) { const [rows] = await connection.query( 'SELECT slug FROM d_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 d_screens s JOIN c_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 d_screens s JOIN c_playlist_slides ps ON ps.playlist_id = s.playlist_id JOIN c_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 c_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.body.save_action)) || req.query.action || '').toLowerCase(); const message = safeOptions.message || ''; function withMessage(url) { if (!message) { return url; } const joiner = String(url || '').indexOf('?') === -1 ? '?' : '&'; return url + joiner + 'message=' + encodeURIComponent(message); } if (action === 'close') { return res.redirect(withMessage(safeOptions.closeUrl || defaultUrl)); } if (action === 'new') { return res.redirect(withMessage(safeOptions.newUrl || defaultUrl)); } return res.redirect(withMessage(defaultUrl)); } module.exports = { normalizeUploadRoot: normalizeUploadRoot, formatDashboardDate: formatDashboardDate, buildDashboardPayload: buildDashboardPayload, readArrayField: readArrayField, parseDateTimeLocal: parseDateTimeLocal, parseTimeLocal: parseTimeLocal, normalizeScheduleMode: normalizeScheduleMode, getAuditUserId: getAuditUserId, getCanvasSignature: getCanvasSignature, fetchPlaylistCanvasId: fetchPlaylistCanvasId, fetchScreensByPlaylistId: fetchScreensByPlaylistId, fetchScreensBySlideId: fetchScreensBySlideId, fetchScreensByTemplateId: fetchScreensByTemplateId, fetchOrderedPlaylistSlides: fetchOrderedPlaylistSlides, redirectAfterSave: redirectAfterSave };