From 7fec2154e06f01afea608523580ddb90b93d30d1 Mon Sep 17 00:00:00 2001 From: Mark Rapson Date: Fri, 4 Sep 2026 22:16:43 +0100 Subject: [PATCH] fix: preserve settings during cleanup --- CHANGELOG.md | 1 + src/web/routes/settings/routes.js | 10 ++++++++-- test/settings-route.test.js | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627e967..9079ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. - Updated the invitation registration page to show fields only for valid tokens, use the themed failure state for invalid links, and pre-fill the invited email and display name. - Made the invited email visibly disabled, kept the display name editable and required, and aligned invitation actions with the existing user forms. - Added bold, italic, and underline formatting controls to the user invitation email message template editor. +- Fixed application-settings cleanup SQL so media and icon saves preserve all supported settings and no longer fail with MariaDB placeholder errors. - Updated API progress bars to inherit the surrounding WYSIWYG text size and text color, preserve rounded corners, and render consistently in the editor preview, player, and thumbnails. ## 2.11.0 - 2026-09-02 diff --git a/src/web/routes/settings/routes.js b/src/web/routes/settings/routes.js index 5b361a4..2052f39 100644 --- a/src/web/routes/settings/routes.js +++ b/src/web/routes/settings/routes.js @@ -65,6 +65,12 @@ module.exports = function registerSettingsPageRoutes(app, deps) { weather: ['weather.open_meteo_api_key', 'weather.pirate_weather_api_key'], icons: ['announcements.suggested_icons'] }; + const ALL_SETTING_KEYS = Object.keys(SECTION_SETTING_KEYS).reduce(function (keys, section) { + return keys.concat(SECTION_SETTING_KEYS[section].filter(function (key) { + return !keys.includes(key); + })); + }, []); + const ALL_SETTING_KEY_PLACEHOLDERS = ALL_SETTING_KEYS.map(function () { return '?'; }).join(', '); function maskApiKey(value) { const key = String(value || ''); @@ -496,7 +502,7 @@ module.exports = function registerSettingsPageRoutes(app, deps) { error.expose = true; throw error; } - await pool.query('DELETE FROM o_app_settings WHERE setting_key NOT IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ['announcements.suggested_icons', 'announcements.default_icon', 'announcements.default_duration_value', 'announcements.default_duration_unit', 'player.default_slide_duration_seconds', 'player.default_fade_between_slides', 'player.skip_unavailable_rtmp', 'data-sources.rss_default_interval_value', 'data-sources.rss_default_interval_unit', 'data-sources.api_default_interval_value', 'data-sources.api_default_interval_unit', 'weather.open_meteo_api_key', 'weather.pirate_weather_api_key', 'uploads.image_max_bytes', 'uploads.video_max_bytes', 'uploads.wysiwyg_image_max_bytes', 'uploads.allowed_mime_types', 'security.session_lifetime_days', 'security.allow_user_session_revocation', 'security.max_active_sessions', 'security.login_max_attempts', 'security.login_lockout_minutes', 'security.login_rate_limit_scope', 'security.password_min_length', 'security.password_min_categories', 'security.password_require_lowercase', 'security.password_require_uppercase', 'security.password_require_number', 'security.password_require_symbol', 'security.require_password_change_for_new_users', 'security.require_password_change_after_admin_reset', 'security.allow_admin_email_verification_bypass', 'email.smtp_enabled', 'email.smtp_host', 'email.smtp_port', 'email.smtp_security', 'email.smtp_username', 'email.smtp_password', 'email.from_address', 'email.from_name', 'email.reply_to', 'audit.enabled', 'audit.categories', 'audit.screen_control_commands', 'audit.include_request_metadata', 'audit.retention_days']); + await pool.query('DELETE FROM o_app_settings WHERE setting_key NOT IN (' + ALL_SETTING_KEY_PLACEHOLDERS + ')', ALL_SETTING_KEYS); const savedSettings = await saveAppSettings(pool, { 'uploads.image_max_bytes': imageMaxMb * 1024 * 1024, 'uploads.video_max_bytes': videoMaxMb * 1024 * 1024, @@ -533,7 +539,7 @@ module.exports = function registerSettingsPageRoutes(app, deps) { error.expose = true; throw error; } - await pool.query('DELETE FROM o_app_settings WHERE setting_key NOT IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ['announcements.suggested_icons', 'announcements.default_icon', 'announcements.default_duration_value', 'announcements.default_duration_unit', 'player.default_slide_duration_seconds', 'player.default_fade_between_slides', 'player.skip_unavailable_rtmp', 'data-sources.rss_default_interval_value', 'data-sources.rss_default_interval_unit', 'data-sources.api_default_interval_value', 'data-sources.api_default_interval_unit', 'weather.open_meteo_api_key', 'weather.pirate_weather_api_key', 'uploads.image_max_bytes', 'uploads.video_max_bytes', 'uploads.wysiwyg_image_max_bytes', 'uploads.allowed_mime_types', 'security.session_lifetime_days', 'security.allow_user_session_revocation', 'security.max_active_sessions', 'security.login_max_attempts', 'security.login_lockout_minutes', 'security.login_rate_limit_scope', 'security.password_min_length', 'security.password_min_categories', 'security.password_require_lowercase', 'security.password_require_uppercase', 'security.password_require_number', 'security.password_require_symbol', 'security.require_password_change_for_new_users', 'security.require_password_change_after_admin_reset', 'audit.enabled', 'audit.categories', 'audit.screen_control_commands', 'audit.include_request_metadata', 'audit.retention_days']); + await pool.query('DELETE FROM o_app_settings WHERE setting_key NOT IN (' + ALL_SETTING_KEY_PLACEHOLDERS + ')', ALL_SETTING_KEYS); const savedSettings = await saveAppSettings(pool, { 'announcements.suggested_icons': suggestedIcons }, req.currentUser && req.currentUser.id); diff --git a/test/settings-route.test.js b/test/settings-route.test.js index e55b43b..bac6acc 100644 --- a/test/settings-route.test.js +++ b/test/settings-route.test.js @@ -66,6 +66,12 @@ test('settings route renders the overview', async () => { }); assert.ok(queries.some(function (entry) { return entry.sql && /DELETE FROM o_app_settings/.test(entry.sql); })); assert.ok(queries.some(function (entry) { return entry.sql && /INSERT INTO o_app_settings/.test(entry.sql); })); + queries.filter(function (entry) { + return entry.sql && /DELETE FROM o_app_settings/.test(entry.sql); + }).forEach(function (entry) { + assert.equal((entry.sql.match(/\?/g) || []).length, entry.params.length); + assert.ok(entry.params.includes('email.invitation_body')); + }); assert.match(saveResponse.redirectedTo, /^\/settings\/system\?message=/); let tooManyError = null;