Release 2.8.0
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { loadFontLibrary } = require('#src/web/lib/media/font-library');
|
||||
const { fetchAppSettings } = require('#src/data/app-settings');
|
||||
const { PERMISSION_DENIED_MESSAGE } = require('#src/rbac');
|
||||
|
||||
module.exports = function registerContentRoutes(app, deps) {
|
||||
@@ -19,6 +20,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
const removeUnusedUploadFiles = deps.removeUnusedUploadFiles;
|
||||
const syncPlaylistUploadsOnChange = deps.syncPlaylistUploadsOnChange;
|
||||
const getAuditUserId = deps.getAuditUserId;
|
||||
const recordRequestAuditEvent = deps.recordRequestAuditEvent;
|
||||
const redirectAfterSave = deps.redirectAfterSave;
|
||||
const notifyPlayerScreens = deps.notifyPlayerScreens;
|
||||
const broadcastDashboardState = deps.broadcastDashboardState;
|
||||
@@ -42,6 +44,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
const rssData = await common.fetchRssFeedsData(pool);
|
||||
const apiData = typeof common.fetchApiSourcesData === 'function' ? await common.fetchApiSourcesData(pool) : { apiSources: [] };
|
||||
const timetableData = typeof common.fetchTimetablesData === 'function' ? await common.fetchTimetablesData(pool) : { timetableGroups: [] };
|
||||
const appSettings = await fetchAppSettings(pool);
|
||||
const apiSources = Array.isArray(apiData.apiSources) ? apiData.apiSources.map(function (source) {
|
||||
return Object.assign({}, source, {
|
||||
responseJson: typeof common.parseJsonSafe === 'function' ? common.parseJsonSafe(source.last_response_json) : null
|
||||
@@ -60,7 +63,13 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
rssFeeds: rssFeeds,
|
||||
apiSources: apiSources,
|
||||
timetableGroups: timetableData.timetableGroups || [],
|
||||
fontLibrary: loadFontLibrary(deps.uploadDir)
|
||||
fontLibrary: loadFontLibrary(deps.uploadDir),
|
||||
uploadLimits: {
|
||||
imageMaxBytes: Number(appSettings['uploads.image_max_bytes']) || IMAGE_UPLOAD_MAX_BYTES,
|
||||
videoMaxBytes: Number(appSettings['uploads.video_max_bytes']) || VIDEO_UPLOAD_MAX_BYTES,
|
||||
wysiwygImageMaxBytes: Number(appSettings['uploads.wysiwyg_image_max_bytes']) || WYSIWYG_IMAGE_UPLOAD_MAX_BYTES,
|
||||
allowedMimeTypes: Array.isArray(appSettings['uploads.allowed_mime_types']) ? appSettings['uploads.allowed_mime_types'] : []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,20 +132,24 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getUploadedFileLimitBytes(file, uploadContext) {
|
||||
function getUploadedFileLimitBytes(file, uploadContext, settings) {
|
||||
if (String(uploadContext || '').trim().toLowerCase() === 'wysiwyg') {
|
||||
return WYSIWYG_IMAGE_UPLOAD_MAX_BYTES;
|
||||
return Number(settings && settings['uploads.wysiwyg_image_max_bytes']) || WYSIWYG_IMAGE_UPLOAD_MAX_BYTES;
|
||||
}
|
||||
|
||||
return getUploadedFileMediaType(file, uploadContext) === 'video' ? VIDEO_UPLOAD_MAX_BYTES : IMAGE_UPLOAD_MAX_BYTES;
|
||||
return getUploadedFileMediaType(file, uploadContext) === 'video'
|
||||
? Number(settings && settings['uploads.video_max_bytes']) || VIDEO_UPLOAD_MAX_BYTES
|
||||
: Number(settings && settings['uploads.image_max_bytes']) || IMAGE_UPLOAD_MAX_BYTES;
|
||||
}
|
||||
|
||||
function getUploadedFileLimitLabel(file, uploadContext) {
|
||||
function getUploadedFileLimitLabel(file, uploadContext, settings) {
|
||||
if (String(uploadContext || '').trim().toLowerCase() === 'wysiwyg') {
|
||||
return '10 MB';
|
||||
const limitMb = getUploadedFileLimitBytes(file, uploadContext, settings) / 1024 / 1024;
|
||||
return (Number.isInteger(limitMb) ? String(limitMb) : limitMb.toFixed(2).replace(/0+$/, '').replace(/\.$/, '')) + ' MB';
|
||||
}
|
||||
|
||||
return getUploadedFileMediaType(file, uploadContext) === 'video' ? '1 GB' : '100 MB';
|
||||
const limitMb = Math.round(getUploadedFileLimitBytes(file, uploadContext, settings) / 1024 / 1024);
|
||||
return limitMb >= 1024 && limitMb % 1024 === 0 ? (limitMb / 1024) + ' GB' : limitMb + ' MB';
|
||||
}
|
||||
|
||||
async function removeUploadedFile(file) {
|
||||
@@ -154,8 +167,18 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
|
||||
async function validateUploadedFiles(files, uploadContext) {
|
||||
const list = Array.isArray(files) ? files.filter(Boolean) : [];
|
||||
const settings = await fetchAppSettings(pool);
|
||||
for (let i = 0; i < list.length; i += 1) {
|
||||
const file = list[i];
|
||||
const fileMimeType = String(file && file.mimetype || '').trim().toLowerCase();
|
||||
const allowedMimeTypes = Array.isArray(settings['uploads.allowed_mime_types']) ? settings['uploads.allowed_mime_types'] : [];
|
||||
if (allowedMimeTypes.indexOf(fileMimeType) === -1) {
|
||||
await removeUploadedFile(file);
|
||||
const error = new Error('This upload type is disabled in Media Uploads settings.');
|
||||
error.statusCode = 400;
|
||||
error.expose = true;
|
||||
throw error;
|
||||
}
|
||||
const mediaType = getUploadedFileMediaType(file, uploadContext);
|
||||
if (!mediaType) {
|
||||
await removeUploadedFile(file);
|
||||
@@ -165,11 +188,11 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (Number(file.size || 0) > getUploadedFileLimitBytes(file, uploadContext)) {
|
||||
if (Number(file.size || 0) > getUploadedFileLimitBytes(file, uploadContext, settings)) {
|
||||
await removeUploadedFile(file);
|
||||
const error = new Error(String(uploadContext || '').trim().toLowerCase() === 'wysiwyg'
|
||||
? 'Image must be 2 MB or smaller. Larger images should use the dedicated Image region.'
|
||||
: 'File must be ' + getUploadedFileLimitLabel(file, uploadContext) + ' or smaller.');
|
||||
? 'Image must be ' + getUploadedFileLimitLabel(file, uploadContext, settings) + ' or smaller. Larger images should use the dedicated Image region.'
|
||||
: 'File must be ' + getUploadedFileLimitLabel(file, uploadContext, settings) + ' or smaller.');
|
||||
error.statusCode = 400;
|
||||
error.expose = true;
|
||||
throw error;
|
||||
@@ -447,6 +470,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
queueSlideThumbnailRefresh(result.insertId, null).catch(function (error) {
|
||||
console.warn('Unable to queue slide thumbnail refresh:', error);
|
||||
});
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'slides', eventType: 'slide.created', actorUserId: actorId, targetType: 'slide', targetId: result.insertId, targetLabel: payload.title });
|
||||
redirectAfterSave(req, res, '/slides/' + result.insertId + '/edit', {
|
||||
closeUrl: '/slides',
|
||||
newUrl: '/slides/new',
|
||||
@@ -488,6 +512,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
screenSlideCounts: screenSlideCounts
|
||||
});
|
||||
await broadcastDashboardState();
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'slides', eventType: 'slide.updated', actorUserId: actorId, targetType: 'slide', targetId: slide.id, targetLabel: payload.title });
|
||||
queueSlideThumbnailRefresh(slide.id, slide.thumbnail_path).catch(function (error) {
|
||||
console.warn('Unable to queue slide thumbnail refresh:', error);
|
||||
});
|
||||
@@ -526,6 +551,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
screenSlideCounts: screenSlideCounts
|
||||
});
|
||||
await broadcastDashboardState();
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'slides', eventType: 'slide.deleted', actorUserId: getAuditUserId(req), targetType: 'slide', targetId: slide.id, targetLabel: slide.title });
|
||||
res.redirect('/slides?message=' + encodeURIComponent('Slide deleted.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -584,6 +610,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
localUploadDir: deps.uploadDir,
|
||||
nextUploadRefs: collectUploadReferencesFromPayload(payload)
|
||||
});
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'templates', eventType: 'template.created', actorUserId: actorId, targetType: 'template', targetId: result.insertId, targetLabel: payload.name });
|
||||
redirectAfterSave(req, res, '/templates/' + result.insertId + '/edit', {
|
||||
closeUrl: '/templates',
|
||||
newUrl: '/templates/new',
|
||||
@@ -654,6 +681,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
});
|
||||
}
|
||||
await notifyPlayerScreens(affectedScreens, 'refresh');
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'templates', eventType: 'template.updated', actorUserId: actorId, targetType: 'template', targetId: template.id, targetLabel: payload.name });
|
||||
redirectAfterSave(req, res, '/templates/' + template.id + '/edit', {
|
||||
closeUrl: '/templates',
|
||||
newUrl: '/templates/new',
|
||||
@@ -686,6 +714,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
previousUploadRefs: uploadRefs
|
||||
});
|
||||
await notifyPlayerScreens(affectedScreens, 'refresh');
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'templates', eventType: 'template.deleted', actorUserId: getAuditUserId(req), targetType: 'template', targetId: template.id, targetLabel: template.name });
|
||||
res.redirect('/templates?message=' + encodeURIComponent('Template deleted.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -743,6 +772,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
}
|
||||
const actorId = getAuditUserId(req);
|
||||
const [result] = await pool.query('INSERT INTO c_canvas_sizes (name, width, height, created_by, modified_by) VALUES (?, ?, ?, ?, ?)', [payload.name, payload.width, payload.height, actorId, actorId]);
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'canvas-sizes', eventType: 'canvas-size.created', actorUserId: actorId, targetType: 'canvas-size', targetId: result.insertId, targetLabel: payload.name, details: { width: payload.width, height: payload.height } });
|
||||
redirectAfterSave(req, res, '/canvas-sizes/' + result.insertId + '/edit', {
|
||||
closeUrl: '/canvas-sizes',
|
||||
newUrl: '/canvas-sizes/new',
|
||||
@@ -784,6 +814,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
return res.status(400).send('That canvas size already exists.');
|
||||
}
|
||||
await pool.query('UPDATE c_canvas_sizes SET name = ?, width = ?, height = ?, modified_by = ? WHERE id = ?', [payload.name, payload.width, payload.height, getAuditUserId(req), canvasSize.id]);
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'canvas-sizes', eventType: 'canvas-size.updated', actorUserId: getAuditUserId(req), targetType: 'canvas-size', targetId: canvasSize.id, targetLabel: payload.name, details: { width: payload.width, height: payload.height } });
|
||||
redirectAfterSave(req, res, '/canvas-sizes', {
|
||||
closeUrl: '/canvas-sizes',
|
||||
newUrl: '/canvas-sizes/new',
|
||||
@@ -806,6 +837,7 @@ module.exports = function registerContentRoutes(app, deps) {
|
||||
}
|
||||
await pool.query('UPDATE c_templates SET canvas_size_id = NULL, modified_by = ? WHERE canvas_size_id = ?', [getAuditUserId(req), canvasSize.id]);
|
||||
await pool.query('DELETE FROM c_canvas_sizes WHERE id = ?', [canvasSize.id]);
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'canvas-sizes', eventType: 'canvas-size.deleted', actorUserId: getAuditUserId(req), targetType: 'canvas-size', targetId: canvasSize.id, targetLabel: canvasSize.name });
|
||||
res.redirect('/canvas-sizes?message=' + encodeURIComponent('Canvas size deleted.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
Reference in New Issue
Block a user