Release 1.5.3

This commit is contained in:
2026-07-25 15:11:41 +01:00
parent 9ee938fd2f
commit 0e89892c94
89 changed files with 7660 additions and 542 deletions
+87 -4
View File
@@ -17,6 +17,7 @@ const registerAdminContentRoutes = require('./web/routes/admin/content');
const registerAdminDataSourceRoutes = require('./web/routes/data-sources');
const registerAdminSettingsRoutes = require('./web/routes/settings/background-tasks');
const { createBackgroundTaskQueue, normalizeIntervalMs } = require('./web/lib/background-task-queue');
const { captureSlideThumbnail } = require('./web/lib/slide-thumbnails');
const { refreshApiSource, refreshRssFeed } = require('./web/lib/data-source-refresh');
const { createWebBootstrap } = require('./web/bootstrap');
const { requirePermission } = require('./rbac');
@@ -52,7 +53,11 @@ async function start() {
const pool = common.createPool();
const PORT = Number(process.env.WEB_PORT || 3000);
const MEDIA_DIR = path.join(__dirname, '..', 'media');
const UPLOADS_DIR = path.join(MEDIA_DIR, 'uploads');
const THUMBNAILS_DIR = path.join(MEDIA_DIR, 'thumbnails');
const ASSET_DIR = path.join(__dirname, 'web', 'public');
const WEB_BASE_URL = (process.env.WEB_PUBLIC_BASE_URL || process.env.WEB_BASE_URL || ('http://127.0.0.1:' + PORT)).replace(/\/$/, '');
const DATA_SOURCE_STARTUP_REFRESH_STAGGER_MS = Math.max(100, Number(process.env.DATA_SOURCE_STARTUP_REFRESH_STAGGER_MS || 250));
const backgroundTaskQueue = createBackgroundTaskQueue({
pool: pool,
maxConcurrent: 1
@@ -83,7 +88,7 @@ async function start() {
common: common,
playerInternalBaseUrl: PLAYER_INTERNAL_BASE_URL,
playerPublicBaseUrl: PLAYER_PUBLIC_BASE_URL,
uploadDir: MEDIA_DIR,
uploadDir: UPLOADS_DIR,
dashboardRefreshIntervalMs: Number(process.env.DASHBOARD_REFRESH_INTERVAL_MS || 2000),
formatDashboardDate: formatDashboardDate,
notifyPlayerScreens: notifyPlayerScreens,
@@ -138,10 +143,29 @@ async function start() {
throw new Error('Unsupported data source refresh task.');
});
backgroundTaskQueue.setTaskHandler('slide-thumbnail-refresh', async function (task) {
const payload = task && task.payload ? task.payload : {};
const slideId = Number(payload.slideId || 0);
if (!Number.isFinite(slideId) || slideId <= 0) {
throw new Error('Slide id is required.');
}
return captureSlideThumbnail({
pool: pool,
common: common,
mediaDir: MEDIA_DIR,
baseUrl: PLAYER_INTERNAL_BASE_URL,
slideId: slideId,
previousThumbnailPath: payload.previousThumbnailPath || null
});
});
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/assets', express.static(ASSET_DIR));
app.use('/media', express.static(MEDIA_DIR));
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
fs.mkdirSync(THUMBNAILS_DIR, { recursive: true });
app.use(async function (req, _res, next) {
try {
@@ -153,7 +177,7 @@ async function start() {
});
app.use(function (req, res, next) {
if (req.path === '/' || req.path === '/login' || req.path === '/logout') {
if (req.path === '/' || req.path === '/login' || req.path === '/logout' || req.path.indexOf('/api/internal/slide-thumbnails/') === 0) {
return next();
}
@@ -255,7 +279,7 @@ async function start() {
common: common,
pages: pages,
upload: upload,
uploadDir: MEDIA_DIR,
uploadDir: UPLOADS_DIR,
fetchScreensBySlideId: fetchScreensBySlideId,
fetchScreensByTemplateId: fetchScreensByTemplateId,
collectUploadReferencesFromSlide: collectUploadReferencesFromSlide,
@@ -266,6 +290,7 @@ async function start() {
redirectAfterSave: redirectAfterSave,
notifyPlayerScreens: notifyPlayerScreens,
broadcastDashboardState: broadcastDashboardState,
backgroundTaskQueue: backgroundTaskQueue,
getSlideDeleteBlockMessage: playerActionService.getSlideDeleteBlockMessage,
getTemplateDeleteBlockMessage: playerActionService.getTemplateDeleteBlockMessage,
getCanvasSizeDeleteBlockMessage: playerActionService.getCanvasSizeDeleteBlockMessage,
@@ -340,7 +365,7 @@ async function start() {
});
// Ensure schema and mirror media before the web service starts handling traffic.
await common.ensureSchema(pool);
await common.ensureSchema(pool, { mediaDir: MEDIA_DIR });
await backgroundTaskQueue.initialize();
async function syncRecurringRefreshes() {
@@ -381,7 +406,65 @@ async function start() {
});
}
async function scheduleInitialDataSourceRefreshes() {
const apiSourcesData = await common.fetchApiSourcesData(pool);
const rssFeedsData = await common.fetchRssFeedsData(pool);
const startupSources = [];
(apiSourcesData.apiSources || []).forEach(function (apiSource) {
startupSources.push({
type: 'api-source',
id: Number(apiSource.id),
name: apiSource.name,
run: function () {
return refreshApiSource(pool, common, apiSource.id, apiSource.api_url, null);
}
});
});
(rssFeedsData.rssFeeds || []).forEach(function (rssFeed) {
startupSources.push({
type: 'rss-feed',
id: Number(rssFeed.id),
name: rssFeed.name,
run: function () {
return refreshRssFeed(pool, common, rssFeed.id, rssFeed.feed_url, rssFeed.item_limit, null);
}
});
});
startupSources.forEach(function (source, index) {
const startupDelayMs = index * DATA_SOURCE_STARTUP_REFRESH_STAGGER_MS;
setTimeout(function () {
backgroundTaskQueue.enqueueTask({
key: 'startup-data-source-refresh:' + source.type + ':' + source.id + ':' + Date.now(),
title: source.type === 'rss-feed' ? 'RSS feed refresh' : 'API source refresh',
category: 'data-source',
taskType: 'data-source-refresh',
metadata: {
sourceType: source.type,
sourceId: source.id,
sourceName: source.name,
startupRefresh: true
},
payload: {
sourceType: source.type,
sourceId: source.id,
sourceName: source.name,
startupRefresh: true
}
}).catch(function (error) {
console.warn('Unable to queue startup refresh for ' + source.type + ' ' + source.id + ':', error);
});
}, startupDelayMs);
});
}
await syncRecurringRefreshes();
scheduleInitialDataSourceRefreshes().catch(function (error) {
console.warn('Unable to schedule startup data source refreshes:', error);
});
fs.mkdirSync(MEDIA_DIR, { recursive: true });
await syncExistingUploadsToPlayer(pool, MEDIA_DIR).catch(function (error) {