Release 2.8.4
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m14s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 35s

This commit is contained in:
2026-08-17 00:46:07 +01:00
parent 7bb34f40fe
commit 403a928b9e
15 changed files with 309 additions and 27 deletions
+57 -4
View File
@@ -1,10 +1,12 @@
// Slide route registration and pagination wiring.
const { renderFragment } = require('../../../view');
const fs = require('fs');
const crypto = require('crypto');
const path = require('path');
const { verifyRequestAuth } = require('#src/request-auth');
const { verifyRequestAuth, verifyPageAuthToken } = require('#src/request-auth');
const { getFontStylesheetHref } = require('#src/web/lib/media/font-library');
const { buildThumbnailPreviewPayload } = require('#src/web/lib/media/slide-thumbnail-preview');
const { buildThumbnailPreviewPayload } = require('#src/web/lib/media/slide-thumbnails');
function safeJsonForScript(value) {
return JSON.stringify(value === undefined ? null : value).replace(/</g, '\\u003c');
@@ -27,6 +29,14 @@ module.exports = function registerSlidesRoutes(app, deps) {
next();
}
function requirePopupPreviewAccess(req, res, next) {
if (verifyRequestAuth(req) || verifyPageAuthToken(req.headers && req.headers['x-pulse-page-auth'])) {
return next();
}
return requirePermission('slides.read')(req, res, next);
}
const LIST_PAGE_SIZE = 25;
app.get('/slides', requirePermission('slides.read'), async function (req, res, next) {
@@ -45,7 +55,7 @@ module.exports = function registerSlidesRoutes(app, deps) {
}
});
app.get('/slides/popup-preview', requirePermission('slides.read'), function (_req, res) {
app.get('/slides/popup-preview', requirePopupPreviewAccess, function (_req, res) {
res.send(renderFragment('slides/popup-preview', {
title: 'Slide preview',
framePopupCard: true,
@@ -61,9 +71,52 @@ module.exports = function registerSlidesRoutes(app, deps) {
return res.status(404).send('Slide not found');
}
const rssData = typeof common.fetchRssFeedsData === 'function' ? await common.fetchRssFeedsData(pool) : { rssFeeds: [] };
const rssFeeds = await Promise.all((rssData.rssFeeds || []).map(async function (feed) {
const items = typeof common.fetchRssFeedItemsByFeedId === 'function' ? await common.fetchRssFeedItemsByFeedId(pool, feed.id) : [];
return Object.assign({}, feed, { items: items });
}));
const apiData = typeof common.fetchApiSourcesData === 'function' ? await common.fetchApiSourcesData(pool) : { apiSources: [] };
const apiSources = (apiData.apiSources || []).map(function (source) {
return Object.assign({}, source, { responseJson: common.parseJsonSafe ? common.parseJsonSafe(source.last_response_json) : null });
});
const getApiItems = function (source) {
const response = source && source.responseJson;
const itemsPath = String(source && source.items_path || '').trim();
if (itemsPath) {
const selected = itemsPath.split('.').reduce(function (current, segment) { return current === undefined || current === null ? '' : current[segment]; }, response);
return Array.isArray(selected) ? selected : [];
}
if (Array.isArray(response)) return response;
if (response && Array.isArray(response.items)) return response.items;
if (response && Array.isArray(response.results)) return response.results;
if (response && Array.isArray(response.data)) return response.data;
return response ? [response] : [];
};
const getItem = function (type, regionContent) {
const index = Math.max(0, Math.max(1, Number(regionContent && regionContent.item_number || 1)) - 1);
if (type === 'api') {
const source = apiSources.find(function (entry) { return Number(entry.id) === Number(regionContent.source_id); });
return getApiItems(source)[index] || null;
}
if (type === 'rss') {
const feed = rssFeeds.find(function (entry) { return Number(entry.id) === Number(regionContent.feed_id); });
return feed && Array.isArray(feed.items) ? feed.items[index] || null : null;
}
return null;
};
const getCachedImagePath = function (remoteUrl) {
const hash = crypto.createHash('sha256').update(String(remoteUrl || '')).digest('hex');
const cacheDir = path.join(mediaDir, 'player-cache', 'remote-images');
const extensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
const file = extensions.map(function (extension) { return hash + extension; }).find(function (name) { return fs.existsSync(path.join(cacheDir, name)); });
return file ? '/media/player-cache/remote-images/' + file : '';
};
const payload = buildThumbnailPreviewPayload(slide, {
baseUrl: webBaseUrl || (String(req.headers.host || '').trim() ? `${req.protocol}://${String(req.headers.host).trim()}` : ''),
fontStylesheetHref: getFontStylesheetHref(mediaDir)
fontStylesheetHref: getFontStylesheetHref(mediaDir),
getItem: getItem,
getCachedImagePath: getCachedImagePath
});
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');