208 lines
8.8 KiB
JavaScript
208 lines
8.8 KiB
JavaScript
const { DEFAULT_ANNOUNCEMENT_ICON_KEYS } = require('./announcement-icons');
|
|
|
|
const SETTING_DEFINITIONS = [
|
|
{ key: 'app.name', type: 'string', defaultValue: 'Pulse Signage' },
|
|
{ key: 'locale.timezone', type: 'string', defaultValue: 'Europe/London' },
|
|
{ key: 'locale.language', type: 'string', defaultValue: 'en' },
|
|
{ key: 'ui.theme', type: 'enum', values: ['dark', 'light', 'auto'], defaultValue: 'dark' },
|
|
{ key: 'security.session_lifetime_days', type: 'integer', min: 1, defaultValue: 14 },
|
|
{ key: 'security.allow_user_session_revocation', type: 'boolean', defaultValue: true },
|
|
{ key: 'security.max_active_sessions', type: 'integer', min: 0, defaultValue: 0 },
|
|
{ key: 'security.password_min_length', type: 'integer', min: 8, defaultValue: 10 },
|
|
{ key: 'security.password_min_categories', type: 'integer', min: 1, defaultValue: 3 },
|
|
{ key: 'security.password_require_lowercase', type: 'boolean', defaultValue: false },
|
|
{ key: 'security.password_require_uppercase', type: 'boolean', defaultValue: false },
|
|
{ key: 'security.password_require_number', type: 'boolean', defaultValue: false },
|
|
{ key: 'security.password_require_symbol', type: 'boolean', defaultValue: false },
|
|
{ key: 'security.require_password_change_for_new_users', type: 'boolean', defaultValue: true },
|
|
{ key: 'security.require_password_change_after_admin_reset', type: 'boolean', defaultValue: true },
|
|
{ key: 'security.login_max_attempts', type: 'integer', min: 1, defaultValue: 5 },
|
|
{ key: 'security.login_lockout_minutes', type: 'integer', min: 1, defaultValue: 15 },
|
|
{ key: 'security.login_rate_limit_scope', type: 'enum', values: ['both', 'username', 'ip'], defaultValue: 'both' },
|
|
{ key: 'audit.enabled', type: 'boolean', defaultValue: true },
|
|
{ key: 'audit.categories', type: 'string_array', defaultValue: ['authentication', 'security', 'sessions', 'users', 'roles', 'system-settings'] },
|
|
{ key: 'audit.include_request_metadata', type: 'boolean', defaultValue: true },
|
|
{ key: 'audit.retention_days', type: 'integer', min: 0, defaultValue: 180 },
|
|
{ key: 'uploads.image_max_bytes', type: 'integer', min: 1, defaultValue: 100 * 1024 * 1024 },
|
|
{ key: 'uploads.video_max_bytes', type: 'integer', min: 1, defaultValue: 1024 * 1024 * 1024 },
|
|
{ key: 'uploads.wysiwyg_image_max_bytes', type: 'integer', min: 1, defaultValue: 2 * 1024 * 1024 },
|
|
{ key: 'uploads.allowed_mime_types', type: 'string_array', defaultValue: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml', 'video/mp4', 'video/webm', 'video/ogg'] },
|
|
{ key: 'uploads.cleanup_days', type: 'integer', min: 0, defaultValue: 30 },
|
|
{ key: 'uploads.optimize_images', type: 'boolean', defaultValue: true },
|
|
{ key: 'announcements.default_icon', type: 'string', defaultValue: 'megaphone-fill' },
|
|
{ key: 'announcements.default_duration_value', type: 'integer', min: 1, defaultValue: 10 },
|
|
{ key: 'announcements.default_duration_unit', type: 'enum', values: ['seconds', 'minutes'], defaultValue: 'seconds' },
|
|
{ key: 'announcements.suggested_icons', type: 'string_array', defaultValue: DEFAULT_ANNOUNCEMENT_ICON_KEYS.slice() },
|
|
{ key: 'player.default_slide_duration_seconds', type: 'integer', min: 1, defaultValue: 10 },
|
|
{ key: 'player.default_fade_between_slides', type: 'boolean', defaultValue: true },
|
|
{ key: 'player.skip_unavailable_rtmp', type: 'boolean', defaultValue: true }
|
|
,{ key: 'data-sources.rss_default_interval_value', type: 'integer', min: 1, defaultValue: 60 }
|
|
,{ key: 'data-sources.rss_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' }
|
|
,{ key: 'data-sources.api_default_interval_value', type: 'integer', min: 1, defaultValue: 60 }
|
|
,{ key: 'data-sources.api_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' }
|
|
];
|
|
|
|
const DEFINITIONS_BY_KEY = new Map(SETTING_DEFINITIONS.map(function (definition) {
|
|
return [definition.key, definition];
|
|
}));
|
|
|
|
function cloneValue(value) {
|
|
if (Array.isArray(value)) {
|
|
return value.slice();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function getAppSettingDefinitions() {
|
|
return SETTING_DEFINITIONS.map(function (definition) {
|
|
return Object.assign({}, definition, {
|
|
values: definition.values ? definition.values.slice() : undefined,
|
|
defaultValue: cloneValue(definition.defaultValue)
|
|
});
|
|
});
|
|
}
|
|
|
|
function getDefaultAppSettings() {
|
|
return SETTING_DEFINITIONS.reduce(function (settings, definition) {
|
|
settings[definition.key] = cloneValue(definition.defaultValue);
|
|
return settings;
|
|
}, {});
|
|
}
|
|
|
|
function normalizeSettingValue(key, value) {
|
|
const definition = DEFINITIONS_BY_KEY.get(String(key || '').trim());
|
|
if (!definition) {
|
|
throw new Error('Unknown application setting: ' + key);
|
|
}
|
|
|
|
if (definition.type === 'string') {
|
|
return String(value == null ? '' : value).trim();
|
|
}
|
|
|
|
if (definition.type === 'integer') {
|
|
const normalized = Number(value);
|
|
if (!Number.isInteger(normalized) || normalized < definition.min) {
|
|
throw new Error('Invalid value for application setting: ' + definition.key);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
if (definition.type === 'boolean') {
|
|
if (value === true || value === 1 || value === '1' || value === 'true') {
|
|
return true;
|
|
}
|
|
if (value === false || value === 0 || value === '0' || value === 'false') {
|
|
return false;
|
|
}
|
|
throw new Error('Invalid value for application setting: ' + definition.key);
|
|
}
|
|
|
|
if (definition.type === 'enum') {
|
|
const normalized = String(value == null ? '' : value).trim();
|
|
if (!definition.values.includes(normalized)) {
|
|
throw new Error('Invalid value for application setting: ' + definition.key);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
if (definition.type === 'string_array') {
|
|
if (!Array.isArray(value)) {
|
|
throw new Error('Invalid value for application setting: ' + definition.key);
|
|
}
|
|
return Array.from(new Set(value.map(function (item) {
|
|
return String(item || '').trim();
|
|
}).filter(Boolean)));
|
|
}
|
|
|
|
throw new Error('Unsupported application setting type: ' + definition.type);
|
|
}
|
|
|
|
function normalizeAppSettings(settings) {
|
|
const input = settings && typeof settings === 'object' ? settings : {};
|
|
return SETTING_DEFINITIONS.reduce(function (normalized, definition) {
|
|
const value = Object.prototype.hasOwnProperty.call(input, definition.key)
|
|
? input[definition.key]
|
|
: definition.defaultValue;
|
|
normalized[definition.key] = normalizeSettingValue(definition.key, value);
|
|
return normalized;
|
|
}, {});
|
|
}
|
|
|
|
function parseStoredValue(value) {
|
|
if (typeof value !== 'string') {
|
|
return value;
|
|
}
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch (_error) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
async function fetchAppSettings(pool) {
|
|
const [rows] = await pool.query('SELECT id, setting_key, setting_value FROM o_app_settings ORDER BY setting_key');
|
|
const storedSettings = {};
|
|
(rows || []).forEach(function (row) {
|
|
const key = String(row && row.setting_key || '').trim();
|
|
if (DEFINITIONS_BY_KEY.has(key)) {
|
|
storedSettings[key] = parseStoredValue(row.setting_value);
|
|
}
|
|
});
|
|
return normalizeAppSettings(Object.assign({}, getDefaultAppSettings(), storedSettings));
|
|
}
|
|
|
|
async function saveAppSettings(pool, settings, modifiedBy) {
|
|
const inputSettings = settings && typeof settings === 'object' ? settings : {};
|
|
const normalizedSettings = normalizeAppSettings(inputSettings);
|
|
const connection = typeof pool.getConnection === 'function' ? await pool.getConnection() : pool;
|
|
const shouldRelease = connection !== pool;
|
|
|
|
try {
|
|
if (typeof connection.beginTransaction === 'function') {
|
|
await connection.beginTransaction();
|
|
}
|
|
for (const definition of SETTING_DEFINITIONS) {
|
|
if (!Object.prototype.hasOwnProperty.call(inputSettings, definition.key)) {
|
|
continue;
|
|
}
|
|
await connection.query(
|
|
`UPDATE o_app_settings
|
|
SET setting_value = ?, modified_by = ?
|
|
WHERE setting_key = ?`,
|
|
[JSON.stringify(normalizedSettings[definition.key]), modifiedBy || null, definition.key]
|
|
);
|
|
await connection.query(
|
|
`INSERT INTO o_app_settings (setting_key, setting_value, created_by, modified_by)
|
|
SELECT ?, ?, ?, ?
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM o_app_settings WHERE setting_key = ?
|
|
)`,
|
|
[definition.key, JSON.stringify(normalizedSettings[definition.key]), modifiedBy || null, modifiedBy || null, definition.key]
|
|
);
|
|
}
|
|
if (typeof connection.commit === 'function') {
|
|
await connection.commit();
|
|
}
|
|
} catch (error) {
|
|
if (typeof connection.rollback === 'function') {
|
|
await connection.rollback();
|
|
}
|
|
throw error;
|
|
} finally {
|
|
if (shouldRelease && typeof connection.release === 'function') {
|
|
connection.release();
|
|
}
|
|
}
|
|
|
|
return normalizedSettings;
|
|
}
|
|
|
|
module.exports = {
|
|
getAppSettingDefinitions,
|
|
getDefaultAppSettings,
|
|
normalizeSettingValue,
|
|
normalizeAppSettings,
|
|
fetchAppSettings,
|
|
saveAppSettings
|
|
};
|