192 lines
7.3 KiB
JavaScript
192 lines
7.3 KiB
JavaScript
// Announcement data access helpers.
|
|
|
|
const { fetchPagedRows } = require('./utils');
|
|
const {
|
|
ANNOUNCEMENT_ICON_KEYS,
|
|
DEFAULT_ANNOUNCEMENT_ICON,
|
|
normalizeAnnouncementIcon: normalizeAnnouncementIconFromConfig
|
|
} = require('./announcement-icons');
|
|
const { validateMaxLength } = require('./utils');
|
|
|
|
const SHORT_LABEL_MAX_LENGTH = 255;
|
|
|
|
const ANNOUNCEMENT_TYPES = ['lower-third', 'fullscreen', 'top-banner'];
|
|
const ANNOUNCEMENT_COLORS = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'dark', 'light'];
|
|
|
|
function normalizeAnnouncementType(value) {
|
|
const normalized = String(value || '').trim().toLowerCase();
|
|
if (normalized === 'center-card') {
|
|
return 'fullscreen';
|
|
}
|
|
return ANNOUNCEMENT_TYPES.includes(normalized) ? normalized : 'lower-third';
|
|
}
|
|
|
|
function normalizeAnnouncementColor(value) {
|
|
const normalized = String(value || '').trim().toLowerCase();
|
|
return ANNOUNCEMENT_COLORS.includes(normalized) ? normalized : 'primary';
|
|
}
|
|
|
|
function normalizeAnnouncementMessage(value) {
|
|
return String(value || '').replace(/\r\n/g, '\n').trim();
|
|
}
|
|
|
|
function normalizeAnnouncementShortLabel(value) {
|
|
return String(value || '').replace(/\r\n/g, ' ').trim();
|
|
}
|
|
|
|
function normalizeAnnouncementDurationSeconds(value) {
|
|
const seconds = Math.max(0, Math.round(Number(value) || 0));
|
|
return seconds > 0 ? seconds : null;
|
|
}
|
|
|
|
function normalizeAnnouncementScreenIds(value) {
|
|
if (Array.isArray(value)) {
|
|
return value.map(function (screenId) {
|
|
return Number(screenId) || 0;
|
|
}).filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
}
|
|
|
|
return String(value || '')
|
|
.split(',')
|
|
.map(function (screenId) {
|
|
return Number(screenId) || 0;
|
|
})
|
|
.filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
}
|
|
|
|
function buildAnnouncementPayload(input) {
|
|
const message = normalizeAnnouncementMessage(input && input.message);
|
|
const shortLabel = validateMaxLength(normalizeAnnouncementShortLabel(input && input.short_label), SHORT_LABEL_MAX_LENGTH, 'Announcement short description');
|
|
const announcementType = normalizeAnnouncementType(input && input.announcement_type);
|
|
const colorKey = normalizeAnnouncementColor(input && input.color_key);
|
|
const iconKey = normalizeAnnouncementIconFromConfig(input && input.icon_key);
|
|
const durationSeconds = normalizeAnnouncementDurationSeconds(input && input.duration_seconds);
|
|
|
|
return {
|
|
message,
|
|
shortLabel,
|
|
announcementType,
|
|
colorKey,
|
|
iconKey,
|
|
durationSeconds,
|
|
expiresAt: durationSeconds ? new Date(Date.now() + (durationSeconds * 1000)) : null
|
|
};
|
|
}
|
|
|
|
function normalizeAnnouncementRow(row) {
|
|
if (!row) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: Number(row.id) || null,
|
|
message: normalizeAnnouncementMessage(row.message),
|
|
short_label: normalizeAnnouncementShortLabel(row.short_label),
|
|
announcement_type: normalizeAnnouncementType(row.announcement_type),
|
|
color_key: normalizeAnnouncementColor(row.color_key),
|
|
icon_key: normalizeAnnouncementIconFromConfig(row.icon_key),
|
|
duration_seconds: row.duration_seconds === null || row.duration_seconds === undefined
|
|
? null
|
|
: Math.max(0, Math.round(Number(row.duration_seconds) || 0)),
|
|
expires_at: row.expires_at || null,
|
|
screen_target_count: row.screen_target_count === null || row.screen_target_count === undefined
|
|
? null
|
|
: Math.max(0, Math.round(Number(row.screen_target_count) || 0)),
|
|
total_screen_count: row.total_screen_count === null || row.total_screen_count === undefined
|
|
? null
|
|
: Math.max(0, Math.round(Number(row.total_screen_count) || 0)),
|
|
screen_targets_label: String(row.screen_targets_label || '').trim(),
|
|
screen_ids: normalizeAnnouncementScreenIds(row.screen_ids),
|
|
created_at: row.created_at || null,
|
|
modified_at: row.modified_at || null,
|
|
created_by: row.created_by || null,
|
|
modified_by: row.modified_by || null
|
|
};
|
|
}
|
|
|
|
async function fetchAnnouncementById(pool, id) {
|
|
const [rows] = await pool.query(`
|
|
SELECT a.id, a.message, a.short_label, a.announcement_type, a.color_key, a.icon_key, a.duration_seconds, a.expires_at, a.created_at, a.modified_at, a.created_by, a.modified_by,
|
|
GROUP_CONCAT(DISTINCT aas.screen_id ORDER BY aas.screen_id SEPARATOR ',') AS screen_ids
|
|
FROM d_announcements a
|
|
LEFT JOIN d_announcement_screens aas ON aas.announcement_id = a.id
|
|
WHERE a.id = ?
|
|
GROUP BY a.id
|
|
`, [id]);
|
|
return normalizeAnnouncementRow(rows[0] || null);
|
|
}
|
|
|
|
async function fetchAnnouncementsPage(pool, page, pageSize, searchTerm, sortKey, sortDirection) {
|
|
const paged = await fetchPagedRows(pool, {
|
|
selectSql: `SELECT a.id, a.message, a.short_label, a.announcement_type, a.color_key, a.icon_key, a.duration_seconds, a.expires_at, a.created_at, a.modified_at, a.created_by, a.modified_by,
|
|
COUNT(DISTINCT aas.screen_id) AS screen_target_count,
|
|
(SELECT COUNT(*) FROM d_screens) AS total_screen_count,
|
|
COALESCE(
|
|
GROUP_CONCAT(DISTINCT COALESCE(NULLIF(TRIM(s.name), ''), s.slug) ORDER BY COALESCE(NULLIF(TRIM(s.name), ''), s.slug) SEPARATOR ', '),
|
|
''
|
|
) AS screen_targets_label
|
|
FROM d_announcements a
|
|
LEFT JOIN d_announcement_screens aas ON aas.announcement_id = a.id
|
|
LEFT JOIN d_screens s ON s.id = aas.screen_id
|
|
GROUP BY a.id`,
|
|
countSql: 'SELECT COUNT(*) AS count FROM d_announcements',
|
|
searchColumns: ['message', 'short_label', 'announcement_type', 'color_key'],
|
|
searchTerm: searchTerm,
|
|
sortColumns: {
|
|
description: 'short_label',
|
|
message: 'message',
|
|
type: 'announcement_type',
|
|
color: 'color_key',
|
|
status: 'expires_at',
|
|
expires: 'expires_at',
|
|
created: 'created_at',
|
|
modified: 'modified_at'
|
|
},
|
|
sortKey: sortKey,
|
|
sortDirection: sortDirection,
|
|
page: page,
|
|
pageSize: pageSize
|
|
});
|
|
|
|
return Object.assign({ announcements: (paged.rows || []).map(normalizeAnnouncementRow) }, paged);
|
|
}
|
|
|
|
async function fetchActiveAnnouncement(pool, screenSlug) {
|
|
const [rows] = await pool.query(`
|
|
SELECT a.id, a.message, a.short_label, a.announcement_type, a.color_key, a.icon_key, a.duration_seconds, a.expires_at, a.created_at, a.modified_at, a.created_by, a.modified_by,
|
|
GROUP_CONCAT(DISTINCT aas.screen_id ORDER BY aas.screen_id SEPARATOR ',') AS screen_ids
|
|
FROM d_announcements a
|
|
LEFT JOIN d_announcement_screens aas ON aas.announcement_id = a.id
|
|
WHERE (a.expires_at IS NULL OR a.expires_at > CURRENT_TIMESTAMP)
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM d_announcement_screens restriction
|
|
JOIN d_screens screen ON screen.id = restriction.screen_id
|
|
WHERE restriction.announcement_id = a.id
|
|
AND screen.slug = ?
|
|
)
|
|
GROUP BY a.id
|
|
ORDER BY a.modified_at DESC, a.created_at DESC, a.id DESC
|
|
LIMIT 1
|
|
`, [screenSlug]);
|
|
return normalizeAnnouncementRow(rows[0] || null);
|
|
}
|
|
|
|
module.exports = {
|
|
ANNOUNCEMENT_TYPES,
|
|
ANNOUNCEMENT_COLORS,
|
|
ANNOUNCEMENT_ICONS: ANNOUNCEMENT_ICON_KEYS,
|
|
DEFAULT_ANNOUNCEMENT_ICON,
|
|
normalizeAnnouncementType,
|
|
normalizeAnnouncementColor,
|
|
normalizeAnnouncementIcon: normalizeAnnouncementIconFromConfig,
|
|
normalizeAnnouncementShortLabel,
|
|
buildAnnouncementPayload,
|
|
fetchAnnouncementById,
|
|
fetchAnnouncementsPage,
|
|
fetchActiveAnnouncement
|
|
}; |