505 lines
22 KiB
JavaScript
505 lines
22 KiB
JavaScript
// Announcement route registration and status wiring.
|
|
|
|
module.exports = function registerAnnouncementRoutes(app, deps) {
|
|
const pool = deps.pool;
|
|
const common = deps.common;
|
|
const pages = deps.pages;
|
|
const forwardAnnouncementRefresh = deps.forwardAnnouncementRefresh;
|
|
const getAuditUserId = deps.getAuditUserId;
|
|
const redirectAfterSave = deps.redirectAfterSave;
|
|
const requirePermission = deps.requirePermission;
|
|
const { buildPagination } = require('../../../lib/pagination');
|
|
const announcementData = require('#src/data/announcements');
|
|
const announcementIcons = require('#src/data/announcement-icons');
|
|
const { fetchAppSettings } = require('#src/data/app-settings');
|
|
const { buildDuplicateAnnouncementName, buildDuplicateAnnouncement } = require('./duplicate');
|
|
|
|
const LIST_PAGE_SIZE = 25;
|
|
const ANNOUNCEMENT_TYPES = [
|
|
{ value: 'lower-third', label: 'Lower third', icon: 'arrow-bar-down' },
|
|
{ value: 'fullscreen', label: 'Fullscreen', icon: 'arrows-fullscreen' },
|
|
{ value: 'top-banner', label: 'Top banner', icon: 'arrow-bar-up' }
|
|
];
|
|
const ANNOUNCEMENT_COLORS = [
|
|
{ value: 'primary', label: 'Primary' },
|
|
{ value: 'secondary', label: 'Secondary' },
|
|
{ value: 'success', label: 'Success' },
|
|
{ value: 'info', label: 'Info' },
|
|
{ value: 'warning', label: 'Warning' },
|
|
{ value: 'danger', label: 'Danger' },
|
|
{ value: 'dark', label: 'Dark' },
|
|
{ value: 'light', label: 'Light' }
|
|
];
|
|
const ANNOUNCEMENT_ICONS = announcementIcons.ANNOUNCEMENT_ICON_OPTIONS || [];
|
|
const ANNOUNCEMENT_ICON_CATALOG = announcementIcons.ANNOUNCEMENT_ICON_CATALOG || ANNOUNCEMENT_ICONS;
|
|
|
|
async function fetchSuggestedAnnouncementIcons() {
|
|
const settings = await fetchAppSettings(pool);
|
|
const catalogByKey = new Map(ANNOUNCEMENT_ICON_CATALOG.map(function (option) {
|
|
return [option.value, option];
|
|
}));
|
|
const configuredKeys = Array.isArray(settings['announcements.suggested_icons'])
|
|
? settings['announcements.suggested_icons']
|
|
: [];
|
|
const configuredOptions = configuredKeys.map(function (key) {
|
|
return catalogByKey.get(String(key || '').trim().toLowerCase()) || null;
|
|
}).filter(Boolean);
|
|
return configuredOptions.length ? configuredOptions : ANNOUNCEMENT_ICONS;
|
|
}
|
|
|
|
function normalizeDurationUnit(value) {
|
|
return String(value || 'minutes').trim().toLowerCase() === 'seconds' ? 'seconds' : 'minutes';
|
|
}
|
|
|
|
function computeDurationSeconds(value, unit) {
|
|
const normalizedUnit = normalizeDurationUnit(unit);
|
|
const amount = Math.max(1, Math.floor(Number(value) || 0));
|
|
return normalizedUnit === 'seconds' ? amount : amount * 60;
|
|
}
|
|
|
|
function formatDurationLabel(seconds) {
|
|
const totalSeconds = Math.max(1, Math.round(Number(seconds) || 0));
|
|
if (totalSeconds % 60 === 0) {
|
|
const minutes = totalSeconds / 60;
|
|
return minutes === 1 ? '1 minute' : `${minutes} minutes`;
|
|
}
|
|
return totalSeconds === 1 ? '1 second' : `${totalSeconds} seconds`;
|
|
}
|
|
|
|
function isAnnouncementActive(announcement) {
|
|
if (!announcement || !announcement.expires_at) {
|
|
return true;
|
|
}
|
|
|
|
const expiresAt = new Date(announcement.expires_at).getTime();
|
|
if (Number.isNaN(expiresAt)) {
|
|
return true;
|
|
}
|
|
|
|
return expiresAt > Date.now();
|
|
}
|
|
|
|
function isAnnouncementDeletionBlocked(announcement) {
|
|
return isAnnouncementActive(announcement);
|
|
}
|
|
|
|
async function fetchAllScreenSlugs() {
|
|
const [rows] = await pool.query('SELECT slug FROM d_screens ORDER BY slug ASC');
|
|
return (rows || [])
|
|
.map(function (row) {
|
|
return String(row && row.slug ? row.slug : '').trim();
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
async function fetchAnnouncementScreens() {
|
|
const [rows] = await pool.query('SELECT id, name, slug FROM d_screens ORDER BY name ASC, slug ASC');
|
|
return (rows || []).map(function (row) {
|
|
return {
|
|
id: Number(row && row.id) || null,
|
|
name: String(row && row.name ? row.name : '').trim(),
|
|
slug: String(row && row.slug ? row.slug : '').trim()
|
|
};
|
|
}).filter(function (screen) {
|
|
return Boolean(screen.id && screen.slug);
|
|
});
|
|
}
|
|
|
|
function parseSelectedScreenIds(value) {
|
|
const values = Array.isArray(value) ? value : value === undefined || value === null || value === '' ? [] : [value];
|
|
return values.map(function (screenId) {
|
|
return Number(screenId) || 0;
|
|
}).filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
}
|
|
|
|
function buildAnnouncementScreenOptions(screens, selectedScreenIds) {
|
|
const normalizedSelectedIds = (Array.isArray(selectedScreenIds) ? selectedScreenIds : [])
|
|
.map(function (screenId) {
|
|
return Number(screenId) || 0;
|
|
})
|
|
.filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
const selectedIds = new Set(normalizedSelectedIds);
|
|
|
|
return (screens || []).map(function (screen) {
|
|
return Object.assign({}, screen, {
|
|
isSelected: selectedIds.has(Number(screen && screen.id) || 0)
|
|
});
|
|
});
|
|
}
|
|
|
|
async function updateAnnouncementScreenTargets(announcementId, selectedScreenIds, actorId) {
|
|
const [screenRows] = await pool.query('SELECT id FROM d_screens ORDER BY id ASC');
|
|
const allScreenIds = (screenRows || []).map(function (row) {
|
|
return Number(row && row.id) || 0;
|
|
}).filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
const selectedIds = parseSelectedScreenIds(selectedScreenIds).filter(function (screenId) {
|
|
return allScreenIds.indexOf(screenId) !== -1;
|
|
});
|
|
const [existingRows] = await pool.query('SELECT screen_id FROM d_announcement_screens WHERE announcement_id = ?', [announcementId]);
|
|
const existingIds = new Set((existingRows || []).map(function (row) {
|
|
return Number(row && row.screen_id) || 0;
|
|
}).filter(function (screenId) {
|
|
return screenId > 0;
|
|
}));
|
|
const selectedIdSet = new Set(selectedIds);
|
|
const removedIds = (existingRows || []).map(function (row) {
|
|
return Number(row && row.screen_id) || 0;
|
|
}).filter(function (screenId) {
|
|
return screenId > 0 && !selectedIdSet.has(screenId);
|
|
});
|
|
|
|
if (removedIds.length) {
|
|
await pool.query('DELETE FROM d_announcement_screens WHERE announcement_id = ? AND screen_id IN (?)', [announcementId, removedIds]);
|
|
}
|
|
|
|
if (selectedIds.length) {
|
|
for (const screenId of selectedIds) {
|
|
await pool.query(
|
|
`UPDATE d_announcement_screens
|
|
SET modified_at = CURRENT_TIMESTAMP, modified_by = ?
|
|
WHERE announcement_id = ? AND screen_id = ?`,
|
|
[actorId || null, announcementId, screenId]
|
|
);
|
|
await pool.query(
|
|
`INSERT INTO d_announcement_screens (announcement_id, screen_id, created_by, modified_by)
|
|
SELECT ?, ?, ?, ?
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM d_announcement_screens
|
|
WHERE announcement_id = ? AND screen_id = ?
|
|
)`,
|
|
[announcementId, screenId, actorId || null, actorId || null, announcementId, screenId]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function refreshAnnouncementPlayers() {
|
|
if (typeof forwardAnnouncementRefresh !== 'function') {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const slugs = await fetchAllScreenSlugs();
|
|
if (!slugs.length) {
|
|
return;
|
|
}
|
|
await Promise.allSettled(slugs.map(function (slug) {
|
|
return forwardAnnouncementRefresh(slug);
|
|
}));
|
|
} catch (_error) {
|
|
// Keep announcement persistence independent from player refresh delivery.
|
|
}
|
|
}
|
|
|
|
async function deactivateConflictingAnnouncements(announcementId, screenIds, actorId) {
|
|
const normalizedScreenIds = (Array.isArray(screenIds) ? screenIds : [])
|
|
.map(function (screenId) {
|
|
return Number(screenId) || 0;
|
|
})
|
|
.filter(function (screenId) {
|
|
return screenId > 0;
|
|
});
|
|
|
|
if (!normalizedScreenIds.length) {
|
|
return;
|
|
}
|
|
|
|
const conflictingIds = new Set();
|
|
const [targetedRows] = await pool.query(
|
|
`SELECT DISTINCT a.id
|
|
FROM d_announcements a
|
|
JOIN d_announcement_screens restriction ON restriction.announcement_id = a.id
|
|
WHERE a.id <> ?
|
|
AND (a.expires_at IS NULL OR a.expires_at > CURRENT_TIMESTAMP)
|
|
AND restriction.screen_id IN (?)`,
|
|
[announcementId, normalizedScreenIds]
|
|
);
|
|
|
|
(targetedRows || []).forEach(function (row) {
|
|
const id = Number(row && row.id) || 0;
|
|
if (id > 0) {
|
|
conflictingIds.add(id);
|
|
}
|
|
});
|
|
|
|
if (!conflictingIds.size) {
|
|
return;
|
|
}
|
|
|
|
await pool.query(
|
|
'UPDATE d_announcements SET expires_at = CURRENT_TIMESTAMP, modified_by = ? WHERE id IN (?)',
|
|
[actorId || null, Array.from(conflictingIds)]
|
|
);
|
|
}
|
|
|
|
function buildAnnouncementFormData(announcement) {
|
|
const normalized = announcement || {};
|
|
const durationSeconds = Number(normalized.duration_seconds || 0) || 0;
|
|
const durationValue = durationSeconds > 0 && durationSeconds % 60 === 0
|
|
? durationSeconds / 60
|
|
: durationSeconds;
|
|
const durationUnit = durationSeconds > 0 && durationSeconds % 60 === 0 ? 'minutes' : 'seconds';
|
|
const durationMode = String(normalized.durationMode || '').trim() || (durationSeconds > 0 ? 'duration' : 'until_disabled');
|
|
const isActive = isAnnouncementActive(normalized);
|
|
const canDelete = Boolean(normalized.id) && !isAnnouncementDeletionBlocked(normalized);
|
|
const hasTargets = Array.isArray(normalized.screen_ids) && normalized.screen_ids.length > 0;
|
|
const deleteBlockedMessage = canDelete
|
|
? ''
|
|
: 'This announcement is currently active and cannot be deleted.';
|
|
|
|
return Object.assign({}, normalized, {
|
|
durationMode: durationMode,
|
|
durationValue: durationSeconds > 0 ? durationValue : Number(normalized.durationValue || 5) || 5,
|
|
durationUnit: durationSeconds > 0 ? durationUnit : String(normalized.durationUnit || 'minutes'),
|
|
durationLabel: durationSeconds > 0 ? formatDurationLabel(durationSeconds) : 'Until disabled',
|
|
isActive: isActive,
|
|
actionLabel: isActive ? 'Stop' : 'Play',
|
|
actionIcon: isActive ? 'bi-stop-fill' : 'bi-play-fill',
|
|
actionDisabled: !isActive && !hasTargets,
|
|
actionClassName: !isActive && !hasTargets
|
|
? 'btn-outline-info'
|
|
: (isActive ? 'btn-outline-warning' : 'btn-info'),
|
|
actionDisabledTitle: !isActive && !hasTargets ? 'Select at least one screen group to play this announcement.' : '',
|
|
actionConfirmMessage: isActive
|
|
? 'Stop this announcement on the selected screens now?'
|
|
: 'Send this announcement to the selected screens now?',
|
|
actionPath: isActive
|
|
? `/announcements/${normalized.id}/stop`
|
|
: `/announcements/${normalized.id}/play`,
|
|
canDelete: canDelete,
|
|
deleteBlockedMessage: deleteBlockedMessage
|
|
});
|
|
}
|
|
|
|
app.get('/announcements', requirePermission('announcements.read'), async function (req, res, next) {
|
|
try {
|
|
const page = Math.max(1, Math.floor(Number(req.query.page) || 1));
|
|
const search = common.getSearchQuery(req);
|
|
const sort = common.getSortQuery(req) || 'description';
|
|
const direction = common.getSortDirectionQuery(req);
|
|
const data = await common.fetchAnnouncementsPage(pool, page, LIST_PAGE_SIZE, search, sort, direction);
|
|
res.send(pages.renderAnnouncementsPage({
|
|
announcements: data.announcements || [],
|
|
pagination: buildPagination(data.totalItems, data.currentPage, 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'announcements', 'Announcement pages')
|
|
}, req.query.message ? String(req.query.message) : '', req.currentUser));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.get('/announcements/new', requirePermission('announcements.create'), async function (req, res, next) {
|
|
try {
|
|
const screens = await fetchAnnouncementScreens();
|
|
const suggestedAnnouncementIcons = await fetchSuggestedAnnouncementIcons();
|
|
const settings = await fetchAppSettings(pool);
|
|
const configuredIcon = String(settings['announcements.default_icon'] || '').trim().toLowerCase();
|
|
const defaultIcon = ANNOUNCEMENT_ICON_CATALOG.some(function (option) { return option.value === configuredIcon; })
|
|
? configuredIcon
|
|
: (suggestedAnnouncementIcons[0] && suggestedAnnouncementIcons[0].value ? suggestedAnnouncementIcons[0].value : announcementIcons.DEFAULT_ANNOUNCEMENT_ICON);
|
|
res.send(pages.renderAnnouncementAddPage(buildAnnouncementFormData({
|
|
message: '',
|
|
short_label: '',
|
|
announcement_type: 'lower-third',
|
|
color_key: 'primary',
|
|
icon_key: defaultIcon,
|
|
durationMode: 'duration',
|
|
duration_seconds: null,
|
|
durationValue: Number(settings['announcements.default_duration_value']) || 10,
|
|
durationUnit: String(settings['announcements.default_duration_unit'] || 'seconds'),
|
|
screen_ids: []
|
|
}), req.query.message ? String(req.query.message) : '', req.currentUser, {
|
|
announcementTypes: ANNOUNCEMENT_TYPES,
|
|
announcementColors: ANNOUNCEMENT_COLORS,
|
|
announcementIcons: suggestedAnnouncementIcons,
|
|
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
|
|
announcementScreens: buildAnnouncementScreenOptions(screens, [])
|
|
}));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.get('/announcements/:id/edit', requirePermission('announcements.update'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
const screens = await fetchAnnouncementScreens();
|
|
res.send(pages.renderAnnouncementEditPage(buildAnnouncementFormData(announcement), null, req.query.message ? String(req.query.message) : '', req.currentUser, {
|
|
announcementTypes: ANNOUNCEMENT_TYPES,
|
|
announcementColors: ANNOUNCEMENT_COLORS,
|
|
announcementIcons: await fetchSuggestedAnnouncementIcons(),
|
|
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
|
|
announcementScreens: buildAnnouncementScreenOptions(screens, announcement.screen_ids || [])
|
|
}));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.get('/announcements/:id/duplicate', requirePermission('announcements.read'), requirePermission('announcements.create'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
|
|
const screens = await fetchAnnouncementScreens();
|
|
let duplicateName = buildDuplicateAnnouncementName(announcement.short_label || announcement.message || 'Announcement');
|
|
let duplicateIndex = 2;
|
|
while (await common.fetchDuplicateName(pool, 'd_announcements', duplicateName, null, 'short_label')) {
|
|
duplicateName = buildDuplicateAnnouncementName(announcement.short_label || announcement.message || 'Announcement') + ' (' + duplicateIndex + ')';
|
|
duplicateIndex += 1;
|
|
}
|
|
|
|
res.send(pages.renderAnnouncementAddPage(buildAnnouncementFormData(buildDuplicateAnnouncement(announcement, duplicateName)), req.query.message ? String(req.query.message) : 'Review the copied values and save when ready.', req.currentUser, {
|
|
announcementTypes: ANNOUNCEMENT_TYPES,
|
|
announcementColors: ANNOUNCEMENT_COLORS,
|
|
announcementIcons: await fetchSuggestedAnnouncementIcons(),
|
|
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
|
|
announcementScreens: buildAnnouncementScreenOptions(screens, announcement.screen_ids || []),
|
|
messageVariant: 'info'
|
|
}));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/announcements', requirePermission('announcements.create'), async function (req, res, next) {
|
|
try {
|
|
const message = String(req.body.message || '').replace(/\r\n/g, '\n').trim();
|
|
const shortLabel = String(req.body.short_label || '').replace(/\r\n/g, ' ').trim();
|
|
const announcementType = common.normalizeAnnouncementType(req.body.announcement_type);
|
|
const colorKey = common.normalizeAnnouncementColor(req.body.color_key);
|
|
const iconKey = common.normalizeAnnouncementIcon(req.body.icon_key);
|
|
const durationMode = String(req.body.duration_mode || 'until_disabled').trim();
|
|
const durationSeconds = durationMode === 'duration'
|
|
? computeDurationSeconds(req.body.duration_value, req.body.duration_unit)
|
|
: null;
|
|
const selectedScreenIds = parseSelectedScreenIds(req.body.screen_ids);
|
|
|
|
if (!message) {
|
|
return res.status(400).send('Announcement text is required.');
|
|
}
|
|
|
|
const actorId = getAuditUserId(req);
|
|
const [result] = await pool.query(
|
|
'INSERT INTO d_announcements (message, short_label, announcement_type, color_key, icon_key, duration_seconds, expires_at, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?)',
|
|
[message, shortLabel, announcementType, colorKey, iconKey, durationSeconds, actorId, actorId]
|
|
);
|
|
await updateAnnouncementScreenTargets(result.insertId, selectedScreenIds, actorId);
|
|
redirectAfterSave(req, res, '/announcements?edit=' + result.insertId, {
|
|
closeUrl: '/announcements',
|
|
newUrl: '/announcements/new',
|
|
message: 'Announcement created.'
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/announcements/:id', requirePermission('announcements.update'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
|
|
const message = String(req.body.message || '').replace(/\r\n/g, '\n').trim();
|
|
const shortLabel = String(req.body.short_label || '').replace(/\r\n/g, ' ').trim();
|
|
const announcementType = common.normalizeAnnouncementType(req.body.announcement_type);
|
|
const colorKey = common.normalizeAnnouncementColor(req.body.color_key);
|
|
const iconKey = common.normalizeAnnouncementIcon(req.body.icon_key);
|
|
const durationMode = String(req.body.duration_mode || 'until_disabled').trim();
|
|
const durationSeconds = durationMode === 'duration'
|
|
? computeDurationSeconds(req.body.duration_value, req.body.duration_unit)
|
|
: null;
|
|
const selectedScreenIds = parseSelectedScreenIds(req.body.screen_ids);
|
|
|
|
if (!message) {
|
|
return res.status(400).send('Announcement text is required.');
|
|
}
|
|
|
|
const actorId = getAuditUserId(req);
|
|
await pool.query(
|
|
'UPDATE d_announcements SET message = ?, short_label = ?, announcement_type = ?, color_key = ?, icon_key = ?, duration_seconds = ?, modified_by = ? WHERE id = ?',
|
|
[message, shortLabel, announcementType, colorKey, iconKey, durationSeconds, actorId, announcement.id]
|
|
);
|
|
await updateAnnouncementScreenTargets(announcement.id, selectedScreenIds, actorId);
|
|
redirectAfterSave(req, res, '/announcements?edit=' + announcement.id, {
|
|
closeUrl: '/announcements',
|
|
newUrl: '/announcements/new',
|
|
message: 'Announcement updated.'
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/announcements/:id/play', requirePermission('announcements.update'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
|
|
await deactivateConflictingAnnouncements(announcement.id, announcement.screen_ids || [], getAuditUserId(req));
|
|
const durationSeconds = announcement.duration_seconds ? Number(announcement.duration_seconds) : null;
|
|
const expiresAt = durationSeconds ? new Date(Date.now() + (durationSeconds * 1000)) : null;
|
|
await pool.query('UPDATE d_announcements SET expires_at = ?, modified_by = ? WHERE id = ?', [expiresAt, getAuditUserId(req), announcement.id]);
|
|
await refreshAnnouncementPlayers();
|
|
if (req.xhr || String(req.headers.accept || '').includes('application/json')) {
|
|
return res.json({ ok: true, message: 'Announcement sent to screens.', announcementId: announcement.id });
|
|
}
|
|
|
|
res.redirect('/announcements?edit=' + announcement.id + '&message=' + encodeURIComponent('Announcement sent to screens.'));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/announcements/:id/stop', requirePermission('announcements.update'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
|
|
await pool.query('UPDATE d_announcements SET expires_at = CURRENT_TIMESTAMP, modified_by = ? WHERE id = ?', [getAuditUserId(req), announcement.id]);
|
|
await refreshAnnouncementPlayers();
|
|
if (req.xhr || String(req.headers.accept || '').includes('application/json')) {
|
|
return res.json({ ok: true, message: 'Announcement stopped.', announcementId: announcement.id });
|
|
}
|
|
|
|
res.redirect('/announcements?edit=' + announcement.id + '&message=' + encodeURIComponent('Announcement stopped.'));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/announcements/:id/delete', requirePermission('announcements.delete'), async function (req, res, next) {
|
|
try {
|
|
const announcement = await common.fetchAnnouncementById(pool, Number(req.params.id));
|
|
if (!announcement) {
|
|
return res.status(404).send('Announcement not found');
|
|
}
|
|
|
|
if (isAnnouncementDeletionBlocked(announcement)) {
|
|
return res.redirect('/announcements/' + announcement.id + '/edit?message=' + encodeURIComponent('This announcement is currently active and cannot be deleted.'));
|
|
}
|
|
|
|
await pool.query('DELETE FROM d_announcements WHERE id = ?', [announcement.id]);
|
|
await refreshAnnouncementPlayers();
|
|
res.redirect('/announcements?message=' + encodeURIComponent('Announcement deleted.'));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
}; |