diff --git a/app/2fa/server/lib/totp.js b/app/2fa/server/lib/totp.js index fcdf46f3bbdb..160c1d830d25 100644 --- a/app/2fa/server/lib/totp.js +++ b/app/2fa/server/lib/totp.js @@ -3,7 +3,7 @@ import { Random } from 'meteor/random'; import speakeasy from 'speakeasy'; import { Users } from '../../../models'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; export const TOTP = { generateSecret() { diff --git a/app/2fa/server/startup/settings.js b/app/2fa/server/startup/settings.ts similarity index 93% rename from app/2fa/server/startup/settings.js rename to app/2fa/server/startup/settings.ts index a4e2f1920c18..46db4643be8a 100644 --- a/app/2fa/server/startup/settings.js +++ b/app/2fa/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('Accounts', function() { +settingsRegistry.addGroup('Accounts', function() { this.section('Two Factor Authentication', function() { const enable2FA = { _id: 'Accounts_TwoFactorAuthentication_Enabled', diff --git a/app/analytics/server/settings.js b/app/analytics/server/settings.ts similarity index 94% rename from app/analytics/server/settings.js rename to app/analytics/server/settings.ts index 932e834cdb6e..959134ed0cab 100644 --- a/app/analytics/server/settings.js +++ b/app/analytics/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('Analytics', function addSettings() { +settingsRegistry.addGroup('Analytics', function addSettings() { this.section('Piwik', function() { const enableQuery = { _id: 'PiwikAnalytics_enabled', value: true }; this.add('PiwikAnalytics_enabled', false, { diff --git a/app/api/server/api.js b/app/api/server/api.js index c492b1a6e81b..3b1d6bdfbb9e 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -744,11 +744,11 @@ const createApis = function _createApis() { createApis(); // register the API to be re-created once the CORS-setting changes. -settings.get(/^(API_Enable_CORS|API_CORS_Origin)$/, () => { +settings.watchMultiple(['API_Enable_CORS', 'API_CORS_Origin'], () => { createApis(); }); -settings.get('Accounts_CustomFields', (key, value) => { +settings.watch('Accounts_CustomFields', (value) => { if (!value) { return API.v1.setLimitedCustomFields([]); } @@ -761,16 +761,16 @@ settings.get('Accounts_CustomFields', (key, value) => { } }); -settings.get('API_Enable_Rate_Limiter_Limit_Time_Default', (key, value) => { +settings.watch('API_Enable_Rate_Limiter_Limit_Time_Default', (value) => { defaultRateLimiterOptions.intervalTimeInMS = value; API.v1.reloadRoutesToRefreshRateLimiter(); }); -settings.get('API_Enable_Rate_Limiter_Limit_Calls_Default', (key, value) => { +settings.watch('API_Enable_Rate_Limiter_Limit_Calls_Default', (value) => { defaultRateLimiterOptions.numRequestsAllowed = value; API.v1.reloadRoutesToRefreshRateLimiter(); }); -settings.get('Prometheus_API_User_Agent', (key, value) => { +settings.watch('Prometheus_API_User_Agent', (value) => { prometheusAPIUserAgent = value; }); diff --git a/app/api/server/helpers/getPaginationItems.js b/app/api/server/helpers/getPaginationItems.js index 93a19b2cbf9f..0cff491a9763 100644 --- a/app/api/server/helpers/getPaginationItems.js +++ b/app/api/server/helpers/getPaginationItems.js @@ -1,7 +1,7 @@ // If the count query param is higher than the "API_Upper_Count_Limit" setting, then we limit that // If the count query param isn't defined, then we set it to the "API_Default_Count" setting // If the count is zero, then that means unlimited and is only allowed if the setting "API_Allow_Infinite_Count" is true -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { API } from '../api'; API.helperMethods.set('getPaginationItems', function _getPaginationItems() { diff --git a/app/api/server/helpers/getUserInfo.js b/app/api/server/helpers/getUserInfo.js index 2d2daee1af34..5353a78d0420 100644 --- a/app/api/server/helpers/getUserInfo.js +++ b/app/api/server/helpers/getUserInfo.js @@ -11,10 +11,10 @@ API.helperMethods.set('getUserInfo', function _getUserInfo(me) { }; const getUserPreferences = () => { const defaultUserSettingPrefix = 'Accounts_Default_User_Preferences_'; - const allDefaultUserSettings = settings.get(new RegExp(`^${ defaultUserSettingPrefix }.*$`)); + const allDefaultUserSettings = settings.getByRegexp(new RegExp(`^${ defaultUserSettingPrefix }.*$`)); - return allDefaultUserSettings.reduce((accumulator, setting) => { - const settingWithoutPrefix = setting.key.replace(defaultUserSettingPrefix, ' ').trim(); + return allDefaultUserSettings.reduce((accumulator, [key]) => { + const settingWithoutPrefix = key.replace(defaultUserSettingPrefix, ' ').trim(); accumulator[settingWithoutPrefix] = getUserPreference(me, settingWithoutPrefix); return accumulator; }, {}); diff --git a/app/api/server/settings.js b/app/api/server/settings.ts similarity index 90% rename from app/api/server/settings.js rename to app/api/server/settings.ts index 7a33ff4dce9d..e042edee99a0 100644 --- a/app/api/server/settings.js +++ b/app/api/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('General', function() { +settingsRegistry.addGroup('General', function() { this.section('REST API', function() { this.add('API_Upper_Count_Limit', 100, { type: 'int', public: false }); this.add('API_Default_Count', 50, { type: 'int', public: false }); diff --git a/app/api/server/v1/autotranslate.js b/app/api/server/v1/autotranslate.js index 6a65c21adc73..7bb9509cdada 100644 --- a/app/api/server/v1/autotranslate.js +++ b/app/api/server/v1/autotranslate.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { API } from '../api'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Messages } from '../../../models/server'; API.v1.addRoute('autotranslate.getSupportedLanguages', { authRequired: true }, { diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index 11680765d1f8..db41290a16f3 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -12,7 +12,7 @@ import { API } from '../api'; import Rooms from '../../../models/server/models/Rooms'; import Users from '../../../models/server/models/Users'; import Subscriptions from '../../../models/server/models/Subscriptions'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { findMentionedMessages, findStarredMessages, findSnippetedMessageById, findSnippetedMessages, findDiscussionsFromRoom } from '../lib/messages'; API.v1.addRoute('chat.delete', { authRequired: true }, { diff --git a/app/api/server/v1/ldap.ts b/app/api/server/v1/ldap.ts index 3b47a64b28a3..ee98484d1791 100644 --- a/app/api/server/v1/ldap.ts +++ b/app/api/server/v1/ldap.ts @@ -16,7 +16,7 @@ API.v1.addRoute('ldap.testConnection', { authRequired: true }, { throw new Error('error-not-authorized'); } - if (settings.get('LDAP_Enable') !== true) { + if (settings.get('LDAP_Enable') !== true) { throw new Error('LDAP_disabled'); } diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index c7a7ca33c97f..b9c720f3522a 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -147,10 +147,7 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { value: Match.Any, }); if (Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) { - settings.storeSettingValue({ - _id: this.urlParams._id, - value: this.bodyParams.value, - }); + settings.set(Settings.findOneNotHiddenById(this.urlParams._id)); setValue(this.urlParams._id, this.bodyParams.value); return API.v1.success(); } diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index d5dc6018c671..714fc5e9265f 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -6,7 +6,7 @@ import _ from 'underscore'; import { Users, Subscriptions } from '../../../models/server'; import { Users as UsersRaw } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { getURL } from '../../../utils'; import { validateCustomFields, diff --git a/app/apple/server/index.js b/app/apple/server/index.js index bfc42742322d..7b8670be5e50 100644 --- a/app/apple/server/index.js +++ b/app/apple/server/index.js @@ -1,2 +1,2 @@ -import './startup.js'; +import './startup'; import './loginHandler.js'; diff --git a/app/apple/server/startup.js b/app/apple/server/startup.ts similarity index 57% rename from app/apple/server/startup.js rename to app/apple/server/startup.ts index c0b04ed32334..97ba8b3a239f 100644 --- a/app/apple/server/startup.js +++ b/app/apple/server/startup.ts @@ -1,17 +1,16 @@ -import _ from 'underscore'; -import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { settings } from '../../settings'; +import { settings, settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('Apple', function() { this.add('Accounts_OAuth_Apple', false, { type: 'boolean', public: true }); }); }); -const configureService = _.debounce(Meteor.bindEnvironment(() => { - if (!settings.get('Accounts_OAuth_Apple')) { + +settings.watch('Accounts_OAuth_Apple', (enabled) => { + if (!enabled) { return ServiceConfiguration.configurations.remove({ service: 'apple', }); @@ -26,10 +25,4 @@ const configureService = _.debounce(Meteor.bindEnvironment(() => { enabled: settings.get('Accounts_OAuth_Apple'), }, }); -}), 1000); - -Meteor.startup(() => { - settings.get('Accounts_OAuth_Apple', () => { - configureService(); - }); }); diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index 998d313595b8..8b73afc4350a 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -1,5 +1,6 @@ import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager'; import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { hasAtLeastOnePermission } from '../../authorization'; @@ -192,7 +193,8 @@ Meteor.startup(() => { }); }); - settings.get('Apps_Framework_enabled', (isEnabled) => { + Tracker.autorun(() => { + const isEnabled = settings.get('Apps_Framework_enabled'); Apps.load(isEnabled); }); }); diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js index 3c245d75ed59..6d84f3797b95 100644 --- a/app/apps/server/communication/rest.js +++ b/app/apps/server/communication/rest.js @@ -4,7 +4,7 @@ import { HTTP } from 'meteor/http'; import { API } from '../../../api/server'; import { getUploadFormData } from '../../../api/server/lib/getUploadFormData'; import { getWorkspaceAccessToken, getUserCloudAccessToken } from '../../../cloud/server'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Info } from '../../../utils'; import { Settings, Users } from '../../../models/server'; import { Apps } from '../orchestrator'; diff --git a/app/apps/server/communication/uikit.js b/app/apps/server/communication/uikit.js index 4abc6b992ab3..9f12a44b0614 100644 --- a/app/apps/server/communication/uikit.js +++ b/app/apps/server/communication/uikit.js @@ -18,9 +18,9 @@ apiServer.disable('x-powered-by'); let corsEnabled = false; let allowListOrigins = []; -settings.get('API_Enable_CORS', (_, value) => { corsEnabled = value; }); +settings.watch('API_Enable_CORS', (value) => { corsEnabled = value; }); -settings.get('API_CORS_Origin', (_, value) => { +settings.watch('API_CORS_Origin', (value) => { allowListOrigins = value ? value.trim().split(',').map((origin) => String(origin).trim().toLocaleLowerCase()) : []; }); diff --git a/app/apps/server/orchestrator.js b/app/apps/server/orchestrator.js index e74dd09d741b..a68d24197f56 100644 --- a/app/apps/server/orchestrator.js +++ b/app/apps/server/orchestrator.js @@ -3,9 +3,9 @@ import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; import { AppManager } from '@rocket.chat/apps-engine/server/AppManager'; import { Meteor } from 'meteor/meteor'; -import { Logger } from '../../logger'; -import { AppsLogsModel, AppsModel, AppsPersistenceModel, Permissions } from '../../models'; -import { settings } from '../../settings'; +import { Logger } from '../../../server/lib/logger/Logger'; +import { AppsLogsModel, AppsModel, AppsPersistenceModel } from '../../models/server'; +import { settings, settingsRegistry } from '../../settings/server'; import { RealAppBridges } from './bridges'; import { AppMethods, AppServerNotifier, AppsRestApi, AppUIKitInteractionApi } from './communication'; import { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter } from './converters'; @@ -32,7 +32,6 @@ export class AppServerOrchestrator { } this._rocketchatLogger = new Logger('Rocket.Chat Apps'); - Permissions.create('manage-apps', ['admin']); if (typeof process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL === 'string' && process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL !== '') { this._marketplaceUrl = process.env.OVERWRITE_INTERNAL_MARKETPLACE_URL; @@ -78,6 +77,9 @@ export class AppServerOrchestrator { return this._model; } + /** + * @returns {AppsPersistenceModel} + */ getPersistenceModel() { return this._persistModel; } @@ -130,6 +132,9 @@ export class AppServerOrchestrator { return settings.get('Apps_Framework_Development_Mode') && !isTesting(); } + /** + * @returns {Logger} + */ getRocketChatLogger() { return this._rocketchatLogger; } @@ -195,7 +200,7 @@ export class AppServerOrchestrator { export const AppEvents = AppInterface; export const Apps = new AppServerOrchestrator(); -settings.addGroup('General', function() { +settingsRegistry.addGroup('General', function() { this.section('Apps', function() { this.add('Apps_Logs_TTL', '30_days', { type: 'select', @@ -259,7 +264,7 @@ settings.addGroup('General', function() { }); }); -settings.get('Apps_Framework_Source_Package_Storage_Type', (_, value) => { +settings.watch('Apps_Framework_Source_Package_Storage_Type', (value) => { if (!Apps.isInitialized()) { appsSourceStorageType = value; } else { @@ -267,7 +272,7 @@ settings.get('Apps_Framework_Source_Package_Storage_Type', (_, value) => { } }); -settings.get('Apps_Framework_Source_Package_Storage_FileSystem_Path', (_, value) => { +settings.watch('Apps_Framework_Source_Package_Storage_FileSystem_Path', (value) => { if (!Apps.isInitialized()) { appsSourceStorageFilesystemPath = value; } else { @@ -275,7 +280,7 @@ settings.get('Apps_Framework_Source_Package_Storage_FileSystem_Path', (_, value) } }); -settings.get('Apps_Framework_enabled', (key, isEnabled) => { +settings.watch('Apps_Framework_enabled', (isEnabled) => { // In case this gets called before `Meteor.startup` if (!Apps.isInitialized()) { return; @@ -288,7 +293,7 @@ settings.get('Apps_Framework_enabled', (key, isEnabled) => { } }); -settings.get('Apps_Logs_TTL', (key, value) => { +settings.watch('Apps_Logs_TTL', (value) => { if (!Apps.isInitialized()) { return; } diff --git a/app/assets/server/assets.js b/app/assets/server/assets.js index cd9c0f0bc6b6..78c10d88ed32 100644 --- a/app/assets/server/assets.js +++ b/app/assets/server/assets.js @@ -7,11 +7,12 @@ import _ from 'underscore'; import sizeOf from 'image-size'; import sharp from 'sharp'; -import { settings } from '../../settings/server'; +import { settings, settingsRegistry } from '../../settings/server'; import { getURL } from '../../utils/lib/getURL'; import { mime } from '../../utils/lib/mimeTypes'; import { hasPermission } from '../../authorization'; import { RocketChatFile } from '../../file'; +import { Settings } from '../../models/server'; const RocketChatAssetsInstance = new RocketChatFile.GridFS({ @@ -25,8 +26,6 @@ const assets = { constraints: { type: 'image', extensions: ['svg', 'png', 'jpg', 'jpeg'], - width: undefined, - height: undefined, }, wizard: { step: 3, @@ -35,12 +34,9 @@ const assets = { }, background: { label: 'login background (svg, png, jpg)', - defaultUrl: undefined, constraints: { type: 'image', extensions: ['svg', 'png', 'jpg', 'jpeg'], - width: undefined, - height: undefined, }, }, favicon_ico: { @@ -49,8 +45,6 @@ const assets = { constraints: { type: 'image', extensions: ['ico'], - width: undefined, - height: undefined, }, }, favicon: { @@ -59,8 +53,6 @@ const assets = { constraints: { type: 'image', extensions: ['svg'], - width: undefined, - height: undefined, }, }, favicon_16: { @@ -179,8 +171,6 @@ const assets = { constraints: { type: 'image', extensions: ['svg'], - width: undefined, - height: undefined, }, }, }; @@ -234,7 +224,7 @@ export const RocketChatAssets = new class { defaultUrl: assets[asset].defaultUrl, }; - settings.updateById(key, value); + Settings.updateValueById(key, value); return RocketChatAssets.processAsset(key, value); }, 200); })); @@ -255,7 +245,7 @@ export const RocketChatAssets = new class { defaultUrl: assets[asset].defaultUrl, }; - settings.updateById(key, value); + Settings.updateValueById(key, value); RocketChatAssets.processAsset(key, value); } @@ -317,9 +307,9 @@ export const RocketChatAssets = new class { } }(); -settings.addGroup('Assets'); +settingsRegistry.addGroup('Assets'); -settings.add('Assets_SvgFavicon_Enable', true, { +settingsRegistry.add('Assets_SvgFavicon_Enable', true, { type: 'boolean', group: 'Assets', i18nLabel: 'Enable_Svg_Favicon', @@ -328,7 +318,7 @@ settings.add('Assets_SvgFavicon_Enable', true, { function addAssetToSetting(asset, value) { const key = `Assets_${ asset }`; - settings.add(key, { + settingsRegistry.add(key, { defaultUrl: value.defaultUrl, }, { type: 'asset', @@ -344,7 +334,7 @@ function addAssetToSetting(asset, value) { if (typeof currentValue === 'object' && currentValue.defaultUrl !== assets[asset].defaultUrl) { currentValue.defaultUrl = assets[asset].defaultUrl; - settings.updateById(key, currentValue); + Settings.updateValueById(key, currentValue); } } @@ -353,7 +343,7 @@ for (const key of Object.keys(assets)) { addAssetToSetting(key, value); } -settings.get(/^Assets_/, (key, value) => RocketChatAssets.processAsset(key, value)); +settings.watchByRegex(/^Assets_/, (key, value) => RocketChatAssets.processAsset(key, value)); Meteor.startup(function() { return Meteor.setTimeout(function() { diff --git a/app/authentication/server/startup/index.js b/app/authentication/server/startup/index.js index 3bf9e82da3cd..ff9e6b02f431 100644 --- a/app/authentication/server/startup/index.js +++ b/app/authentication/server/startup/index.js @@ -25,16 +25,15 @@ Accounts.config({ forbidClientAccountCreation: true, }); -const updateMailConfig = _.debounce(() => { - Accounts._options.loginExpirationInDays = settings.get('Accounts_LoginExpiration'); - Accounts.emailTemplates.siteName = settings.get('Site_Name'); +Meteor.startup(() => { + settings.watchMultiple(['Accounts_LoginExpiration', 'Site_Name', 'From_Email'], () => { + Accounts._options.loginExpirationInDays = settings.get('Accounts_LoginExpiration'); - Accounts.emailTemplates.from = `${ settings.get('Site_Name') } <${ settings.get('From_Email') }>`; -}, 1000); + Accounts.emailTemplates.siteName = settings.get('Site_Name'); -Meteor.startup(() => { - settings.get(/^(Accounts_LoginExpiration|Site_Name|From_Email)$/, updateMailConfig); + Accounts.emailTemplates.from = `${ settings.get('Site_Name') } <${ settings.get('From_Email') }>`; + }); }); Accounts.emailTemplates.userToActivate = { diff --git a/app/authentication/server/startup/settings.ts b/app/authentication/server/startup/settings.ts index 8f160e2b72ef..9ee111123f40 100644 --- a/app/authentication/server/startup/settings.ts +++ b/app/authentication/server/startup/settings.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../../settings/server'; +import { settingsRegistry } from '../../../settings/server'; Meteor.startup(function() { - settings.addGroup('Accounts', function() { + settingsRegistry.addGroup('Accounts', function() { const enableQueryCollectData = { _id: 'Block_Multiple_Failed_Logins_Enabled', value: true }; this.section('Login_Attempts', function() { diff --git a/app/authorization/server/startup.js b/app/authorization/server/functions/upsertPermissions.js similarity index 94% rename from app/authorization/server/startup.js rename to app/authorization/server/functions/upsertPermissions.js index b2d1c41cc966..76e97fcef480 100644 --- a/app/authorization/server/startup.js +++ b/app/authorization/server/functions/upsertPermissions.js @@ -1,11 +1,11 @@ /* eslint no-multi-spaces: 0 */ -import { Meteor } from 'meteor/meteor'; +import Roles from '../../../models/server/models/Roles'; +import Permissions from '../../../models/server/models/Permissions'; +import Settings from '../../../models/server/models/Settings'; +import { settings } from '../../../settings/server'; +import { getSettingPermissionId, CONSTANTS } from '../../lib'; -import { Roles, Permissions, Settings } from '../../models/server'; -import { settings } from '../../settings/server'; -import { getSettingPermissionId, CONSTANTS } from '../lib'; - -Meteor.startup(function() { +export const upsertPermissions = () => { // Note: // 1.if we need to create a role that can only edit channel message, but not edit group message // then we can define edit--message instead of edit-message @@ -140,8 +140,18 @@ Meteor.startup(function() { { _id: 'view-all-teams', roles: ['admin'] }, { _id: 'remove-closed-livechat-room', roles: ['livechat-manager', 'admin'] }, { _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] }, + { _id: 'manage-apps', roles: ['admin'] }, + { _id: 'post-readonly', roles: ['admin', 'owner', 'moderator'] }, + { _id: 'set-readonly', roles: ['admin', 'owner'] }, + { _id: 'set-react-when-readonly', roles: ['admin', 'owner'] }, + { _id: 'manage-cloud', roles: ['admin'] }, + { _id: 'manage-sounds', roles: ['admin'] }, + { _id: 'access-mailer', roles: ['admin'] }, + { _id: 'pin-message', roles: ['owner', 'moderator', 'admin'] }, + { _id: 'snippet-message', roles: ['owner', 'moderator', 'admin'] }, ]; + for (const permission of permissions) { Permissions.create(permission._id, permission.roles); } @@ -172,7 +182,7 @@ Meteor.startup(function() { selector.settingId = settingId; } - Permissions.find(selector).fetch().forEach( + Permissions.find(selector).forEach( function(permission) { previousSettingPermissions[permission._id] = permission; }); @@ -240,7 +250,7 @@ Meteor.startup(function() { createPermissionsForExistingSettings(); // register a callback for settings for be create in higher-level-packages - const createPermissionForAddedSetting = function(settingId) { + settings.on('*', function([settingId]) { const previousSettingPermissions = getPreviousPermissions(settingId); const setting = Settings.findOneById(settingId); if (setting) { @@ -248,7 +258,5 @@ Meteor.startup(function() { createSettingPermission(setting, previousSettingPermissions); } } - }; - - settings.onload('*', createPermissionForAddedSetting); -}); + }); +}; diff --git a/app/authorization/server/index.js b/app/authorization/server/index.js index c248f1c6e0e9..f4d7cc8c08b7 100644 --- a/app/authorization/server/index.js +++ b/app/authorization/server/index.js @@ -21,7 +21,6 @@ import './methods/removeRoleFromPermission'; import './methods/removeUserFromRole'; import './methods/saveRole'; import './streamer/permissions'; -import './startup'; export { getRoles, diff --git a/app/autolinker/server/settings.js b/app/autolinker/server/settings.js deleted file mode 100644 index 81e65d05c0d2..000000000000 --- a/app/autolinker/server/settings.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - const enableQuery = { - _id: 'AutoLinker', - value: true, - }; - - settings.add('AutoLinker', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nLabel: 'Enabled' }); - - settings.add('AutoLinker_StripPrefix', false, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nDescription: 'AutoLinker_StripPrefix_Description', enableQuery }); - settings.add('AutoLinker_Urls_Scheme', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); - settings.add('AutoLinker_Urls_www', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); - settings.add('AutoLinker_Urls_TLD', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); - settings.add('AutoLinker_UrlsRegExp', '(://|www\\.).+', { type: 'string', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); - settings.add('AutoLinker_Email', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); - settings.add('AutoLinker_Phone', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nDescription: 'AutoLinker_Phone_Description', enableQuery }); -}); diff --git a/app/autolinker/server/settings.ts b/app/autolinker/server/settings.ts new file mode 100644 index 000000000000..e8e9c60f1be4 --- /dev/null +++ b/app/autolinker/server/settings.ts @@ -0,0 +1,20 @@ +import { Meteor } from 'meteor/meteor'; + +import { settingsRegistry } from '../../settings/server'; + +Meteor.startup(function() { + const enableQuery = { + _id: 'AutoLinker', + value: true, + }; + + settingsRegistry.add('AutoLinker', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nLabel: 'Enabled' }); + + settingsRegistry.add('AutoLinker_StripPrefix', false, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nDescription: 'AutoLinker_StripPrefix_Description', enableQuery }); + settingsRegistry.add('AutoLinker_Urls_Scheme', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); + settingsRegistry.add('AutoLinker_Urls_www', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); + settingsRegistry.add('AutoLinker_Urls_TLD', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); + settingsRegistry.add('AutoLinker_UrlsRegExp', '(://|www\\.).+', { type: 'string', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); + settingsRegistry.add('AutoLinker_Email', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, enableQuery }); + settingsRegistry.add('AutoLinker_Phone', true, { type: 'boolean', group: 'Message', section: 'AutoLinker', public: true, i18nDescription: 'AutoLinker_Phone_Description', enableQuery }); +}); diff --git a/app/autotranslate/server/autotranslate.js b/app/autotranslate/server/autotranslate.js index 12e3f61ac943..152b8981b5db 100644 --- a/app/autotranslate/server/autotranslate.js +++ b/app/autotranslate/server/autotranslate.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { escapeHTML } from '@rocket.chat/string-helpers'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { callbacks } from '../../callbacks'; import { Subscriptions, Messages } from '../../models'; import { Markdown } from '../../markdown/server'; @@ -80,24 +80,6 @@ export class TranslationProviderRegistry { callbacks.add('afterSaveMessage', provider.translateMessage.bind(provider), callbacks.priority.MEDIUM, 'autotranslate'); } - - /** - * Make the activated provider by setting as the active. - */ - static loadActiveServiceProvider() { - /** Register the active service provider on the 'AfterSaveMessage' callback. - * So the registered provider will be invoked when a message is saved. - * All the other inactive service provider must be deactivated. - */ - settings.get('AutoTranslate_ServiceProvider', (key, providerName) => { - TranslationProviderRegistry.setCurrentProvider(providerName); - }); - - // Get Auto Translate Active flag - settings.get('AutoTranslate_Enabled', (key, value) => { - TranslationProviderRegistry.setEnable(value); - }); - } } /** @@ -352,5 +334,16 @@ export class AutoTranslate { } Meteor.startup(() => { - TranslationProviderRegistry.loadActiveServiceProvider(); + /** Register the active service provider on the 'AfterSaveMessage' callback. + * So the registered provider will be invoked when a message is saved. + * All the other inactive service provider must be deactivated. + */ + settings.watch('AutoTranslate_ServiceProvider', (providerName) => { + TranslationProviderRegistry.setCurrentProvider(providerName); + }); + + // Get Auto Translate Active flag + settings.watch('AutoTranslate_Enabled', (value) => { + TranslationProviderRegistry.setEnable(value); + }); }); diff --git a/app/autotranslate/server/deeplTranslate.js b/app/autotranslate/server/deeplTranslate.js index b45500602a3b..996d18d94783 100644 --- a/app/autotranslate/server/deeplTranslate.js +++ b/app/autotranslate/server/deeplTranslate.js @@ -28,7 +28,7 @@ class DeeplAutoTranslate extends AutoTranslate { this.name = 'deepl-translate'; this.apiEndPointUrl = 'https://api.deepl.com/v2/translate'; // Get the service provide API key. - settings.get('AutoTranslate_DeepLAPIKey', (key, value) => { + settings.watch('AutoTranslate_DeepLAPIKey', (value) => { this.apiKey = value; }); } diff --git a/app/autotranslate/server/googleTranslate.js b/app/autotranslate/server/googleTranslate.js index 88c5403e550b..24a70c3257e7 100644 --- a/app/autotranslate/server/googleTranslate.js +++ b/app/autotranslate/server/googleTranslate.js @@ -8,7 +8,7 @@ import _ from 'underscore'; import { AutoTranslate, TranslationProviderRegistry } from './autotranslate'; import { SystemLogger } from '../../../server/lib/logger/system'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; /** * Represents google translate class @@ -26,7 +26,7 @@ class GoogleAutoTranslate extends AutoTranslate { this.name = 'google-translate'; this.apiEndPointUrl = 'https://translation.googleapis.com/language/translate/v2'; // Get the service provide API key. - settings.get('AutoTranslate_GoogleAPIKey', (key, value) => { + settings.watch('AutoTranslate_GoogleAPIKey', (value) => { this.apiKey = value; }); } diff --git a/app/autotranslate/server/msTranslate.js b/app/autotranslate/server/msTranslate.js index 1c3da27e6c29..eb909a379248 100644 --- a/app/autotranslate/server/msTranslate.js +++ b/app/autotranslate/server/msTranslate.js @@ -8,7 +8,7 @@ import _ from 'underscore'; import { TranslationProviderRegistry, AutoTranslate } from './autotranslate'; import { msLogger } from './logger'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; /** * Microsoft translation service provider class representation. @@ -31,7 +31,7 @@ class MsAutoTranslate extends AutoTranslate { this.apiGetLanguages = 'https://api.cognitive.microsofttranslator.com/languages?api-version=3.0'; this.breakSentence = 'https://api.cognitive.microsofttranslator.com/breaksentence?api-version=3.0'; // Get the service provide API key. - settings.get('AutoTranslate_MicrosoftAPIKey', (key, value) => { + settings.watch('AutoTranslate_MicrosoftAPIKey', (value) => { this.apiKey = value; }); } diff --git a/app/autotranslate/server/settings.js b/app/autotranslate/server/settings.ts similarity index 80% rename from app/autotranslate/server/settings.js rename to app/autotranslate/server/settings.ts index d58f5a9b3533..e250f885d854 100644 --- a/app/autotranslate/server/settings.js +++ b/app/autotranslate/server/settings.ts @@ -1,16 +1,16 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.add('AutoTranslate_Enabled', false, { + settingsRegistry.add('AutoTranslate_Enabled', false, { type: 'boolean', group: 'Message', section: 'AutoTranslate', public: true, }); - settings.add('AutoTranslate_ServiceProvider', 'google-translate', { + settingsRegistry.add('AutoTranslate_ServiceProvider', 'google-translate', { type: 'select', group: 'Message', section: 'AutoTranslate', @@ -29,7 +29,7 @@ Meteor.startup(function() { public: true, }); - settings.add('AutoTranslate_GoogleAPIKey', '', { + settingsRegistry.add('AutoTranslate_GoogleAPIKey', '', { type: 'string', group: 'Message', section: 'AutoTranslate_Google', @@ -44,7 +44,7 @@ Meteor.startup(function() { }], }); - settings.add('AutoTranslate_DeepLAPIKey', '', { + settingsRegistry.add('AutoTranslate_DeepLAPIKey', '', { type: 'string', group: 'Message', section: 'AutoTranslate_DeepL', @@ -58,7 +58,7 @@ Meteor.startup(function() { }], }); - settings.add('AutoTranslate_MicrosoftAPIKey', '', { + settingsRegistry.add('AutoTranslate_MicrosoftAPIKey', '', { type: 'string', group: 'Message', section: 'AutoTranslate_Microsoft', diff --git a/app/blockstack/server/loginHandler.js b/app/blockstack/server/loginHandler.js index 53756efe8891..90ba370756d1 100644 --- a/app/blockstack/server/loginHandler.js +++ b/app/blockstack/server/loginHandler.js @@ -4,7 +4,7 @@ import { Accounts } from 'meteor/accounts-base'; import { updateOrCreateUser } from './userHandler'; import { handleAccessToken } from './tokenHandler'; import { logger } from './logger'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { Users } from '../../models'; import { setUserAvatar } from '../../lib'; diff --git a/app/blockstack/server/routes.js b/app/blockstack/server/routes.js index 81902030e426..eb6947113dee 100644 --- a/app/blockstack/server/routes.js +++ b/app/blockstack/server/routes.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { RocketChatAssets } from '../../assets/server'; WebApp.connectHandlers.use('/_blockstack/manifest', Meteor.bindEnvironment(function(req, res) { diff --git a/app/blockstack/server/settings.js b/app/blockstack/server/settings.js index f5e79db64975..d61fbb1b749f 100644 --- a/app/blockstack/server/settings.js +++ b/app/blockstack/server/settings.js @@ -1,9 +1,8 @@ -import _ from 'underscore'; import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; import { logger } from './logger'; -import { settings } from '../../settings'; +import { settings, settingsRegistry } from '../../settings/server'; const defaults = { enable: false, @@ -18,7 +17,7 @@ const defaults = { }; Meteor.startup(() => { - settings.addGroup('Blockstack', function() { + settingsRegistry.addGroup('Blockstack', function() { this.add('Blockstack_Enable', defaults.enable, { type: 'boolean', i18nLabel: 'Enable', @@ -43,7 +42,12 @@ const getSettings = () => Object.assign({}, defaults, { generateUsername: settings.get('Blockstack_Generate_Username'), }); -const configureService = _.debounce(Meteor.bindEnvironment(() => { + +// Add settings to auth provider configs on startup +settings.watchMultiple(['Blockstack_Enable', + 'Blockstack_Auth_Description', + 'Blockstack_ButtonLabelText', + 'Blockstack_Generate_Username'], () => { const serviceConfig = getSettings(); if (!serviceConfig.enable) { @@ -60,11 +64,4 @@ const configureService = _.debounce(Meteor.bindEnvironment(() => { }); logger.debug('Init Blockstack auth', serviceConfig); -}), 1000); - -// Add settings to auth provider configs on startup -Meteor.startup(() => { - settings.get(/^Blockstack_.+/, () => { - configureService(); - }); }); diff --git a/app/bot-helpers/server/index.js b/app/bot-helpers/server/index.js index e88fef7641d0..3436902e7536 100644 --- a/app/bot-helpers/server/index.js +++ b/app/bot-helpers/server/index.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { Users, Rooms } from '../../models'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { hasRole } from '../../authorization'; /** @@ -151,7 +151,7 @@ class BotHelpers { const botHelpers = new BotHelpers(); // init cursors with fields setting and update on setting change -settings.get('BotHelpers_userFields', function(settingKey, settingValue) { +settings.watch('BotHelpers_userFields', function(settingValue) { botHelpers.setupCursors(settingValue); }); diff --git a/app/bot-helpers/server/settings.js b/app/bot-helpers/server/settings.ts similarity index 74% rename from app/bot-helpers/server/settings.js rename to app/bot-helpers/server/settings.ts index dc4f5640c940..1a5891211b34 100644 --- a/app/bot-helpers/server/settings.js +++ b/app/bot-helpers/server/settings.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.addGroup('Bots', function() { + settingsRegistry.addGroup('Bots', function() { this.add('BotHelpers_userFields', '_id, name, username, emails, language, utcOffset', { type: 'string', section: 'Helpers', diff --git a/app/cas/server/cas_rocketchat.js b/app/cas/server/cas_rocketchat.js index 6e4fea97c122..2c8989af060f 100644 --- a/app/cas/server/cas_rocketchat.js +++ b/app/cas/server/cas_rocketchat.js @@ -2,12 +2,12 @@ import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; import { Logger } from '../../logger'; -import { settings } from '../../settings'; +import { settings, settingsRegistry } from '../../settings/server'; export const logger = new Logger('CAS'); Meteor.startup(function() { - settings.addGroup('CAS', function() { + settingsRegistry.addGroup('CAS', function() { this.add('CAS_enabled', false, { type: 'boolean', group: 'CAS', public: true }); this.add('CAS_base_url', '', { type: 'string', group: 'CAS', public: true }); this.add('CAS_login_url', '', { type: 'string', group: 'CAS', public: true }); @@ -67,6 +67,6 @@ function updateServices(/* record*/) { }, 2000); } -settings.get(/^CAS_.+/, (key, value) => { +settings.watchByRegex(/^CAS_.+/, (key, value) => { updateServices(value); }); diff --git a/app/channel-settings/server/index.js b/app/channel-settings/server/index.js index abbd2bf327ff..032eaa2905c9 100644 --- a/app/channel-settings/server/index.js +++ b/app/channel-settings/server/index.js @@ -1,4 +1,3 @@ -import './startup'; import './methods/saveRoomSettings'; export { saveRoomTopic } from './functions/saveRoomTopic'; diff --git a/app/channel-settings/server/startup.js b/app/channel-settings/server/startup.js deleted file mode 100644 index 5e9aeb7baaf2..000000000000 --- a/app/channel-settings/server/startup.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Permissions } from '../../models'; - -Meteor.startup(function() { - Permissions.create('post-readonly', ['admin', 'owner', 'moderator']); - Permissions.create('set-readonly', ['admin', 'owner']); - Permissions.create('set-react-when-readonly', ['admin', 'owner']); -}); diff --git a/app/cloud/server/functions/startRegisterWorkspace.js b/app/cloud/server/functions/startRegisterWorkspace.js index eba17c2649b1..bb533c79c580 100644 --- a/app/cloud/server/functions/startRegisterWorkspace.js +++ b/app/cloud/server/functions/startRegisterWorkspace.js @@ -2,7 +2,7 @@ import { HTTP } from 'meteor/http'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; import { syncWorkspace } from './syncWorkspace'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Settings } from '../../../models'; import { buildWorkspaceRegistrationData } from './buildRegistrationData'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -16,7 +16,7 @@ export function startRegisterWorkspace(resend = false) { return true; } - settings.updateById('Register_Server', true); + Settings.updateValueById('Register_Server', true); const regInfo = buildWorkspaceRegistrationData(); diff --git a/app/cloud/server/index.js b/app/cloud/server/index.js index b574f4fe4776..98ae3b710b96 100644 --- a/app/cloud/server/index.js +++ b/app/cloud/server/index.js @@ -8,19 +8,14 @@ import { getWorkspaceLicense } from './functions/getWorkspaceLicense'; import { getUserCloudAccessToken } from './functions/getUserCloudAccessToken'; import { getWorkspaceKey } from './functions/getWorkspaceKey'; import { syncWorkspace } from './functions/syncWorkspace'; -import { Permissions } from '../../models'; import { settings } from '../../settings/server'; -if (Permissions) { - Permissions.create('manage-cloud', ['admin']); -} - const licenseCronName = 'Cloud Workspace Sync'; Meteor.startup(function() { // run token/license sync if registered let TroubleshootDisableWorkspaceSync; - settings.get('Troubleshoot_Disable_Workspace_Sync', (key, value) => { + settings.watch('Troubleshoot_Disable_Workspace_Sync', (value) => { if (TroubleshootDisableWorkspaceSync === value) { return; } TroubleshootDisableWorkspaceSync = value; diff --git a/app/colors/server/settings.js b/app/colors/server/settings.js deleted file mode 100644 index e2224144c341..000000000000 --- a/app/colors/server/settings.js +++ /dev/null @@ -1,9 +0,0 @@ -import { settings } from '../../settings'; - -settings.add('HexColorPreview_Enabled', true, { - type: 'boolean', - i18nLabel: 'Enabled', - group: 'Message', - section: 'Hex_Color_Preview', - public: true, -}); diff --git a/app/colors/server/settings.ts b/app/colors/server/settings.ts new file mode 100644 index 000000000000..0351e8974ca5 --- /dev/null +++ b/app/colors/server/settings.ts @@ -0,0 +1,9 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.add('HexColorPreview_Enabled', true, { + type: 'boolean', + i18nLabel: 'Enabled', + group: 'Message', + section: 'Hex_Color_Preview', + public: true, +}); diff --git a/app/cors/client/index.js b/app/cors/client/index.js index e44dbe195eff..54f26cdf0e05 100644 --- a/app/cors/client/index.js +++ b/app/cors/client/index.js @@ -1 +1,11 @@ -import '../lib/common'; +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; + + +import { settings } from '../../settings/client'; + +Meteor.startup(function() { + Tracker.autorun(function() { + Meteor.absoluteUrl.defaultOptions.secure = Boolean(settings.get('Force_SSL')); + }); +}); diff --git a/app/cors/lib/common.js b/app/cors/lib/common.js deleted file mode 100644 index e9e53068854e..000000000000 --- a/app/cors/lib/common.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.onload('Force_SSL', function(key, value) { - Meteor.absoluteUrl.defaultOptions.secure = value; - }); -}); diff --git a/app/cors/server/cors.js b/app/cors/server/cors.js index f09ea4d7808d..80a1f7a385ab 100644 --- a/app/cors/server/cors.js +++ b/app/cors/server/cors.js @@ -4,14 +4,13 @@ import { Meteor } from 'meteor/meteor'; import { WebApp, WebAppInternals } from 'meteor/webapp'; import _ from 'underscore'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { Logger } from '../../logger'; const logger = new Logger('CORS'); - -settings.get('Enable_CSP', (_, enabled) => { +settings.watch('Enable_CSP', (enabled) => { WebAppInternals.setInlineScriptsAllowed(!enabled); }); diff --git a/app/cors/server/index.js b/app/cors/server/index.js index 95faa2aa07cf..da8ac6e5a117 100644 --- a/app/cors/server/index.js +++ b/app/cors/server/index.js @@ -1,2 +1,10 @@ import './cors'; -import '../lib/common'; +import { Meteor } from 'meteor/meteor'; + +import { settings } from '../../settings/server'; + +Meteor.startup(function() { + settings.watch('Force_SSL', (value) => { + Meteor.absoluteUrl.defaultOptions.secure = Boolean(value); + }); +}); diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index c21b4550bd97..d5b4b5b97ef3 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { SHA256 } from 'meteor/sha'; import { SyncedCron } from 'meteor/littledata:synced-cron'; import { Accounts } from 'meteor/accounts-base'; -import _ from 'underscore'; import { Logger } from '../../logger'; import { _setRealName } from '../../lib'; @@ -312,33 +311,26 @@ Accounts.registerLoginHandler('crowd', function(loginRequest) { const jobName = 'CROWD_Sync'; -const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounced() { - if (settings.get('CROWD_Sync_User_Data') !== true) { - logger.info('Disabling CROWD Background Sync'); - if (SyncedCron.nextScheduledAtDate(jobName)) { - SyncedCron.remove(jobName); - } - return; - } - - const crowd = new CROWD(); - - if (settings.get('CROWD_Sync_Interval')) { - logger.info('Enabling CROWD Background Sync'); - SyncedCron.add({ - name: jobName, - schedule: (parser) => parser.text(settings.get('CROWD_Sync_Interval')), - job() { - crowd.sync(); - }, - }); - } -}), 500); - Meteor.startup(() => { - Meteor.defer(() => { - settings.get('CROWD_Sync_Interval', addCronJob); - settings.get('CROWD_Sync_User_Data', addCronJob); + settings.watchMultiple(['CROWD_Sync_User_Data', 'CROWD_Sync_Interval'], function addCronJobDebounced([data, interval]) { + if (data !== true) { + logger.info('Disabling CROWD Background Sync'); + if (SyncedCron.nextScheduledAtDate(jobName)) { + SyncedCron.remove(jobName); + } + return; + } + const crowd = new CROWD(); + if (interval) { + logger.info('Enabling CROWD Background Sync'); + SyncedCron.add({ + name: jobName, + schedule: (parser) => parser.text(interval), + job() { + crowd.sync(); + }, + }); + } }); }); diff --git a/app/crowd/server/settings.js b/app/crowd/server/settings.ts similarity index 93% rename from app/crowd/server/settings.js rename to app/crowd/server/settings.ts index b58362967294..74488d84b7ae 100644 --- a/app/crowd/server/settings.js +++ b/app/crowd/server/settings.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.addGroup('AtlassianCrowd', function() { + settingsRegistry.addGroup('AtlassianCrowd', function() { const enableQuery = { _id: 'CROWD_Enable', value: true }; const enableSyncQuery = [enableQuery, { _id: 'CROWD_Sync_User_Data', value: true }]; diff --git a/app/custom-sounds/server/index.js b/app/custom-sounds/server/index.js index d6b84fae0269..34ce44c157b7 100644 --- a/app/custom-sounds/server/index.js +++ b/app/custom-sounds/server/index.js @@ -1,5 +1,4 @@ import './startup/custom-sounds'; -import './startup/permissions'; import './startup/settings'; import './methods/deleteCustomSound'; import './methods/insertOrUpdateSound'; diff --git a/app/custom-sounds/server/startup/permissions.js b/app/custom-sounds/server/startup/permissions.js deleted file mode 100644 index 9e66458ea367..000000000000 --- a/app/custom-sounds/server/startup/permissions.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Permissions } from '../../../models'; - -Meteor.startup(() => { - if (Permissions) { - Permissions.create('manage-sounds', ['admin']); - } -}); diff --git a/app/custom-sounds/server/startup/settings.js b/app/custom-sounds/server/startup/settings.ts similarity index 77% rename from app/custom-sounds/server/startup/settings.js rename to app/custom-sounds/server/startup/settings.ts index 442288baae35..ded4dc15cd4a 100644 --- a/app/custom-sounds/server/startup/settings.js +++ b/app/custom-sounds/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('CustomSoundsFilesystem', function() { +settingsRegistry.addGroup('CustomSoundsFilesystem', function() { this.add('CustomSounds_Storage_Type', 'GridFS', { type: 'select', values: [{ diff --git a/app/discussion/server/config.js b/app/discussion/server/config.js deleted file mode 100644 index 87780561842b..000000000000 --- a/app/discussion/server/config.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(() => { - settings.addGroup('Discussion', function() { - // the channel for which discussions are created if none is explicitly chosen - - this.add('Discussion_enabled', true, { - group: 'Discussion', - i18nLabel: 'Enable', - type: 'boolean', - public: true, - }); - }); - - const globalQuery = { - _id: 'RetentionPolicy_Enabled', - value: true, - }; - - settings.add('RetentionPolicy_DoNotPruneDiscussion', true, { - group: 'RetentionPolicy', - section: 'Global Policy', - type: 'boolean', - public: true, - i18nLabel: 'RetentionPolicy_DoNotPruneDiscussion', - i18nDescription: 'RetentionPolicy_DoNotPruneDiscussion_Description', - enableQuery: globalQuery, - }); - - settings.add('RetentionPolicy_DoNotPruneThreads', true, { - group: 'RetentionPolicy', - section: 'Global Policy', - type: 'boolean', - public: true, - i18nLabel: 'RetentionPolicy_DoNotPruneThreads', - i18nDescription: 'RetentionPolicy_DoNotPruneThreads_Description', - enableQuery: globalQuery, - }); -}); diff --git a/app/discussion/server/config.ts b/app/discussion/server/config.ts new file mode 100644 index 000000000000..c459c2ff2243 --- /dev/null +++ b/app/discussion/server/config.ts @@ -0,0 +1,37 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.addGroup('Discussion', function() { + // the channel for which discussions are created if none is explicitly chosen + + this.add('Discussion_enabled', true, { + group: 'Discussion', + i18nLabel: 'Enable', + type: 'boolean', + public: true, + }); +}); + +const globalQuery = { + _id: 'RetentionPolicy_Enabled', + value: true, +}; + +settingsRegistry.add('RetentionPolicy_DoNotPruneDiscussion', true, { + group: 'RetentionPolicy', + section: 'Global Policy', + type: 'boolean', + public: true, + i18nLabel: 'RetentionPolicy_DoNotPruneDiscussion', + i18nDescription: 'RetentionPolicy_DoNotPruneDiscussion_Description', + enableQuery: globalQuery, +}); + +settingsRegistry.add('RetentionPolicy_DoNotPruneThreads', true, { + group: 'RetentionPolicy', + section: 'Global Policy', + type: 'boolean', + public: true, + i18nLabel: 'RetentionPolicy_DoNotPruneThreads', + i18nDescription: 'RetentionPolicy_DoNotPruneThreads_Description', + enableQuery: globalQuery, +}); diff --git a/app/dolphin/server/startup.js b/app/dolphin/server/startup.js deleted file mode 100644 index bb4c871b18b1..000000000000 --- a/app/dolphin/server/startup.js +++ /dev/null @@ -1,10 +0,0 @@ -import { settings } from '../../settings'; - -settings.add('Accounts_OAuth_Dolphin_URL', '', { type: 'string', group: 'OAuth', public: true, section: 'Dolphin', i18nLabel: 'URL' }); -settings.add('Accounts_OAuth_Dolphin', false, { type: 'boolean', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Enable' }); -settings.add('Accounts_OAuth_Dolphin_id', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_id' }); -settings.add('Accounts_OAuth_Dolphin_secret', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Secret', secret: true }); -settings.add('Accounts_OAuth_Dolphin_login_style', 'redirect', { type: 'select', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [{ key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' }] }); -settings.add('Accounts_OAuth_Dolphin_button_label_text', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true }); -settings.add('Accounts_OAuth_Dolphin_button_label_color', '#FFFFFF', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true }); -settings.add('Accounts_OAuth_Dolphin_button_color', '#1d74f5', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true }); diff --git a/app/dolphin/server/startup.ts b/app/dolphin/server/startup.ts new file mode 100644 index 000000000000..66120fbc72fc --- /dev/null +++ b/app/dolphin/server/startup.ts @@ -0,0 +1,10 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.add('Accounts_OAuth_Dolphin_URL', '', { type: 'string', group: 'OAuth', public: true, section: 'Dolphin', i18nLabel: 'URL' }); +settingsRegistry.add('Accounts_OAuth_Dolphin', false, { type: 'boolean', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Enable' }); +settingsRegistry.add('Accounts_OAuth_Dolphin_id', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_id' }); +settingsRegistry.add('Accounts_OAuth_Dolphin_secret', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Secret', secret: true }); +settingsRegistry.add('Accounts_OAuth_Dolphin_login_style', 'redirect', { type: 'select', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [{ key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' }] }); +settingsRegistry.add('Accounts_OAuth_Dolphin_button_label_text', '', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true }); +settingsRegistry.add('Accounts_OAuth_Dolphin_button_label_color', '#FFFFFF', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true }); +settingsRegistry.add('Accounts_OAuth_Dolphin_button_color', '#1d74f5', { type: 'string', group: 'OAuth', section: 'Dolphin', i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true }); diff --git a/app/drupal/server/startup.js b/app/drupal/server/startup.ts similarity index 77% rename from app/drupal/server/startup.js rename to app/drupal/server/startup.ts index 749d51c065b2..16118de8c685 100644 --- a/app/drupal/server/startup.js +++ b/app/drupal/server/startup.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('Drupal', function() { const enableQuery = { _id: 'Accounts_OAuth_Drupal', @@ -11,6 +11,6 @@ settings.addGroup('OAuth', function() { this.add('API_Drupal_URL', '', { type: 'string', public: true, enableQuery, i18nDescription: 'API_Drupal_URL_Description' }); this.add('Accounts_OAuth_Drupal_id', '', { type: 'string', enableQuery }); this.add('Accounts_OAuth_Drupal_secret', '', { type: 'string', enableQuery, secret: true }); - this.add('Accounts_OAuth_Drupal_callback_url', '_oauth/drupal', { type: 'relativeUrl', readonly: true, force: true, enableQuery }); + this.add('Accounts_OAuth_Drupal_callback_url', '_oauth/drupal', { type: 'relativeUrl', readonly: true, enableQuery }); }); }); diff --git a/app/e2e/server/settings.js b/app/e2e/server/settings.ts similarity index 79% rename from app/e2e/server/settings.js rename to app/e2e/server/settings.ts index e15f5e4df1ab..3b3aad9e6dc7 100644 --- a/app/e2e/server/settings.js +++ b/app/e2e/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('E2E Encryption', function() { +settingsRegistry.addGroup('E2E Encryption', function() { this.add('E2E_Enable', false, { type: 'boolean', i18nLabel: 'Enabled', diff --git a/app/emoji-custom/server/startup/settings.js b/app/emoji-custom/server/startup/settings.ts similarity index 77% rename from app/emoji-custom/server/startup/settings.js rename to app/emoji-custom/server/startup/settings.ts index 9b6d84a29310..b5ef6d29ac19 100644 --- a/app/emoji-custom/server/startup/settings.js +++ b/app/emoji-custom/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('EmojiCustomFilesystem', function() { +settingsRegistry.addGroup('EmojiCustomFilesystem', function() { this.add('EmojiUpload_Storage_Type', 'GridFS', { type: 'select', values: [{ diff --git a/app/error-handler/server/lib/RocketChat.ErrorHandler.js b/app/error-handler/server/lib/RocketChat.ErrorHandler.js index a542d801fe72..1552f6dfb670 100644 --- a/app/error-handler/server/lib/RocketChat.ErrorHandler.js +++ b/app/error-handler/server/lib/RocketChat.ErrorHandler.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Users, Rooms } from '../../../models'; import { sendMessage } from '../../../lib'; @@ -13,7 +13,7 @@ class ErrorHandler { Meteor.startup(() => { this.registerHandlers(); - settings.get('Log_Exceptions_to_Channel', (key, value) => { + settings.watch('Log_Exceptions_to_Channel', (value) => { this.rid = null; const roomName = value.trim(); if (roomName) { diff --git a/app/error-handler/server/startup/settings.js b/app/error-handler/server/startup/settings.js deleted file mode 100644 index 77421c3fb2a5..000000000000 --- a/app/error-handler/server/startup/settings.js +++ /dev/null @@ -1,5 +0,0 @@ -import { settings } from '../../../settings'; - -settings.addGroup('Logs', function() { - this.add('Log_Exceptions_to_Channel', '', { type: 'string' }); -}); diff --git a/app/error-handler/server/startup/settings.ts b/app/error-handler/server/startup/settings.ts new file mode 100644 index 000000000000..589240c4ba21 --- /dev/null +++ b/app/error-handler/server/startup/settings.ts @@ -0,0 +1,5 @@ +import { settingsRegistry } from '../../../settings/server'; + +settingsRegistry.addGroup('Logs', function() { + this.add('Log_Exceptions_to_Channel', '', { type: 'string' }); +}); diff --git a/app/federation/server/startup/settings.js b/app/federation/server/startup/settings.ts similarity index 85% rename from app/federation/server/startup/settings.js rename to app/federation/server/startup/settings.ts index a94caaaeff25..79da62054bb4 100644 --- a/app/federation/server/startup/settings.js +++ b/app/federation/server/startup/settings.ts @@ -1,7 +1,6 @@ -import { debounce } from 'underscore'; import { Meteor } from 'meteor/meteor'; -import { settings } from '../../../settings/server'; +import { settingsRegistry, settings } from '../../../settings/server'; import { updateStatus, updateEnabled, isRegisteringOrEnabled } from '../functions/helpers'; import { getFederationDomain } from '../lib/getFederationDomain'; import { getFederationDiscoveryMethod } from '../lib/getFederationDiscoveryMethod'; @@ -14,7 +13,7 @@ import { STATUS_ENABLED, STATUS_REGISTERING, STATUS_ERROR_REGISTERING, STATUS_DI Meteor.startup(function() { const federationPublicKey = FederationKeys.getPublicKeyString(); - settings.addGroup('Federation', function() { + settingsRegistry.addGroup('Federation', function() { this.add('FEDERATION_Enabled', false, { type: 'boolean', i18nLabel: 'Enabled', @@ -34,7 +33,7 @@ Meteor.startup(function() { i18nLabel: 'FEDERATION_Domain', i18nDescription: 'FEDERATION_Domain_Description', alert: 'FEDERATION_Domain_Alert', - disableReset: true, + // disableReset: true, }); this.add('FEDERATION_Public_Key', federationPublicKey, { @@ -66,7 +65,7 @@ Meteor.startup(function() { }); }); -const updateSettings = debounce(Meteor.bindEnvironment(function() { +const updateSettings = function() { // Get the key pair if (getFederationDiscoveryMethod() === 'hub' && !isRegisteringOrEnabled()) { @@ -86,9 +85,10 @@ const updateSettings = debounce(Meteor.bindEnvironment(function() { } else { updateStatus(STATUS_ENABLED); } -}), 150); +}; -function enableOrDisable(key, value) { +// Add settings listeners +settings.watch('FEDERATION_Enabled', function enableOrDisable(value) { setupLogger.info(`Federation is ${ value ? 'enabled' : 'disabled' }`); if (value) { @@ -102,9 +102,6 @@ function enableOrDisable(key, value) { } value && updateSettings(); -} +}); -// Add settings listeners -settings.get('FEDERATION_Enabled', enableOrDisable); -settings.get('FEDERATION_Domain', updateSettings); -settings.get('FEDERATION_Discovery_Method', updateSettings); +settings.watchMultiple(['FEDERATION_Discovery_Method', 'FEDERATION_Domain'], updateSettings); diff --git a/app/file-upload/server/config/AmazonS3.js b/app/file-upload/server/config/AmazonS3.js index efbef8d05651..2218fdf7488f 100644 --- a/app/file-upload/server/config/AmazonS3.js +++ b/app/file-upload/server/config/AmazonS3.js @@ -109,4 +109,4 @@ const configure = _.debounce(function() { AmazonS3UserDataFiles.store = FileUpload.configureUploadsStore('AmazonS3', AmazonS3UserDataFiles.name, config); }, 500); -settings.get(/^FileUpload_S3_/, configure); +settings.watchByRegex(/^FileUpload_S3_/, configure); diff --git a/app/file-upload/server/config/FileSystem.js b/app/file-upload/server/config/FileSystem.js index c0119eaca6f0..9890ebceeb90 100644 --- a/app/file-upload/server/config/FileSystem.js +++ b/app/file-upload/server/config/FileSystem.js @@ -2,9 +2,8 @@ import fs from 'fs'; import { Meteor } from 'meteor/meteor'; import { UploadFS } from 'meteor/jalik:ufs'; -import _ from 'underscore'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { FileUploadClass, FileUpload } from '../lib/FileUpload'; import { getFileRange, setRangeHeaders } from '../lib/ranges'; @@ -120,7 +119,7 @@ const FileSystemUserDataFiles = new FileUploadClass({ }, }); -const createFileSystemStore = _.debounce(function() { +settings.watch('FileUpload_FileSystemPath', function() { const options = { path: settings.get('FileUpload_FileSystemPath'), // '/tmp/uploads/photos', }; @@ -131,6 +130,4 @@ const createFileSystemStore = _.debounce(function() { // DEPRECATED backwards compatibililty (remove) UploadFS.getStores().fileSystem = UploadFS.getStores()[FileSystemUploads.name]; -}, 500); - -settings.get('FileUpload_FileSystemPath', createFileSystemStore); +}); diff --git a/app/file-upload/server/config/GoogleStorage.js b/app/file-upload/server/config/GoogleStorage.js index 1717f38c4240..1e2da55951e5 100644 --- a/app/file-upload/server/config/GoogleStorage.js +++ b/app/file-upload/server/config/GoogleStorage.js @@ -93,4 +93,4 @@ const configure = _.debounce(function() { GoogleCloudStorageUserDataFiles.store = FileUpload.configureUploadsStore('GoogleStorage', GoogleCloudStorageUserDataFiles.name, config); }, 500); -settings.get(/^FileUpload_GoogleStorage_/, configure); +settings.watchByRegex(/^FileUpload_GoogleStorage_/, configure); diff --git a/app/file-upload/server/config/Webdav.js b/app/file-upload/server/config/Webdav.js index 2386b40ae141..ed6fc3dd7770 100644 --- a/app/file-upload/server/config/Webdav.js +++ b/app/file-upload/server/config/Webdav.js @@ -69,4 +69,4 @@ const configure = _.debounce(function() { WebdavUserDataFiles.store = FileUpload.configureUploadsStore('Webdav', WebdavUserDataFiles.name, config); }, 500); -settings.get(/^FileUpload_Webdav_/, configure); +settings.watchByRegex(/^FileUpload_Webdav_/, configure); diff --git a/app/file-upload/server/config/_configUploadStorage.js b/app/file-upload/server/config/_configUploadStorage.js index b0b656b5c468..a21f873764b4 100644 --- a/app/file-upload/server/config/_configUploadStorage.js +++ b/app/file-upload/server/config/_configUploadStorage.js @@ -20,4 +20,4 @@ const configStore = _.debounce(() => { } }, 1000); -settings.get(/^FileUpload_/, configStore); +settings.watchByRegex(/^FileUpload_/, configStore); diff --git a/app/file-upload/server/lib/FileUpload.js b/app/file-upload/server/lib/FileUpload.js index 22e1cfc210fb..aa8d49ab408e 100644 --- a/app/file-upload/server/lib/FileUpload.js +++ b/app/file-upload/server/lib/FileUpload.js @@ -33,7 +33,7 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; const cookie = new Cookies(); let maxFileSize = 0; -settings.get('FileUpload_MaxFileSize', function(key, value) { +settings.watch('FileUpload_MaxFileSize', function(value) { try { maxFileSize = parseInt(value); } catch (e) { diff --git a/app/file-upload/server/methods/getS3FileUrl.js b/app/file-upload/server/methods/getS3FileUrl.js index 9cd915c5e77c..f68f720d171b 100644 --- a/app/file-upload/server/methods/getS3FileUrl.js +++ b/app/file-upload/server/methods/getS3FileUrl.js @@ -1,12 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { UploadFS } from 'meteor/jalik:ufs'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Uploads } from '../../../models'; let protectedFiles; -settings.get('FileUpload_ProtectFiles', function(key, value) { +settings.watch('FileUpload_ProtectFiles', function(value) { protectedFiles = value; }); diff --git a/app/file-upload/server/startup/settings.js b/app/file-upload/server/startup/settings.ts similarity index 98% rename from app/file-upload/server/startup/settings.js rename to app/file-upload/server/startup/settings.ts index 4b228108e86b..f0fa109e3d7a 100644 --- a/app/file-upload/server/startup/settings.js +++ b/app/file-upload/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('FileUpload', function() { +settingsRegistry.addGroup('FileUpload', function() { this.add('FileUpload_Enabled', true, { type: 'boolean', public: true, diff --git a/app/github-enterprise/server/startup.js b/app/github-enterprise/server/startup.ts similarity index 77% rename from app/github-enterprise/server/startup.js rename to app/github-enterprise/server/startup.ts index c67ec14f8162..de3d3b8af705 100644 --- a/app/github-enterprise/server/startup.js +++ b/app/github-enterprise/server/startup.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('GitHub Enterprise', function() { const enableQuery = { _id: 'Accounts_OAuth_GitHub_Enterprise', @@ -11,6 +11,6 @@ settings.addGroup('OAuth', function() { this.add('API_GitHub_Enterprise_URL', '', { type: 'string', public: true, enableQuery, i18nDescription: 'API_GitHub_Enterprise_URL_Description' }); this.add('Accounts_OAuth_GitHub_Enterprise_id', '', { type: 'string', enableQuery, secret: true }); this.add('Accounts_OAuth_GitHub_Enterprise_secret', '', { type: 'string', enableQuery, secret: true }); - this.add('Accounts_OAuth_GitHub_Enterprise_callback_url', '_oauth/github_enterprise', { type: 'relativeUrl', readonly: true, force: true, enableQuery }); + this.add('Accounts_OAuth_GitHub_Enterprise_callback_url', '_oauth/github_enterprise', { type: 'relativeUrl', readonly: true, enableQuery }); }); }); diff --git a/app/gitlab/server/startup.js b/app/gitlab/server/startup.ts similarity index 82% rename from app/gitlab/server/startup.js rename to app/gitlab/server/startup.ts index 2c98e63db7cb..81e4edb74425 100644 --- a/app/gitlab/server/startup.js +++ b/app/gitlab/server/startup.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('GitLab', function() { const enableQuery = { _id: 'Accounts_OAuth_Gitlab', @@ -13,6 +13,6 @@ settings.addGroup('OAuth', function() { this.add('Accounts_OAuth_Gitlab_secret', '', { type: 'string', enableQuery, secret: true }); this.add('Accounts_OAuth_Gitlab_identity_path', '/api/v4/user', { type: 'string', public: true, enableQuery }); this.add('Accounts_OAuth_Gitlab_merge_users', false, { type: 'boolean', public: true, enableQuery }); - this.add('Accounts_OAuth_Gitlab_callback_url', '_oauth/gitlab', { type: 'relativeUrl', readonly: true, force: true, enableQuery }); + this.add('Accounts_OAuth_Gitlab_callback_url', '_oauth/gitlab', { type: 'relativeUrl', readonly: true, enableQuery }); }); }); diff --git a/app/iframe-login/server/iframe_rocketchat.js b/app/iframe-login/server/iframe_rocketchat.ts similarity index 80% rename from app/iframe-login/server/iframe_rocketchat.js rename to app/iframe-login/server/iframe_rocketchat.ts index b23285be8e6f..675db0dfff51 100644 --- a/app/iframe-login/server/iframe_rocketchat.js +++ b/app/iframe-login/server/iframe_rocketchat.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.addGroup('Accounts', function() { + settingsRegistry.addGroup('Accounts', function() { this.section('Iframe', function() { this.add('Accounts_iframe_enabled', false, { type: 'boolean', public: true }); this.add('Accounts_iframe_url', '', { type: 'string', public: true }); diff --git a/app/importer-hipchat-enterprise/server/importer.js b/app/importer-hipchat-enterprise/server/importer.js index b5b2fc2e43b7..d3482df088d9 100644 --- a/app/importer-hipchat-enterprise/server/importer.js +++ b/app/importer-hipchat-enterprise/server/importer.js @@ -3,28 +3,12 @@ import path from 'path'; import fs from 'fs'; import { Meteor } from 'meteor/meteor'; -import TurndownService from 'turndown'; import { Base, ProgressStep, } from '../../importer/server'; -const turndownService = new TurndownService({ - strongDelimiter: '*', - hr: '', - br: '\n', -}); - -turndownService.addRule('strikethrough', { - filter: 'img', - - replacement(content, node) { - const src = node.getAttribute('src') || ''; - const alt = node.alt || node.title || src; - return src ? `[${ alt }](${ src })` : ''; - }, -}); export class HipChatEnterpriseImporter extends Base { constructor(info, importRecord) { @@ -151,6 +135,30 @@ export class HipChatEnterpriseImporter extends Base { return count; } + get turndownService() { + const TurndownService = Promise.await(import('turndown')).default; + + const turndownService = new TurndownService({ + strongDelimiter: '*', + hr: '', + br: '\n', + }); + + turndownService.addRule('strikethrough', { + filter: 'img', + + replacement(content, node) { + const src = node.getAttribute('src') || ''; + const alt = node.alt || node.title || src; + return src ? `[${ alt }](${ src })` : ''; + }, + }); + + this.turndownService = turndownService; + + return turndownService; + } + convertImportedMessage(importedMessage, rid, type) { const idType = type === 'private' ? type : `${ rid }-${ type }`; const newId = `hipchatenterprise-${ idType }-${ importedMessage.id }`; @@ -167,7 +175,7 @@ export class HipChatEnterpriseImporter extends Base { const text = importedMessage.message; if (importedMessage.message_format === 'html') { - newMessage.msg = turndownService.turndown(text); + newMessage.msg = this.turndownService.turndown(text); } else if (text.startsWith('/me ')) { newMessage.msg = `${ text.replace(/\/me /, '_') }_`; } else { diff --git a/app/irc/server/irc-settings.js b/app/irc/server/irc-settings.ts similarity index 92% rename from app/irc/server/irc-settings.js rename to app/irc/server/irc-settings.ts index 9ad6ad0ca5fb..9e1bb613e690 100644 --- a/app/irc/server/irc-settings.js +++ b/app/irc/server/irc-settings.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.addGroup('IRC_Federation', function() { + settingsRegistry.addGroup('IRC_Federation', function() { this.add('IRC_Enabled', false, { type: 'boolean', i18nLabel: 'Enabled', diff --git a/app/issuelinks/server/settings.js b/app/issuelinks/server/settings.ts similarity index 67% rename from app/issuelinks/server/settings.js rename to app/issuelinks/server/settings.ts index 94db4a32aa9d..ad38e55fd6b5 100644 --- a/app/issuelinks/server/settings.js +++ b/app/issuelinks/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.add('IssueLinks_Enabled', false, { +settingsRegistry.add('IssueLinks_Enabled', false, { type: 'boolean', i18nLabel: 'Enabled', i18nDescription: 'IssueLinks_Incompatible', @@ -9,7 +9,7 @@ settings.add('IssueLinks_Enabled', false, { public: true, }); -settings.add('IssueLinks_Template', '', { +settingsRegistry.add('IssueLinks_Template', '', { type: 'string', i18nLabel: 'IssueLinks_LinkTemplate', i18nDescription: 'IssueLinks_LinkTemplate_Description', diff --git a/app/katex/server/settings.js b/app/katex/server/settings.ts similarity index 67% rename from app/katex/server/settings.js rename to app/katex/server/settings.ts index 43f1fbbd0b1a..1c5cd9381a16 100644 --- a/app/katex/server/settings.js +++ b/app/katex/server/settings.ts @@ -1,20 +1,20 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { const enableQuery = { _id: 'Katex_Enabled', value: true, }; - settings.add('Katex_Enabled', true, { + settingsRegistry.add('Katex_Enabled', true, { type: 'boolean', group: 'Message', section: 'Katex', public: true, - i18n: 'Katex_Enabled_Description', + i18nDescription: 'Katex_Enabled_Description', }); - settings.add('Katex_Parenthesis_Syntax', true, { + settingsRegistry.add('Katex_Parenthesis_Syntax', true, { type: 'boolean', group: 'Message', section: 'Katex', @@ -22,7 +22,7 @@ Meteor.startup(function() { enableQuery, i18nDescription: 'Katex_Parenthesis_Syntax_Description', }); - return settings.add('Katex_Dollar_Syntax', false, { + return settingsRegistry.add('Katex_Dollar_Syntax', false, { type: 'boolean', group: 'Message', section: 'Katex', diff --git a/app/lib/README.md b/app/lib/README.md index 547fa1ee25d9..2308f1e0da4f 100644 --- a/app/lib/README.md +++ b/app/lib/README.md @@ -8,7 +8,7 @@ This package contains the main libraries of Rocket.Chat. This is an example to create settings: ```javascript -RocketChat.settings.addGroup('Settings_Group', function() { +settingsRegistry.addGroup('Settings_Group', function() { this.add('SettingInGroup', 'default_value', { type: 'boolean', public: true }); this.section('Group_Section', function() { @@ -24,7 +24,7 @@ RocketChat.settings.addGroup('Settings_Group', function() { }); ``` -`RocketChat.settings.add` type: +`settingsRegistry.add` type: * `string` - Stores a string value * Additional options: @@ -40,7 +40,7 @@ RocketChat.settings.addGroup('Settings_Group', function() { * `actionText`: Translatable value of the button * `asset` - Creates an upload field -`RocketChat.settings.add` options: +`settingsRegistry.add` options: * `description` - Description of the setting * `public` - Boolean to set if the setting should be sent to client or not diff --git a/app/lib/client/index.js b/app/lib/client/index.js index 5619d5776428..321f6f1e5bf3 100644 --- a/app/lib/client/index.js +++ b/app/lib/client/index.js @@ -1,4 +1,4 @@ -import '../lib/startup/settingsOnLoadSiteUrl'; +import './startup/settingsOnLoadSiteUrl'; import '../lib/MessageTypes'; import './OAuthProxy'; import './methods/sendMessage'; diff --git a/app/lib/client/startup/settingsOnLoadSiteUrl.ts b/app/lib/client/startup/settingsOnLoadSiteUrl.ts new file mode 100644 index 000000000000..c61ec53e19d1 --- /dev/null +++ b/app/lib/client/startup/settingsOnLoadSiteUrl.ts @@ -0,0 +1,18 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; + +import { settings } from '../../../settings/client'; + +Meteor.startup(() => { + Tracker.autorun(() => { + const value = settings.get('Site_Url'); + if (value == null || value.trim() === '') { + return; + } + (window as any).__meteor_runtime_config__.ROOT_URL = value; + + if (Meteor.absoluteUrl.defaultOptions && Meteor.absoluteUrl.defaultOptions.rootUrl) { + Meteor.absoluteUrl.defaultOptions.rootUrl = value; + } + }); +}); diff --git a/app/lib/server/functions/addOAuthService.js b/app/lib/server/functions/addOAuthService.js deleted file mode 100644 index 2308a0bf477b..000000000000 --- a/app/lib/server/functions/addOAuthService.js +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint no-multi-spaces: 0 */ -/* eslint comma-spacing: 0 */ -import { capitalize } from '@rocket.chat/string-helpers'; - -import { settings } from '../../../settings'; - -export function addOAuthService(name, values = {}) { - name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); - name = capitalize(name); - settings.add(`Accounts_OAuth_Custom-${ name }` , values.enabled || false , { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Enable', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-url` , values.serverURL || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'URL', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-token_path` , values.tokenPath || '/oauth/token' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Token_Path', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-token_sent_via` , values.tokenSentVia || 'payload' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Token_Sent_Via', persistent: true, values: [{ key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' }] }); - settings.add(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`, values.identityTokenSentVia || 'default' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Identity_Token_Sent_Via', persistent: true, values: [{ key: 'default', i18nLabel: 'Same_As_Token_Sent_Via' }, { key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' }] }); - settings.add(`Accounts_OAuth_Custom-${ name }-identity_path` , values.identityPath || '/me' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Identity_Path', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-authorize_path` , values.authorizePath || '/oauth/authorize' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Authorize_Path', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-scope` , values.scope || 'openid' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Scope', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-access_token_param` , values.accessTokenParam || 'access_token' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Access_Token_Param', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-id` , values.clientId || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_id', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-secret` , values.clientSecret || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Secret', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-login_style` , values.loginStyle || 'popup' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [{ key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' }] }); - settings.add(`Accounts_OAuth_Custom-${ name }-button_label_text` , values.buttonLabelText || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-button_label_color` , values.buttonLabelColor || '#FFFFFF' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-button_color` , values.buttonColor || '#1d74f5' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-key_field` , values.keyField || 'username' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Key_Field', persistent: true, values: [{ key: 'username', i18nLabel: 'Username' }, { key: 'email', i18nLabel: 'Email' }] }); - settings.add(`Accounts_OAuth_Custom-${ name }-username_field` , values.usernameField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Username_Field', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-email_field` , values.emailField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Email_Field', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-name_field` , values.nameField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Name_Field', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-avatar_field` , values.avatarField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Avatar_Field', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-roles_claim` , values.rolesClaim || 'roles', { type: 'string', - group: 'OAuth', - section: `Custom OAuth: ${ name }`, - i18nLabel: 'Accounts_OAuth_Custom_Roles_Claim', - enterprise: true, - invalidValue: 'roles', - modules: ['oauth-enterprise'] }); - settings.add(`Accounts_OAuth_Custom-${ name }-groups_claim` , values.groupsClaim || 'groups', { type: 'string', - group: 'OAuth', - section: `Custom OAuth: ${ name }`, - i18nLabel: 'Accounts_OAuth_Custom_Groups_Claim', - enterprise: true, - invalidValue: 'groups', - modules: ['oauth-enterprise'] }); - - settings.add(`Accounts_OAuth_Custom-${ name }-channels_admin` , values.channelsAdmin || 'rocket.cat' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Channel_Admin', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-map_channels` , values.mapChannels || false, { type: 'boolean', - group: 'OAuth', - section: `Custom OAuth: ${ name }`, - i18nLabel: 'Accounts_OAuth_Custom_Map_Channels', - enterprise: true, - invalidValue: false, - modules: ['oauth-enterprise'] }); - settings.add(`Accounts_OAuth_Custom-${ name }-merge_roles` , values.mergeRoles || false, { type: 'boolean', - group: 'OAuth', - section: `Custom OAuth: ${ name }`, - i18nLabel: 'Accounts_OAuth_Custom_Merge_Roles', - enterprise: true, - invalidValue: false, - modules: ['oauth-enterprise'] }); - settings.add(`Accounts_OAuth_Custom-${ name }-merge_users`, values.mergeUsers || false, { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Merge_Users', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-show_button` , values.showButton || true , { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Show_Button_On_Login_Page', persistent: true }); - settings.add(`Accounts_OAuth_Custom-${ name }-groups_channel_map` , values.channelsMap || '{\n\t"rocket-admin": "admin",\n\t"tech-support": "support"\n}' , { type: 'code' , multiline: true, code: 'application/json', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Channel_Map', persistent: true }); -} diff --git a/app/lib/server/functions/addOAuthService.ts b/app/lib/server/functions/addOAuthService.ts new file mode 100644 index 000000000000..a41bb2ee174e --- /dev/null +++ b/app/lib/server/functions/addOAuthService.ts @@ -0,0 +1,63 @@ +/* eslint no-multi-spaces: 0 */ +/* eslint comma-spacing: 0 */ +import { capitalize } from '@rocket.chat/string-helpers'; + +import { settingsRegistry } from '../../../settings/server'; + +export function addOAuthService(name: string, values: { [k: string]: string | boolean| undefined } = {}): void { + name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); + name = capitalize(name); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }` , values.enabled || false , { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Enable', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-url` , values.serverURL || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'URL', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-token_path` , values.tokenPath || '/oauth/token' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Token_Path', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-token_sent_via` , values.tokenSentVia || 'payload' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Token_Sent_Via', persistent: true, values: [{ key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' }] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`, values.identityTokenSentVia || 'default' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Identity_Token_Sent_Via', persistent: true, values: [{ key: 'default', i18nLabel: 'Same_As_Token_Sent_Via' }, { key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' }] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-identity_path` , values.identityPath || '/me' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Identity_Path', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-authorize_path` , values.authorizePath || '/oauth/authorize' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Authorize_Path', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-scope` , values.scope || 'openid' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Scope', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-access_token_param` , values.accessTokenParam || 'access_token' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Access_Token_Param', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-id` , values.clientId || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_id', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-secret` , values.clientSecret || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Secret', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-login_style` , values.loginStyle || 'popup' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [{ key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' }] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-button_label_text` , values.buttonLabelText || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-button_label_color` , values.buttonLabelColor || '#FFFFFF' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-button_color` , values.buttonColor || '#1d74f5' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-key_field` , values.keyField || 'username' , { type: 'select' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Key_Field', persistent: true, values: [{ key: 'username', i18nLabel: 'Username' }, { key: 'email', i18nLabel: 'Email' }] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-username_field` , values.usernameField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Username_Field', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-email_field` , values.emailField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Email_Field', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-name_field` , values.nameField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Name_Field', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-avatar_field` , values.avatarField || '' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Avatar_Field', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-roles_claim` , values.rolesClaim || 'roles', { type: 'string', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Roles_Claim', + enterprise: true, + invalidValue: 'roles', + modules: ['oauth-enterprise'] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-groups_claim` , values.groupsClaim || 'groups', { type: 'string', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Groups_Claim', + enterprise: true, + invalidValue: 'groups', + modules: ['oauth-enterprise'] }); + + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-channels_admin` , values.channelsAdmin || 'rocket.cat' , { type: 'string' , group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Channel_Admin', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-map_channels` , values.mapChannels || false, { type: 'boolean', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Map_Channels', + enterprise: true, + invalidValue: false, + modules: ['oauth-enterprise'] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-merge_roles` , values.mergeRoles || false, { type: 'boolean', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Merge_Roles', + enterprise: true, + invalidValue: false, + modules: ['oauth-enterprise'] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-merge_users`, values.mergeUsers || false, { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Merge_Users', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-show_button` , values.showButton || true , { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Show_Button_On_Login_Page', persistent: true }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-groups_channel_map` , values.channelsMap || '{\n\t"rocket-admin": "admin",\n\t"tech-support": "support"\n}' , { type: 'code' , multiline: true, code: 'application/json', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Channel_Map', persistent: true }); +} diff --git a/app/lib/server/functions/checkUsernameAvailability.js b/app/lib/server/functions/checkUsernameAvailability.js index f152e93266b7..305922981ec4 100644 --- a/app/lib/server/functions/checkUsernameAvailability.js +++ b/app/lib/server/functions/checkUsernameAvailability.js @@ -3,7 +3,7 @@ import s from 'underscore.string'; import _ from 'underscore'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Team } from '../../../../server/sdk'; import { validateName } from './validateName'; @@ -11,7 +11,7 @@ let usernameBlackList = []; const toRegExp = (username) => new RegExp(`^${ escapeRegExp(username).trim() }$`, 'i'); -settings.get('Accounts_BlockedUsernameList', (key, value) => { +settings.watch('Accounts_BlockedUsernameList', (value) => { usernameBlackList = ['all', 'here'].concat(value.split(',')).map(toRegExp); }); diff --git a/app/lib/server/functions/getFullUserData.js b/app/lib/server/functions/getFullUserData.js index 1a33ac474316..c12ab44230d9 100644 --- a/app/lib/server/functions/getFullUserData.js +++ b/app/lib/server/functions/getFullUserData.js @@ -1,5 +1,5 @@ import { Logger } from '../../../logger'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { Users } from '../../../models/server'; import { hasPermission } from '../../../authorization'; @@ -35,7 +35,7 @@ const fullFields = { let publicCustomFields = {}; let customFields = {}; -settings.get('Accounts_CustomFields', (key, value) => { +settings.watch('Accounts_CustomFields', (value) => { publicCustomFields = {}; customFields = {}; diff --git a/app/lib/server/functions/notifications/email.js b/app/lib/server/functions/notifications/email.js index 3583899487ea..9a8b077da359 100644 --- a/app/lib/server/functions/notifications/email.js +++ b/app/lib/server/functions/notifications/email.js @@ -4,7 +4,7 @@ import s from 'underscore.string'; import { escapeHTML } from '@rocket.chat/string-helpers'; import * as Mailer from '../../../../mailer'; -import { settings } from '../../../../settings'; +import { settings } from '../../../../settings/server'; import { roomTypes } from '../../../../utils'; import { metrics } from '../../../../metrics'; import { callbacks } from '../../../../callbacks'; @@ -13,7 +13,7 @@ import { getURL } from '../../../../utils/server'; let advice = ''; let goToMessage = ''; Meteor.startup(() => { - settings.get('email_style', function() { + settings.watch('email_style', function() { goToMessage = Mailer.inlinecss('

{Offline_Link_Message}

'); }); Mailer.getTemplate('Email_Footer_Direct_Reply', (value) => { diff --git a/app/lib/server/lib/bugsnag.ts b/app/lib/server/lib/bugsnag.ts index 8686d581d7b1..ce55ec3a6516 100644 --- a/app/lib/server/lib/bugsnag.ts +++ b/app/lib/server/lib/bugsnag.ts @@ -27,7 +27,7 @@ function _bugsnagDebug(message: any, stack: any, ...args: any): void { return originalMeteorDebug(message, stack, ...args); } -settings.get('Bugsnag_api_key', (_key, value) => { +settings.watch('Bugsnag_api_key', (value) => { if (!value) { return; } diff --git a/app/lib/server/lib/debug.js b/app/lib/server/lib/debug.js index 2d37b333468e..9a6446037190 100644 --- a/app/lib/server/lib/debug.js +++ b/app/lib/server/lib/debug.js @@ -12,13 +12,13 @@ const logger = new Logger('Meteor'); let Log_Trace_Methods; let Log_Trace_Subscriptions; -settings.get('Log_Trace_Methods', (key, value) => { Log_Trace_Methods = value; }); -settings.get('Log_Trace_Subscriptions', (key, value) => { Log_Trace_Subscriptions = value; }); +settings.watch('Log_Trace_Methods', (value) => { Log_Trace_Methods = value; }); +settings.watch('Log_Trace_Subscriptions', (value) => { Log_Trace_Subscriptions = value; }); let Log_Trace_Methods_Filter; let Log_Trace_Subscriptions_Filter; -settings.get('Log_Trace_Methods_Filter', (key, value) => { Log_Trace_Methods_Filter = value ? new RegExp(value) : undefined; }); -settings.get('Log_Trace_Subscriptions_Filter', (key, value) => { Log_Trace_Subscriptions_Filter = value ? new RegExp(value) : undefined; }); +settings.watch('Log_Trace_Methods_Filter', (value) => { Log_Trace_Methods_Filter = value ? new RegExp(value) : undefined; }); +settings.watch('Log_Trace_Subscriptions_Filter', (value) => { Log_Trace_Subscriptions_Filter = value ? new RegExp(value) : undefined; }); const traceConnection = (enable, filter, prefix, name, connection, userId) => { if (!enable) { diff --git a/app/lib/server/lib/getHiddenSystemMessages.ts b/app/lib/server/lib/getHiddenSystemMessages.ts index 0d734dacfc54..592fe5dd7e66 100644 --- a/app/lib/server/lib/getHiddenSystemMessages.ts +++ b/app/lib/server/lib/getHiddenSystemMessages.ts @@ -3,7 +3,7 @@ import { IRoom } from '../../../../definition/IRoom'; const hideMessagesOfTypeServer = new Set(); -settings.get('Hide_System_Messages', function(_key, values) { +settings.watch('Hide_System_Messages', function(values) { if (!values || !Array.isArray(values)) { return; } diff --git a/app/lib/server/lib/index.js b/app/lib/server/lib/index.js index 958e8b04d095..f003ebf15fce 100644 --- a/app/lib/server/lib/index.js +++ b/app/lib/server/lib/index.js @@ -9,7 +9,7 @@ import './notifyUsersOnMessage'; import './meteorFixes'; export { sendNotification } from './sendNotificationsOnMessage'; -export { hostname } from '../../lib/startup/settingsOnLoadSiteUrl'; +export { hostname } from '../startup/settingsOnLoadSiteUrl'; export { passwordPolicy } from './passwordPolicy'; export { validateEmailDomain } from './validateEmailDomain'; export { RateLimiterClass as RateLimiter } from './RateLimiter'; diff --git a/app/lib/server/lib/passwordPolicy.js b/app/lib/server/lib/passwordPolicy.js index c426fbd4cc11..2c0c2d9c2b30 100644 --- a/app/lib/server/lib/passwordPolicy.js +++ b/app/lib/server/lib/passwordPolicy.js @@ -1,14 +1,14 @@ import PasswordPolicy from './PasswordPolicyClass'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; export const passwordPolicy = new PasswordPolicy(); -settings.get('Accounts_Password_Policy_Enabled', (key, value) => { passwordPolicy.enabled = value; }); -settings.get('Accounts_Password_Policy_MinLength', (key, value) => { passwordPolicy.minLength = value; }); -settings.get('Accounts_Password_Policy_MaxLength', (key, value) => { passwordPolicy.maxLength = value; }); -settings.get('Accounts_Password_Policy_ForbidRepeatingCharacters', (key, value) => { passwordPolicy.forbidRepeatingCharacters = value; }); -settings.get('Accounts_Password_Policy_ForbidRepeatingCharactersCount', (key, value) => { passwordPolicy.forbidRepeatingCharactersCount = value; }); -settings.get('Accounts_Password_Policy_AtLeastOneLowercase', (key, value) => { passwordPolicy.mustContainAtLeastOneLowercase = value; }); -settings.get('Accounts_Password_Policy_AtLeastOneUppercase', (key, value) => { passwordPolicy.mustContainAtLeastOneUppercase = value; }); -settings.get('Accounts_Password_Policy_AtLeastOneNumber', (key, value) => { passwordPolicy.mustContainAtLeastOneNumber = value; }); -settings.get('Accounts_Password_Policy_AtLeastOneSpecialCharacter', (key, value) => { passwordPolicy.mustContainAtLeastOneSpecialCharacter = value; }); +settings.watch('Accounts_Password_Policy_Enabled', (value) => { passwordPolicy.enabled = value; }); +settings.watch('Accounts_Password_Policy_MinLength', (value) => { passwordPolicy.minLength = value; }); +settings.watch('Accounts_Password_Policy_MaxLength', (value) => { passwordPolicy.maxLength = value; }); +settings.watch('Accounts_Password_Policy_ForbidRepeatingCharacters', (value) => { passwordPolicy.forbidRepeatingCharacters = value; }); +settings.watch('Accounts_Password_Policy_ForbidRepeatingCharactersCount', (value) => { passwordPolicy.forbidRepeatingCharactersCount = value; }); +settings.watch('Accounts_Password_Policy_AtLeastOneLowercase', (value) => { passwordPolicy.mustContainAtLeastOneLowercase = value; }); +settings.watch('Accounts_Password_Policy_AtLeastOneUppercase', (value) => { passwordPolicy.mustContainAtLeastOneUppercase = value; }); +settings.watch('Accounts_Password_Policy_AtLeastOneNumber', (value) => { passwordPolicy.mustContainAtLeastOneNumber = value; }); +settings.watch('Accounts_Password_Policy_AtLeastOneSpecialCharacter', (value) => { passwordPolicy.mustContainAtLeastOneSpecialCharacter = value; }); diff --git a/app/lib/server/lib/sendNotificationsOnMessage.js b/app/lib/server/lib/sendNotificationsOnMessage.js index ac829ef55f1e..7ad193d3c7f6 100644 --- a/app/lib/server/lib/sendNotificationsOnMessage.js +++ b/app/lib/server/lib/sendNotificationsOnMessage.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import moment from 'moment'; import { hasPermission } from '../../../authorization'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { callbacks } from '../../../callbacks/server'; import { Subscriptions, Users } from '../../../models/server'; import { roomTypes } from '../../../utils'; @@ -393,7 +393,7 @@ export async function sendAllNotifications(message, room) { return message; } -settings.get('Troubleshoot_Disable_Notifications', (key, value) => { +settings.watch('Troubleshoot_Disable_Notifications', (value) => { if (TroubleshootDisableNotifications === value) { return; } TroubleshootDisableNotifications = value; diff --git a/app/lib/server/lib/validateEmailDomain.js b/app/lib/server/lib/validateEmailDomain.js index d9cdd297ad04..85df94bed533 100644 --- a/app/lib/server/lib/validateEmailDomain.js +++ b/app/lib/server/lib/validateEmailDomain.js @@ -10,10 +10,8 @@ const emailValidationRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[ let emailDomainBlackList = []; let emailDomainWhiteList = []; -let useDefaultBlackList = false; -let useDNSDomainCheck = false; -settings.get('Accounts_BlockedDomainsList', function(key, value) { +settings.watch('Accounts_BlockedDomainsList', function(value) { if (!value) { emailDomainBlackList = []; return; @@ -21,7 +19,7 @@ settings.get('Accounts_BlockedDomainsList', function(key, value) { emailDomainBlackList = value.split(',').filter(Boolean).map((domain) => domain.trim()); }); -settings.get('Accounts_AllowedDomainsList', function(key, value) { +settings.watch('Accounts_AllowedDomainsList', function(value) { if (!value) { emailDomainWhiteList = []; return; @@ -29,12 +27,6 @@ settings.get('Accounts_AllowedDomainsList', function(key, value) { emailDomainWhiteList = value.split(',').filter(Boolean).map((domain) => domain.trim()); }); -settings.get('Accounts_UseDefaultBlockedDomainsList', function(key, value) { - useDefaultBlackList = value; -}); -settings.get('Accounts_UseDNSDomainCheck', function(key, value) { - useDNSDomainCheck = value; -}); export const validateEmailDomain = function(email) { if (!emailValidationRegex.test(email)) { @@ -46,11 +38,11 @@ export const validateEmailDomain = function(email) { if (emailDomainWhiteList.length && !emailDomainWhiteList.includes(emailDomain)) { throw new Meteor.Error('error-invalid-domain', 'The email domain is not in whitelist', { function: 'RocketChat.validateEmailDomain' }); } - if (emailDomainBlackList.length && (emailDomainBlackList.indexOf(emailDomain) !== -1 || (useDefaultBlackList && emailDomainDefaultBlackList.indexOf(emailDomain) !== -1))) { + if (emailDomainBlackList.length && (emailDomainBlackList.indexOf(emailDomain) !== -1 || (settings.get('Accounts_UseDefaultBlockedDomainsList') && emailDomainDefaultBlackList.indexOf(emailDomain) !== -1))) { throw new Meteor.Error('error-email-domain-blacklisted', 'The email domain is blacklisted', { function: 'RocketChat.validateEmailDomain' }); } - if (useDNSDomainCheck) { + if (settings.get('Accounts_UseDNSDomainCheck')) { try { dnsResolveMx(emailDomain); } catch (e) { diff --git a/app/lib/server/methods/filterBadWords.ts b/app/lib/server/methods/filterBadWords.ts index 57f89059fc7a..b4544ad5d394 100644 --- a/app/lib/server/methods/filterBadWords.ts +++ b/app/lib/server/methods/filterBadWords.ts @@ -8,7 +8,7 @@ import { IMessage } from '../../../../definition/IMessage'; const Dep = new Tracker.Dependency(); Meteor.startup(() => { - settings.get(/Message_AllowBadWordsFilter|Message_BadWordsFilterList|Message_BadWordsWhitelist/, () => { + settings.watchMultiple(['Message_AllowBadWordsFilter', 'Message_BadWordsFilterList', 'Message_BadWordsWhitelist'], () => { Dep.changed(); }); Tracker.autorun(() => { diff --git a/app/lib/server/methods/removeOAuthService.js b/app/lib/server/methods/removeOAuthService.js index ba6f6a4dd5a3..5c271f3e75f0 100644 --- a/app/lib/server/methods/removeOAuthService.js +++ b/app/lib/server/methods/removeOAuthService.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; -import { settings } from '../../../settings'; +import { Settings } from '../../../models/server'; Meteor.methods({ removeOAuthService(name) { @@ -19,33 +19,33 @@ Meteor.methods({ name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); name = capitalize(name); - settings.removeById(`Accounts_OAuth_Custom-${ name }`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-url`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-token_path`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_path`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-authorize_path`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-scope`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-access_token_param`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-token_sent_via`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-id`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-secret`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_text`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_color`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-button_color`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-login_style`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-key_field`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-username_field`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-email_field`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-name_field`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-avatar_field`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_claim`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_roles`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_users`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-show_button`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_claim`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-channels_admin`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-map_channels`); - settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_channel_map`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-url`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_path`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_path`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-authorize_path`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-scope`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-access_token_param`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_sent_via`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-id`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-secret`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_text`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_color`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_color`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-login_style`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-key_field`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-username_field`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-email_field`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-name_field`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-avatar_field`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_claim`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_roles`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_users`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-show_button`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_claim`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-channels_admin`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-map_channels`); + Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_channel_map`); }, }); diff --git a/app/lib/server/methods/saveSetting.js b/app/lib/server/methods/saveSetting.js index e8270d35835c..b375b1ad5952 100644 --- a/app/lib/server/methods/saveSetting.js +++ b/app/lib/server/methods/saveSetting.js @@ -3,7 +3,6 @@ import { Match, check } from 'meteor/check'; import { hasPermission, hasAllPermission } from '../../../authorization/server'; import { getSettingPermissionId } from '../../../authorization/lib'; -import { settings } from '../../../settings'; import { Settings } from '../../../models'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; @@ -45,7 +44,7 @@ Meteor.methods({ break; } - settings.updateById(_id, value, editor); + Settings.updateValueAndEditorById(_id, value, editor); return true; }), }); diff --git a/app/lib/server/methods/saveSettings.js b/app/lib/server/methods/saveSettings.js index d46bca64aac9..6b99b3c7665c 100644 --- a/app/lib/server/methods/saveSettings.js +++ b/app/lib/server/methods/saveSettings.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; -import { settings } from '../../../settings'; import { Settings } from '../../../models'; import { getSettingPermissionId } from '../../../authorization/lib'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; @@ -54,7 +53,7 @@ Meteor.methods({ }); } - params.forEach(({ _id, value, editor }) => settings.updateById(_id, value, editor)); + params.forEach(({ _id, value, editor }) => Settings.updateValueById(_id, value, editor)); return true; }), diff --git a/app/lib/server/startup/email.js b/app/lib/server/startup/email.ts similarity index 99% rename from app/lib/server/startup/email.js rename to app/lib/server/startup/email.ts index 40cac50e9c8b..b8b378d93d38 100644 --- a/app/lib/server/startup/email.js +++ b/app/lib/server/startup/email.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('Email', function() { +settingsRegistry.addGroup('Email', function() { this.section('Style', function() { this.add('email_plain_text_only', false, { type: 'boolean', diff --git a/app/lib/server/startup/oAuthServicesUpdate.js b/app/lib/server/startup/oAuthServicesUpdate.js index b414abdab315..42fdbc05b7f3 100644 --- a/app/lib/server/startup/oAuthServicesUpdate.js +++ b/app/lib/server/startup/oAuthServicesUpdate.js @@ -4,58 +4,58 @@ import _ from 'underscore'; import { CustomOAuth } from '../../../custom-oauth'; import { Logger } from '../../../logger'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { addOAuthService } from '../functions/addOAuthService'; const logger = new Logger('rocketchat:lib'); function _OAuthServicesUpdate() { - const services = settings.get(/^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i); - services.forEach((service) => { - logger.info({ oauth_updated: service.key }); - let serviceName = service.key.replace('Accounts_OAuth_', ''); + const services = settings.getByRegexp(/^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i); + services.forEach(([key, value]) => { + logger.debug({ oauth_updated: key }); + let serviceName = key.replace('Accounts_OAuth_', ''); if (serviceName === 'Meteor') { serviceName = 'meteor-developer'; } - if (/Accounts_OAuth_Custom-/.test(service.key)) { - serviceName = service.key.replace('Accounts_OAuth_Custom-', ''); + if (/Accounts_OAuth_Custom-/.test(key)) { + serviceName = key.replace('Accounts_OAuth_Custom-', ''); } - if (service.value === true) { + if (value === true) { const data = { - clientId: settings.get(`${ service.key }_id`), - secret: settings.get(`${ service.key }_secret`), + clientId: settings.get(`${ key }_id`), + secret: settings.get(`${ key }_secret`), }; - if (/Accounts_OAuth_Custom-/.test(service.key)) { + if (/Accounts_OAuth_Custom-/.test(key)) { data.custom = true; - data.clientId = settings.get(`${ service.key }-id`); - data.secret = settings.get(`${ service.key }-secret`); - data.serverURL = settings.get(`${ service.key }-url`); - data.tokenPath = settings.get(`${ service.key }-token_path`); - data.identityPath = settings.get(`${ service.key }-identity_path`); - data.authorizePath = settings.get(`${ service.key }-authorize_path`); - data.scope = settings.get(`${ service.key }-scope`); - data.accessTokenParam = settings.get(`${ service.key }-access_token_param`); - data.buttonLabelText = settings.get(`${ service.key }-button_label_text`); - data.buttonLabelColor = settings.get(`${ service.key }-button_label_color`); - data.loginStyle = settings.get(`${ service.key }-login_style`); - data.buttonColor = settings.get(`${ service.key }-button_color`); - data.tokenSentVia = settings.get(`${ service.key }-token_sent_via`); - data.identityTokenSentVia = settings.get(`${ service.key }-identity_token_sent_via`); - data.keyField = settings.get(`${ service.key }-key_field`); - data.usernameField = settings.get(`${ service.key }-username_field`); - data.emailField = settings.get(`${ service.key }-email_field`); - data.nameField = settings.get(`${ service.key }-name_field`); - data.avatarField = settings.get(`${ service.key }-avatar_field`); - data.rolesClaim = settings.get(`${ service.key }-roles_claim`); - data.groupsClaim = settings.get(`${ service.key }-groups_claim`); - data.channelsMap = settings.get(`${ service.key }-groups_channel_map`); - data.channelsAdmin = settings.get(`${ service.key }-channels_admin`); - data.mergeUsers = settings.get(`${ service.key }-merge_users`); - data.mapChannels = settings.get(`${ service.key }-map_channels`); - data.mergeRoles = settings.get(`${ service.key }-merge_roles`); - data.showButton = settings.get(`${ service.key }-show_button`); + data.clientId = settings.get(`${ key }-id`); + data.secret = settings.get(`${ key }-secret`); + data.serverURL = settings.get(`${ key }-url`); + data.tokenPath = settings.get(`${ key }-token_path`); + data.identityPath = settings.get(`${ key }-identity_path`); + data.authorizePath = settings.get(`${ key }-authorize_path`); + data.scope = settings.get(`${ key }-scope`); + data.accessTokenParam = settings.get(`${ key }-access_token_param`); + data.buttonLabelText = settings.get(`${ key }-button_label_text`); + data.buttonLabelColor = settings.get(`${ key }-button_label_color`); + data.loginStyle = settings.get(`${ key }-login_style`); + data.buttonColor = settings.get(`${ key }-button_color`); + data.tokenSentVia = settings.get(`${ key }-token_sent_via`); + data.identityTokenSentVia = settings.get(`${ key }-identity_token_sent_via`); + data.keyField = settings.get(`${ key }-key_field`); + data.usernameField = settings.get(`${ key }-username_field`); + data.emailField = settings.get(`${ key }-email_field`); + data.nameField = settings.get(`${ key }-name_field`); + data.avatarField = settings.get(`${ key }-avatar_field`); + data.rolesClaim = settings.get(`${ key }-roles_claim`); + data.groupsClaim = settings.get(`${ key }-groups_claim`); + data.channelsMap = settings.get(`${ key }-groups_channel_map`); + data.channelsAdmin = settings.get(`${ key }-channels_admin`); + data.mergeUsers = settings.get(`${ key }-merge_users`); + data.mapChannels = settings.get(`${ key }-map_channels`); + data.mergeRoles = settings.get(`${ key }-merge_roles`); + data.showButton = settings.get(`${ key }-show_button`); new CustomOAuth(serviceName.toLowerCase(), { serverURL: data.serverURL, @@ -131,11 +131,11 @@ function OAuthServicesRemove(_id) { }); } -settings.get(/^Accounts_OAuth_.+/, function() { +settings.watchByRegex(/^Accounts_OAuth_.+/, function() { return OAuthServicesUpdate(); // eslint-disable-line new-cap }); -settings.get(/^Accounts_OAuth_Custom-[a-z0-9_]+/, function(key, value) { +settings.watchByRegex(/^Accounts_OAuth_Custom-[a-z0-9_]+/, function(key, value) { if (!value) { return OAuthServicesRemove(key);// eslint-disable-line new-cap } diff --git a/app/lib/server/startup/rateLimiter.js b/app/lib/server/startup/rateLimiter.js index f7339c19958d..464404b88048 100644 --- a/app/lib/server/startup/rateLimiter.js +++ b/app/lib/server/startup/rateLimiter.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { RateLimiter } from 'meteor/rate-limit'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { metrics } from '../../../metrics'; import { Logger } from '../../../logger'; @@ -203,9 +203,9 @@ const configConnectionByMethod = _.debounce(() => { }, 1000); if (!process.env.TEST_MODE) { - settings.get(/^DDP_Rate_Limit_IP_.+/, configIP); - settings.get(/^DDP_Rate_Limit_User_[^B].+/, configUser); - settings.get(/^DDP_Rate_Limit_Connection_[^B].+/, configConnection); - settings.get(/^DDP_Rate_Limit_User_By_Method_.+/, configUserByMethod); - settings.get(/^DDP_Rate_Limit_Connection_By_Method_.+/, configConnectionByMethod); + settings.watchByRegex(/^DDP_Rate_Limit_IP_.+/, configIP); + settings.watchByRegex(/^DDP_Rate_Limit_User_[^B].+/, configUser); + settings.watchByRegex(/^DDP_Rate_Limit_Connection_[^B].+/, configConnection); + settings.watchByRegex(/^DDP_Rate_Limit_User_By_Method_.+/, configUserByMethod); + settings.watchByRegex(/^DDP_Rate_Limit_Connection_By_Method_.+/, configConnectionByMethod); } diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.ts similarity index 98% rename from app/lib/server/startup/settings.js rename to app/lib/server/startup/settings.ts index b0d97305922e..b8c3309b5cf2 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.ts @@ -1,18 +1,19 @@ import { Random } from 'meteor/random'; -import { settings } from '../../../settings/server'; +import { settingsRegistry } from '../../../settings/server'; import './email'; import { MessageTypesValues } from '../../lib/MessageTypes'; // Insert server unique id if it doesn't exist -settings.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), { +settingsRegistry.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), { public: true, }); // When you define a setting and want to add a description, you don't need to automatically define the i18nDescription // if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work. -settings.addGroup('Accounts', function() { + +settingsRegistry.addGroup('Accounts', function() { this.add('Accounts_AllowAnonymousRead', false, { type: 'boolean', public: true, @@ -148,7 +149,7 @@ settings.addGroup('Accounts', function() { enableQuery: { _id: 'SMTP_Host', value: { - $exists: 1, + $exists: true, $ne: '', }, }, @@ -622,7 +623,7 @@ settings.addGroup('Accounts', function() { }); }); -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('Facebook', function() { const enableQuery = { _id: 'Accounts_OAuth_Facebook', @@ -644,7 +645,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Facebook_callback_url', '_oauth/facebook', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -669,7 +669,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Google_callback_url', '_oauth/google', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -694,7 +693,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Github_callback_url', '_oauth/github', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -719,7 +717,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Linkedin_callback_url', '_oauth/linkedin', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -744,7 +741,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Meteor_callback_url', '_oauth/meteor', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -769,7 +765,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Twitter_callback_url', '_oauth/twitter', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); @@ -785,7 +780,7 @@ settings.addGroup('OAuth', function() { }); }); -settings.addGroup('General', function() { +settingsRegistry.addGroup('General', function() { this.add('Show_Setup_Wizard', 'pending', { type: 'select', public: true, @@ -803,7 +798,9 @@ settings.addGroup('General', function() { }, ], }); - this.add('Site_Url', typeof __meteor_runtime_config__ !== 'undefined' && __meteor_runtime_config__ !== null ? __meteor_runtime_config__.ROOT_URL : null, { + + // eslint-disable-next-line @typescript-eslint/camelcase + this.add('Site_Url', typeof (global as any).__meteor_runtime_config__ !== 'undefined' && (global as any).__meteor_runtime_config__ !== null ? (global as any).__meteor_runtime_config__.ROOT_URL : null, { type: 'string', i18nDescription: 'Site_Url_Description', public: true, @@ -1076,7 +1073,7 @@ settings.addGroup('General', function() { }); }); -settings.addGroup('Message', function() { +settingsRegistry.addGroup('Message', function() { this.section('Message_Attachments', function() { this.add('Message_Attachments_GroupAttach', false, { type: 'boolean', @@ -1287,7 +1284,7 @@ settings.addGroup('Message', function() { }); }); -settings.addGroup('Meta', function() { +settingsRegistry.addGroup('Meta', function() { this.add('Meta_language', '', { type: 'string', }); @@ -1313,7 +1310,7 @@ settings.addGroup('Meta', function() { }); }); -settings.addGroup('Mobile', function() { +settingsRegistry.addGroup('Mobile', function() { this.add('Allow_Save_Media_to_Gallery', true, { type: 'boolean', public: true, @@ -1334,7 +1331,7 @@ const pushEnabledWithoutGateway = [ }, ]; -settings.addGroup('Push', function() { +settingsRegistry.addGroup('Push', function() { this.add('Push_enable', true, { type: 'boolean', public: true, @@ -1455,7 +1452,7 @@ settings.addGroup('Push', function() { }); }); -settings.addGroup('Layout', function() { +settingsRegistry.addGroup('Layout', function() { this.section('Content', function() { this.add('Layout_Home_Title', 'Home', { type: 'string', @@ -1569,7 +1566,7 @@ settings.addGroup('Layout', function() { }); }); -settings.addGroup('Logs', function() { +settingsRegistry.addGroup('Logs', function() { this.add('Log_Level', '0', { type: 'select', values: [ @@ -1621,7 +1618,7 @@ settings.addGroup('Logs', function() { }); // See the default port allocation at https://github.com/prometheus/prometheus/wiki/Default-port-allocations this.add('Prometheus_Port', 9458, { - type: 'string', + type: 'int', i18nLabel: 'Port', }); this.add('Prometheus_Reset_Interval', 0, { @@ -1637,7 +1634,7 @@ settings.addGroup('Logs', function() { }); }); -settings.addGroup('Setup_Wizard', function() { +settingsRegistry.addGroup('Setup_Wizard', function() { this.section('Organization_Info', function() { this.add('Organization_Type', '', { type: 'select', @@ -2979,7 +2976,7 @@ settings.addGroup('Setup_Wizard', function() { }); }); -settings.addGroup('Rate Limiter', function() { +settingsRegistry.addGroup('Rate Limiter', function() { this.section('DDP Rate Limiter', function() { this.add('DDP_Rate_Limit_IP_Enabled', true, { type: 'boolean' }); this.add('DDP_Rate_Limit_IP_Requests_Allowed', 120000, { type: 'int', enableQuery: { _id: 'DDP_Rate_Limit_IP_Enabled', value: true } }); @@ -3010,7 +3007,7 @@ settings.addGroup('Rate Limiter', function() { }); }); -settings.addGroup('Troubleshoot', function() { +settingsRegistry.addGroup('Troubleshoot', function() { this.add('Troubleshoot_Disable_Notifications', false, { type: 'boolean', alert: 'Troubleshoot_Disable_Notifications_Alert', @@ -3044,5 +3041,3 @@ settings.addGroup('Troubleshoot', function() { alert: 'Troubleshoot_Disable_Workspace_Sync_Alert', }); }); - -settings.init(); diff --git a/app/lib/server/startup/settingsOnLoadCdnPrefix.js b/app/lib/server/startup/settingsOnLoadCdnPrefix.js index 3c3e62bb356e..84d307dbc375 100644 --- a/app/lib/server/startup/settingsOnLoadCdnPrefix.js +++ b/app/lib/server/startup/settingsOnLoadCdnPrefix.js @@ -2,19 +2,19 @@ import { Meteor } from 'meteor/meteor'; import { WebAppInternals } from 'meteor/webapp'; import _ from 'underscore'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; function testWebAppInternals(fn) { typeof WebAppInternals !== 'undefined' && fn(WebAppInternals); } -settings.onload('CDN_PREFIX', function(key, value) { +settings.change('CDN_PREFIX', function(value) { const useForAll = settings.get('CDN_PREFIX_ALL'); if (_.isString(value) && value.trim() && useForAll) { return testWebAppInternals((WebAppInternals) => WebAppInternals.setBundledJsCssPrefix(value)); } }); -settings.onload('CDN_JSCSS_PREFIX', function(key, value) { +settings.change('CDN_JSCSS_PREFIX', function(value) { const useForAll = settings.get('CDN_PREFIX_ALL'); if (_.isString(value) && value.trim() && !useForAll) { return testWebAppInternals((WebAppInternals) => WebAppInternals.setBundledJsCssPrefix(value)); diff --git a/app/lib/server/startup/settingsOnLoadDirectReply.js b/app/lib/server/startup/settingsOnLoadDirectReply.js index f38565b79981..e85f4b6000ab 100644 --- a/app/lib/server/startup/settingsOnLoadDirectReply.js +++ b/app/lib/server/startup/settingsOnLoadDirectReply.js @@ -1,8 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; import { Logger } from '../../../logger/server'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { IMAPIntercepter, POP3Helper, POP3 } from '../lib/interceptDirectReplyEmails.js'; const logger = new Logger('Email Intercepter'); @@ -11,7 +10,7 @@ const logger = new Logger('Email Intercepter'); let IMAP; let _POP3Helper; -const startEmailIntercepter = _.debounce(Meteor.bindEnvironment(function() { +settings.watchMultiple(['Direct_Reply_Enable', 'Direct_Reply_Protocol', 'Direct_Reply_Host', 'Direct_Reply_Port', 'Direct_Reply_Username', 'Direct_Reply_Password', 'Direct_Reply_Protocol', 'Direct_Reply_Protocol'], function() { logger.debug('Starting Email Intercepter...'); if (settings.get('Direct_Reply_Enable') && settings.get('Direct_Reply_Protocol') && settings.get('Direct_Reply_Host') && settings.get('Direct_Reply_Port') && settings.get('Direct_Reply_Username') && settings.get('Direct_Reply_Password')) { @@ -71,6 +70,4 @@ const startEmailIntercepter = _.debounce(Meteor.bindEnvironment(function() { // stop POP3 instance _POP3Helper.stop(); } -}), 1000); - -settings.onload(/^Direct_Reply_.+/, startEmailIntercepter); +}); diff --git a/app/lib/server/startup/settingsOnLoadSMTP.js b/app/lib/server/startup/settingsOnLoadSMTP.ts similarity index 53% rename from app/lib/server/startup/settingsOnLoadSMTP.js rename to app/lib/server/startup/settingsOnLoadSMTP.ts index cb31e03e7541..6d1b9340d9b8 100644 --- a/app/lib/server/startup/settingsOnLoadSMTP.js +++ b/app/lib/server/startup/settingsOnLoadSMTP.ts @@ -1,10 +1,13 @@ -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; - import { settings } from '../../../settings/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; -const buildMailURL = _.debounce(function() { +settings.watchMultiple(['SMTP_Host', + 'SMTP_Port', + 'SMTP_Username', + 'SMTP_Password', + 'SMTP_Protocol', + 'SMTP_Pool', + 'SMTP_IgnoreTLS'], function() { SystemLogger.info('Updating process.env.MAIL_URL'); if (settings.get('SMTP_Host')) { @@ -28,42 +31,4 @@ const buildMailURL = _.debounce(function() { return process.env.MAIL_URL; } -}, 500); - -settings.onload('SMTP_Host', function(key, value) { - if (_.isString(value)) { - return buildMailURL(); - } -}); - -settings.onload('SMTP_Port', function() { - return buildMailURL(); -}); - -settings.onload('SMTP_Username', function(key, value) { - if (_.isString(value)) { - return buildMailURL(); - } -}); - -settings.onload('SMTP_Password', function(key, value) { - if (_.isString(value)) { - return buildMailURL(); - } -}); - -settings.onload('SMTP_Protocol', function() { - return buildMailURL(); -}); - -settings.onload('SMTP_Pool', function() { - return buildMailURL(); -}); - -settings.onload('SMTP_IgnoreTLS', function() { - return buildMailURL(); -}); - -Meteor.startup(function() { - return buildMailURL(); }); diff --git a/app/lib/lib/startup/settingsOnLoadSiteUrl.js b/app/lib/server/startup/settingsOnLoadSiteUrl.ts similarity index 52% rename from app/lib/lib/startup/settingsOnLoadSiteUrl.js rename to app/lib/server/startup/settingsOnLoadSiteUrl.ts index 2fb003f72df0..fecadde87061 100644 --- a/app/lib/lib/startup/settingsOnLoadSiteUrl.js +++ b/app/lib/server/startup/settingsOnLoadSiteUrl.ts @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { WebAppInternals } from 'meteor/webapp'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; -export let hostname; +export let hostname: string; -settings.get('Site_Url', function(key, value) { +settings.watch('Site_Url', function(value) { if (value == null || value.trim() === '') { return; } @@ -16,17 +16,16 @@ settings.get('Site_Url', function(key, value) { host = match[1]; // prefix = match[2].replace(/\/$/, ''); } - __meteor_runtime_config__.ROOT_URL = value; + (global as any).__meteor_runtime_config__.ROOT_URL = value; if (Meteor.absoluteUrl.defaultOptions && Meteor.absoluteUrl.defaultOptions.rootUrl) { Meteor.absoluteUrl.defaultOptions.rootUrl = value; } - if (Meteor.isServer) { - hostname = host.replace(/^https?:\/\//, ''); - process.env.MOBILE_ROOT_URL = host; - process.env.MOBILE_DDP_URL = host; - if (typeof WebAppInternals !== 'undefined' && WebAppInternals.generateBoilerplate) { - return WebAppInternals.generateBoilerplate(); - } + + hostname = host.replace(/^https?:\/\//, ''); + process.env.MOBILE_ROOT_URL = host; + process.env.MOBILE_DDP_URL = host; + if (typeof WebAppInternals !== 'undefined' && WebAppInternals.generateBoilerplate) { + return WebAppInternals.generateBoilerplate(); } }); diff --git a/app/livechat/imports/server/rest/upload.js b/app/livechat/imports/server/rest/upload.js index cbda364a2ee4..f200edd6e523 100644 --- a/app/livechat/imports/server/rest/upload.js +++ b/app/livechat/imports/server/rest/upload.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import filesize from 'filesize'; -import { settings } from '../../../../settings'; +import { settings } from '../../../../settings/server'; import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models'; import { fileUploadIsValidContentType } from '../../../../utils/server'; import { FileUpload } from '../../../../file-upload'; @@ -10,7 +10,7 @@ import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData' let maxFileSize; -settings.get('FileUpload_MaxFileSize', function(key, value) { +settings.watch('FileUpload_MaxFileSize', function(value) { try { maxFileSize = parseInt(value); } catch (e) { diff --git a/app/livechat/server/config.js b/app/livechat/server/config.js deleted file mode 100644 index 45f59a8b26a9..000000000000 --- a/app/livechat/server/config.js +++ /dev/null @@ -1,558 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.addGroup('Omnichannel'); - - settings.add('Livechat_enabled', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - }); - - settings.add('Livechat_title', 'Rocket.Chat', { - type: 'string', - group: 'Omnichannel', - section: 'Livechat', - public: true, - }); - - settings.add('Livechat_title_color', '#C1272D', { - type: 'color', - editor: 'color', - allowedTypes: ['color', 'expression'], - group: 'Omnichannel', - section: 'Livechat', - public: true, - }); - - settings.add('Livechat_enable_message_character_limit', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - }); - - settings.add('Livechat_message_character_limit', 0, { - type: 'int', - group: 'Omnichannel', - section: 'Livechat', - public: true, - }); - - settings.add('Livechat_display_offline_form', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Display_offline_form', - }); - - settings.add('Livechat_validate_offline_email', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Validate_email_address', - }); - - settings.add('Livechat_offline_form_unavailable', '', { - type: 'string', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Offline_form_unavailable_message', - }); - - settings.add('Livechat_offline_title', 'Leave a message', { - type: 'string', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Title', - }); - settings.add('Livechat_offline_title_color', '#666666', { - type: 'color', - editor: 'color', - allowedTypes: ['color', 'expression'], - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Color', - }); - settings.add('Livechat_offline_message', '', { - type: 'string', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Instructions', - i18nDescription: 'Instructions_to_your_visitor_fill_the_form_to_send_a_message', - }); - settings.add('Livechat_offline_email', '', { - type: 'string', - group: 'Omnichannel', - i18nLabel: 'Email_address_to_send_offline_messages', - section: 'Livechat', - }); - settings.add('Livechat_offline_success_message', '', { - type: 'string', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Offline_success_message', - }); - - settings.add('Livechat_allow_switching_departments', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - section: 'Livechat', - i18nLabel: 'Allow_switching_departments', - }); - - settings.add('Livechat_show_agent_info', true, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Show_agent_info', - }); - - settings.add('Livechat_show_agent_email', true, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - enableQuery: { _id: 'Livechat_show_agent_info', value: true }, - i18nLabel: 'Show_agent_email', - }); - - settings.add('Livechat_request_comment_when_closing_conversation', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - i18nLabel: 'Request_comment_when_closing_conversation', - i18nDescription: 'Request_comment_when_closing_conversation_description', - }); - - settings.add('Livechat_conversation_finished_message', '', { - type: 'string', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Conversation_finished_message', - }); - - settings.add('Livechat_conversation_finished_text', '', { - type: 'string', - multiline: true, - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Conversation_finished_text', - }); - - settings.add('Livechat_registration_form', true, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Show_preregistration_form', - }); - - settings.add('Livechat_name_field_registration_form', true, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Show_name_field', - }); - - settings.add('Livechat_email_field_registration_form', true, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Show_email_field', - }); - - settings.add('Livechat_guest_count', 1, { type: 'int', group: 'Omnichannel' }); - - settings.add('Livechat_Room_Count', 1, { - type: 'int', - group: 'Omnichannel', - i18nLabel: 'Livechat_room_count', - }); - - settings.add('Livechat_enabled_when_agent_idle', true, { - type: 'boolean', - group: 'Omnichannel', - i18nLabel: 'Accept_new_livechats_when_agent_is_idle', - }); - - settings.add('Livechat_webhookUrl', '', { - type: 'string', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Webhook_URL', - }); - - settings.add('Livechat_secret_token', '', { - type: 'string', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Secret_token', - secret: true, - }); - - settings.add('Livechat_webhook_on_start', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_chat_start', - }); - - settings.add('Livechat_webhook_on_close', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_chat_close', - }); - - settings.add('Livechat_webhook_on_chat_taken', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_chat_taken', - }); - - settings.add('Livechat_webhook_on_chat_queued', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_chat_queued', - }); - - settings.add('Livechat_webhook_on_forward', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_forwarding', - }); - - settings.add('Livechat_webhook_on_offline_msg', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_offline_messages', - }); - - settings.add('Livechat_webhook_on_visitor_message', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_visitor_message', - }); - - settings.add('Livechat_webhook_on_agent_message', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_agent_message', - }); - - settings.add('Send_visitor_navigation_history_livechat_webhook_request', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_visitor_navigation_history_on_request', - i18nDescription: 'Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled', - enableQuery: { _id: 'Livechat_Visitor_navigation_as_a_message', value: true }, - }); - - settings.add('Livechat_webhook_on_capture', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Send_request_on_lead_capture', - }); - - settings.add('Livechat_lead_email_regex', '\\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\\.)+[A-Z]{2,4}\\b', { - type: 'string', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Lead_capture_email_regex', - }); - - settings.add('Livechat_lead_phone_regex', '((?:\\([0-9]{1,3}\\)|[0-9]{2})[ \\-]*?[0-9]{4,5}(?:[\\-\\s\\_]{1,2})?[0-9]{4}(?:(?=[^0-9])|$)|[0-9]{4,5}(?:[\\-\\s\\_]{1,2})?[0-9]{4}(?:(?=[^0-9])|$))', { - type: 'string', - group: 'Omnichannel', - section: 'CRM_Integration', - i18nLabel: 'Lead_capture_phone_regex', - }); - - settings.add('Livechat_history_monitor_type', 'url', { - type: 'select', - group: 'Omnichannel', - section: 'Livechat', - i18nLabel: 'Monitor_history_for_changes_on', - values: [ - { key: 'url', i18nLabel: 'Page_URL' }, - { key: 'title', i18nLabel: 'Page_title' }, - ], - }); - - settings.add('Livechat_Visitor_navigation_as_a_message', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Send_Visitor_navigation_history_as_a_message', - }); - - settings.addGroup('Omnichannel', function() { - this.section('Business_Hours', function() { - this.add('Livechat_enable_business_hours', false, { - type: 'boolean', - group: 'Omnichannel', - public: true, - i18nLabel: 'Business_hours_enabled', - }); - }); - }); - - settings.add('Livechat_continuous_sound_notification_new_livechat_room', false, { - type: 'boolean', - group: 'Omnichannel', - public: true, - i18nLabel: 'Continuous_sound_notifications_for_new_livechat_room', - }); - - settings.add('Livechat_videocall_enabled', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Videocall_enabled', - i18nDescription: 'Beta_feature_Depends_on_Video_Conference_to_be_enabled', - enableQuery: { _id: 'Jitsi_Enabled', value: true }, - }); - - settings.add('Livechat_fileupload_enabled', true, { - type: 'boolean', - group: 'Omnichannel', - public: true, - i18nLabel: 'FileUpload_Enabled', - enableQuery: { _id: 'FileUpload_Enabled', value: true }, - }); - - settings.add('Livechat_enable_transcript', false, { - type: 'boolean', - group: 'Omnichannel', - public: true, - i18nLabel: 'Transcript_Enabled', - }); - - settings.add('Livechat_transcript_message', '', { - type: 'string', - group: 'Omnichannel', - public: true, - i18nLabel: 'Transcript_message', - enableQuery: { _id: 'Livechat_enable_transcript', value: true }, - }); - - settings.add('Livechat_registration_form_message', '', { - type: 'string', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Livechat_registration_form_message', - }); - - settings.add('Livechat_AllowedDomainsList', '', { - type: 'string', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Livechat_AllowedDomainsList', - i18nDescription: 'Domains_allowed_to_embed_the_livechat_widget', - }); - - settings.add('Livechat_OfflineMessageToChannel_enabled', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - }); - - settings.add('Livechat_OfflineMessageToChannel_channel_name', '', { - type: 'string', - group: 'Omnichannel', - section: 'Livechat', - public: true, - enableQuery: { _id: 'Livechat_OfflineMessageToChannel_enabled', value: true }, - i18nLabel: 'Channel_name', - }); - - settings.add('Livechat_Facebook_Enabled', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Facebook', - }); - - settings.add('Livechat_Facebook_API_Key', '', { - type: 'string', - group: 'Omnichannel', - section: 'Facebook', - i18nDescription: 'If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours', - }); - - settings.add('Livechat_Facebook_API_Secret', '', { - type: 'string', - group: 'Omnichannel', - section: 'Facebook', - i18nDescription: 'If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours', - }); - - settings.add('Livechat_RDStation_Token', '', { - type: 'string', - group: 'Omnichannel', - public: false, - section: 'RD Station', - i18nLabel: 'RDStation_Token', - }); - - settings.add('Livechat_Routing_Method', 'Auto_Selection', { - type: 'select', - group: 'Omnichannel', - public: true, - section: 'Routing', - values: [ - { key: 'External', i18nLabel: 'External_Service' }, - { key: 'Auto_Selection', i18nLabel: 'Auto_Selection' }, - { key: 'Manual_Selection', i18nLabel: 'Manual_Selection' }, - ], - }); - - settings.add('Livechat_accept_chats_with_no_agents', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Accept_with_no_online_agents', - i18nDescription: 'Accept_incoming_livechat_requests_even_if_there_are_no_online_agents', - }); - - settings.add('Livechat_assign_new_conversation_to_bot', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Assign_new_conversations_to_bot_agent', - i18nDescription: 'Assign_new_conversations_to_bot_agent_description', - }); - - settings.add('Livechat_guest_pool_max_number_incoming_livechats_displayed', 0, { - type: 'int', - group: 'Omnichannel', - section: 'Routing', - public: true, - i18nLabel: 'Max_number_incoming_livechats_displayed', - i18nDescription: 'Max_number_incoming_livechats_displayed_description', - enableQuery: { _id: 'Livechat_Routing_Method', value: 'Manual_Selection' }, - }); - - settings.add('Livechat_show_queue_list_link', false, { - type: 'boolean', - group: 'Omnichannel', - public: true, - section: 'Routing', - i18nLabel: 'Show_queue_list_to_all_agents', - enableQuery: { _id: 'Livechat_Routing_Method', value: { $ne: 'External' } }, - }); - - settings.add('Livechat_External_Queue_URL', '', { - type: 'string', - group: 'Omnichannel', - public: false, - section: 'Routing', - i18nLabel: 'External_Queue_Service_URL', - i18nDescription: 'For_more_details_please_check_our_docs', - enableQuery: { _id: 'Livechat_Routing_Method', value: 'External' }, - }); - - settings.add('Livechat_External_Queue_Token', '', { - type: 'string', - group: 'Omnichannel', - public: false, - section: 'Routing', - i18nLabel: 'Secret_token', - enableQuery: { _id: 'Livechat_Routing_Method', value: 'External' }, - }); - - settings.add('Livechat_Allow_collect_and_store_HTTP_header_informations', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'GDPR', - public: true, - i18nLabel: 'Allow_collect_and_store_HTTP_header_informations', - i18nDescription: 'Allow_collect_and_store_HTTP_header_informations_description', - }); - - settings.add('Livechat_force_accept_data_processing_consent', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'GDPR', - public: true, - alert: 'Force_visitor_to_accept_data_processing_consent_enabled_alert', - i18nLabel: 'Force_visitor_to_accept_data_processing_consent', - i18nDescription: 'Force_visitor_to_accept_data_processing_consent_description', - }); - - settings.add('Livechat_data_processing_consent_text', '', { - type: 'string', - multiline: true, - group: 'Omnichannel', - section: 'GDPR', - public: true, - i18nLabel: 'Data_processing_consent_text', - i18nDescription: 'Data_processing_consent_text_description', - enableQuery: { _id: 'Livechat_force_accept_data_processing_consent', value: true }, - }); - - settings.add('Livechat_agent_leave_action', 'none', { - type: 'select', - group: 'Omnichannel', - section: 'Sessions', - values: [ - { key: 'none', i18nLabel: 'None' }, - { key: 'forward', i18nLabel: 'Forward' }, - { key: 'close', i18nLabel: 'Close' }, - ], - i18nLabel: 'How_to_handle_open_sessions_when_agent_goes_offline', - }); - - settings.add('Livechat_agent_leave_action_timeout', 60, { - type: 'int', - group: 'Omnichannel', - section: 'Sessions', - enableQuery: { _id: 'Livechat_agent_leave_action', value: { $ne: 'none' } }, - i18nLabel: 'How_long_to_wait_after_agent_goes_offline', - i18nDescription: 'Time_in_seconds', - }); - - settings.add('Livechat_agent_leave_comment', '', { - type: 'string', - group: 'Omnichannel', - section: 'Sessions', - enableQuery: { _id: 'Livechat_agent_leave_action', value: 'close' }, - i18nLabel: 'Comment_to_leave_on_closing_session', - }); - - settings.add('Livechat_visitor_inactivity_timeout', 3600, { - type: 'int', - group: 'Omnichannel', - section: 'Sessions', - i18nLabel: 'How_long_to_wait_to_consider_visitor_abandonment', - i18nDescription: 'Time_in_seconds', - }); -}); diff --git a/app/livechat/server/config.ts b/app/livechat/server/config.ts new file mode 100644 index 000000000000..dab5fe320ae2 --- /dev/null +++ b/app/livechat/server/config.ts @@ -0,0 +1,559 @@ +import { Meteor } from 'meteor/meteor'; + +import { SettingEditor } from '../../../definition/ISetting'; +import { settingsRegistry } from '../../settings/server'; + +Meteor.startup(function() { + settingsRegistry.addGroup('Omnichannel', function() { + this.add('Livechat_enabled', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + }); + + this.add('Livechat_title', 'Rocket.Chat', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + public: true, + }); + + this.add('Livechat_title_color', '#C1272D', { + type: 'color', + editor: SettingEditor.COLOR, + // allowedTypes: ['color', 'expression'], + group: 'Omnichannel', + section: 'Livechat', + public: true, + }); + + this.add('Livechat_enable_message_character_limit', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + }); + + this.add('Livechat_message_character_limit', 0, { + type: 'int', + group: 'Omnichannel', + section: 'Livechat', + public: true, + }); + + this.add('Livechat_display_offline_form', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Display_offline_form', + }); + + this.add('Livechat_validate_offline_email', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Validate_email_address', + }); + + this.add('Livechat_offline_form_unavailable', '', { + type: 'string', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Offline_form_unavailable_message', + }); + + this.add('Livechat_offline_title', 'Leave a message', { + type: 'string', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Title', + }); + this.add('Livechat_offline_title_color', '#666666', { + type: 'color', + editor: SettingEditor.COLOR, + // allowedTypes: ['color', 'expression'], + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Color', + }); + this.add('Livechat_offline_message', '', { + type: 'string', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Instructions', + i18nDescription: 'Instructions_to_your_visitor_fill_the_form_to_send_a_message', + }); + this.add('Livechat_offline_email', '', { + type: 'string', + group: 'Omnichannel', + i18nLabel: 'Email_address_to_send_offline_messages', + section: 'Livechat', + }); + this.add('Livechat_offline_success_message', '', { + type: 'string', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Offline_success_message', + }); + + this.add('Livechat_allow_switching_departments', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Allow_switching_departments', + }); + + this.add('Livechat_show_agent_info', true, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Show_agent_info', + }); + + this.add('Livechat_show_agent_email', true, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + enableQuery: { _id: 'Livechat_show_agent_info', value: true }, + i18nLabel: 'Show_agent_email', + }); + + this.add('Livechat_request_comment_when_closing_conversation', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + i18nLabel: 'Request_comment_when_closing_conversation', + i18nDescription: 'Request_comment_when_closing_conversation_description', + }); + + this.add('Livechat_conversation_finished_message', '', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Conversation_finished_message', + }); + + this.add('Livechat_conversation_finished_text', '', { + type: 'string', + multiline: true, + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Conversation_finished_text', + }); + + this.add('Livechat_registration_form', true, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Show_preregistration_form', + }); + + this.add('Livechat_name_field_registration_form', true, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Show_name_field', + }); + + this.add('Livechat_email_field_registration_form', true, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Show_email_field', + }); + + this.add('Livechat_guest_count', 1, { type: 'int', group: 'Omnichannel' }); + + this.add('Livechat_Room_Count', 1, { + type: 'int', + group: 'Omnichannel', + i18nLabel: 'Livechat_room_count', + }); + + this.add('Livechat_enabled_when_agent_idle', true, { + type: 'boolean', + group: 'Omnichannel', + i18nLabel: 'Accept_new_livechats_when_agent_is_idle', + }); + + this.add('Livechat_webhookUrl', '', { + type: 'string', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Webhook_URL', + }); + + this.add('Livechat_secret_token', '', { + type: 'string', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Secret_token', + secret: true, + }); + + this.add('Livechat_webhook_on_start', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_chat_start', + }); + + this.add('Livechat_webhook_on_close', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_chat_close', + }); + + this.add('Livechat_webhook_on_chat_taken', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_chat_taken', + }); + + this.add('Livechat_webhook_on_chat_queued', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_chat_queued', + }); + + this.add('Livechat_webhook_on_forward', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_forwarding', + }); + + this.add('Livechat_webhook_on_offline_msg', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_offline_messages', + }); + + this.add('Livechat_webhook_on_visitor_message', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_visitor_message', + }); + + this.add('Livechat_webhook_on_agent_message', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_agent_message', + }); + + this.add('Send_visitor_navigation_history_livechat_webhook_request', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_visitor_navigation_history_on_request', + i18nDescription: 'Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled', + enableQuery: { _id: 'Livechat_Visitor_navigation_as_a_message', value: true }, + }); + + this.add('Livechat_webhook_on_capture', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Send_request_on_lead_capture', + }); + + this.add('Livechat_lead_email_regex', '\\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\\.)+[A-Z]{2,4}\\b', { + type: 'string', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Lead_capture_email_regex', + }); + + this.add('Livechat_lead_phone_regex', '((?:\\([0-9]{1,3}\\)|[0-9]{2})[ \\-]*?[0-9]{4,5}(?:[\\-\\s\\_]{1,2})?[0-9]{4}(?:(?=[^0-9])|$)|[0-9]{4,5}(?:[\\-\\s\\_]{1,2})?[0-9]{4}(?:(?=[^0-9])|$))', { + type: 'string', + group: 'Omnichannel', + section: 'CRM_Integration', + i18nLabel: 'Lead_capture_phone_regex', + }); + + this.add('Livechat_history_monitor_type', 'url', { + type: 'select', + group: 'Omnichannel', + section: 'Livechat', + i18nLabel: 'Monitor_history_for_changes_on', + values: [ + { key: 'url', i18nLabel: 'Page_URL' }, + { key: 'title', i18nLabel: 'Page_title' }, + ], + }); + + this.add('Livechat_Visitor_navigation_as_a_message', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Send_Visitor_navigation_history_as_a_message', + }); + + settingsRegistry.addGroup('Omnichannel', function() { + this.section('Business_Hours', function() { + this.add('Livechat_enable_business_hours', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + i18nLabel: 'Business_hours_enabled', + }); + }); + }); + + this.add('Livechat_continuous_sound_notification_new_livechat_room', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + i18nLabel: 'Continuous_sound_notifications_for_new_livechat_room', + }); + + this.add('Livechat_videocall_enabled', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Videocall_enabled', + i18nDescription: 'Beta_feature_Depends_on_Video_Conference_to_be_enabled', + enableQuery: { _id: 'Jitsi_Enabled', value: true }, + }); + + this.add('Livechat_fileupload_enabled', true, { + type: 'boolean', + group: 'Omnichannel', + public: true, + i18nLabel: 'FileUpload_Enabled', + enableQuery: { _id: 'FileUpload_Enabled', value: true }, + }); + + this.add('Livechat_enable_transcript', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + i18nLabel: 'Transcript_Enabled', + }); + + this.add('Livechat_transcript_message', '', { + type: 'string', + group: 'Omnichannel', + public: true, + i18nLabel: 'Transcript_message', + enableQuery: { _id: 'Livechat_enable_transcript', value: true }, + }); + + this.add('Livechat_registration_form_message', '', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Livechat_registration_form_message', + }); + + this.add('Livechat_AllowedDomainsList', '', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + public: true, + i18nLabel: 'Livechat_AllowedDomainsList', + i18nDescription: 'Domains_allowed_to_embed_the_livechat_widget', + }); + + this.add('Livechat_OfflineMessageToChannel_enabled', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + public: true, + }); + + this.add('Livechat_OfflineMessageToChannel_channel_name', '', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + public: true, + enableQuery: { _id: 'Livechat_OfflineMessageToChannel_enabled', value: true }, + i18nLabel: 'Channel_name', + }); + + this.add('Livechat_Facebook_Enabled', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Facebook', + }); + + this.add('Livechat_Facebook_API_Key', '', { + type: 'string', + group: 'Omnichannel', + section: 'Facebook', + i18nDescription: 'If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours', + }); + + this.add('Livechat_Facebook_API_Secret', '', { + type: 'string', + group: 'Omnichannel', + section: 'Facebook', + i18nDescription: 'If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours', + }); + + this.add('Livechat_RDStation_Token', '', { + type: 'string', + group: 'Omnichannel', + public: false, + section: 'RD Station', + i18nLabel: 'RDStation_Token', + }); + + this.add('Livechat_Routing_Method', 'Auto_Selection', { + type: 'select', + group: 'Omnichannel', + public: true, + section: 'Routing', + values: [ + { key: 'External', i18nLabel: 'External_Service' }, + { key: 'Auto_Selection', i18nLabel: 'Auto_Selection' }, + { key: 'Manual_Selection', i18nLabel: 'Manual_Selection' }, + ], + }); + + this.add('Livechat_accept_chats_with_no_agents', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Routing', + i18nLabel: 'Accept_with_no_online_agents', + i18nDescription: 'Accept_incoming_livechat_requests_even_if_there_are_no_online_agents', + }); + + this.add('Livechat_assign_new_conversation_to_bot', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Routing', + i18nLabel: 'Assign_new_conversations_to_bot_agent', + i18nDescription: 'Assign_new_conversations_to_bot_agent_description', + }); + + this.add('Livechat_guest_pool_max_number_incoming_livechats_displayed', 0, { + type: 'int', + group: 'Omnichannel', + section: 'Routing', + public: true, + i18nLabel: 'Max_number_incoming_livechats_displayed', + i18nDescription: 'Max_number_incoming_livechats_displayed_description', + enableQuery: { _id: 'Livechat_Routing_Method', value: 'Manual_Selection' }, + }); + + this.add('Livechat_show_queue_list_link', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + section: 'Routing', + i18nLabel: 'Show_queue_list_to_all_agents', + enableQuery: { _id: 'Livechat_Routing_Method', value: { $ne: 'External' } }, + }); + + this.add('Livechat_External_Queue_URL', '', { + type: 'string', + group: 'Omnichannel', + public: false, + section: 'Routing', + i18nLabel: 'External_Queue_Service_URL', + i18nDescription: 'For_more_details_please_check_our_docs', + enableQuery: { _id: 'Livechat_Routing_Method', value: 'External' }, + }); + + this.add('Livechat_External_Queue_Token', '', { + type: 'string', + group: 'Omnichannel', + public: false, + section: 'Routing', + i18nLabel: 'Secret_token', + enableQuery: { _id: 'Livechat_Routing_Method', value: 'External' }, + }); + + this.add('Livechat_Allow_collect_and_store_HTTP_header_informations', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'GDPR', + public: true, + i18nLabel: 'Allow_collect_and_store_HTTP_header_informations', + i18nDescription: 'Allow_collect_and_store_HTTP_header_informations_description', + }); + + this.add('Livechat_force_accept_data_processing_consent', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'GDPR', + public: true, + alert: 'Force_visitor_to_accept_data_processing_consent_enabled_alert', + i18nLabel: 'Force_visitor_to_accept_data_processing_consent', + i18nDescription: 'Force_visitor_to_accept_data_processing_consent_description', + }); + + this.add('Livechat_data_processing_consent_text', '', { + type: 'string', + multiline: true, + group: 'Omnichannel', + section: 'GDPR', + public: true, + i18nLabel: 'Data_processing_consent_text', + i18nDescription: 'Data_processing_consent_text_description', + enableQuery: { _id: 'Livechat_force_accept_data_processing_consent', value: true }, + }); + + this.add('Livechat_agent_leave_action', 'none', { + type: 'select', + group: 'Omnichannel', + section: 'Sessions', + values: [ + { key: 'none', i18nLabel: 'None' }, + { key: 'forward', i18nLabel: 'Forward' }, + { key: 'close', i18nLabel: 'Close' }, + ], + i18nLabel: 'How_to_handle_open_sessions_when_agent_goes_offline', + }); + + this.add('Livechat_agent_leave_action_timeout', 60, { + type: 'int', + group: 'Omnichannel', + section: 'Sessions', + enableQuery: { _id: 'Livechat_agent_leave_action', value: { $ne: 'none' } }, + i18nLabel: 'How_long_to_wait_after_agent_goes_offline', + i18nDescription: 'Time_in_seconds', + }); + + this.add('Livechat_agent_leave_comment', '', { + type: 'string', + group: 'Omnichannel', + section: 'Sessions', + enableQuery: { _id: 'Livechat_agent_leave_action', value: 'close' }, + i18nLabel: 'Comment_to_leave_on_closing_session', + }); + + this.add('Livechat_visitor_inactivity_timeout', 3600, { + type: 'int', + group: 'Omnichannel', + section: 'Sessions', + i18nLabel: 'How_long_to_wait_to_consider_visitor_abandonment', + i18nDescription: 'Time_in_seconds', + }); + }); +}); diff --git a/app/livechat/server/externalFrame/settings.js b/app/livechat/server/externalFrame/settings.ts similarity index 87% rename from app/livechat/server/externalFrame/settings.js rename to app/livechat/server/externalFrame/settings.ts index e707e8604de7..367fa3f304b7 100644 --- a/app/livechat/server/externalFrame/settings.js +++ b/app/livechat/server/externalFrame/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings/server/functions/settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('Omnichannel', function() { +settingsRegistry.addGroup('Omnichannel', function() { this.section('External Frame', function() { this.add('Omnichannel_External_Frame_Enabled', false, { type: 'boolean', diff --git a/app/livechat/server/hooks/sendToCRM.js b/app/livechat/server/hooks/sendToCRM.js index 2b80cd7408e7..ac1ec663904b 100644 --- a/app/livechat/server/hooks/sendToCRM.js +++ b/app/livechat/server/hooks/sendToCRM.js @@ -1,4 +1,4 @@ -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { callbacks } from '../../../callbacks'; import { Messages, LivechatRooms } from '../../../models'; import { Livechat } from '../lib/Livechat'; @@ -8,27 +8,12 @@ import { normalizeMessageFileUpload } from '../../../utils/server/functions/norm const msgNavType = 'livechat_navigation_history'; const msgClosingType = 'livechat-close'; -let sendNavHistoryMessageEnabled = false; -let sendNavHistoryWebhookEnabled = false; -let crmWebhookUrl = ''; -settings.get('Livechat_Visitor_navigation_as_a_message', (key, value) => { - sendNavHistoryMessageEnabled = value; -}); -settings.get('Send_visitor_navigation_history_livechat_webhook_request', (key, value) => { - sendNavHistoryWebhookEnabled = value; -}); -settings.get('Livechat_webhookUrl', (key, value) => { - crmWebhookUrl = value; -}); - -const crmEnabled = () => crmWebhookUrl !== '' && crmWebhookUrl !== undefined; - const sendMessageType = (msgType) => { switch (msgType) { case msgClosingType: return true; case msgNavType: - return sendNavHistoryMessageEnabled && sendNavHistoryWebhookEnabled; + return settings.get('Livechat_Visitor_navigation_as_a_message') && settings.get('Send_visitor_navigation_history_livechat_webhook_request'); default: return false; } @@ -51,7 +36,7 @@ const getAdditionalFieldsByType = (type, room) => { } }; function sendToCRM(type, room, includeMessages = true) { - if (crmEnabled() === false) { + if (!settings.get('Livechat_webhookUrl')) { return room; } diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 027e163d6bb5..c5fbbf7a3891 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -1273,6 +1273,6 @@ export const Livechat = { }, }; -settings.get('Livechat_history_monitor_type', (key, value) => { +settings.watch('Livechat_history_monitor_type', (value) => { Livechat.historyMonitorType = value; }); diff --git a/app/livechat/server/lib/RoutingManager.js b/app/livechat/server/lib/RoutingManager.js index 8e1fa4892772..bc8e5ed07453 100644 --- a/app/livechat/server/lib/RoutingManager.js +++ b/app/livechat/server/lib/RoutingManager.js @@ -22,7 +22,7 @@ export const RoutingManager = { methodName: null, methods: {}, - startQueue() { + startQueue() { // todo: move to eventemitter or middleware // queue shouldn't start on CE }, diff --git a/app/livechat/server/lib/stream/agentStatus.ts b/app/livechat/server/lib/stream/agentStatus.ts index c04cd48d5e80..ed46bacbd0f8 100644 --- a/app/livechat/server/lib/stream/agentStatus.ts +++ b/app/livechat/server/lib/stream/agentStatus.ts @@ -8,19 +8,19 @@ let actionTimeout = 60000; let action = 'none'; let comment = ''; -settings.get('Livechat_agent_leave_action_timeout', (_key, value) => { +settings.watch('Livechat_agent_leave_action_timeout', (value) => { if (typeof value !== 'number') { return; } actionTimeout = value * 1000; }); -settings.get('Livechat_agent_leave_action', (_key, value) => { +settings.watch('Livechat_agent_leave_action', (value) => { monitorAgents = value !== 'none'; action = value as string; }); -settings.get('Livechat_agent_leave_comment', (_key, value) => { +settings.watch('Livechat_agent_leave_comment', (value) => { if (typeof value !== 'string') { return; } diff --git a/app/livechat/server/methods/facebook.js b/app/livechat/server/methods/facebook.js index a6ef3b9aac94..90d27222aa2e 100644 --- a/app/livechat/server/methods/facebook.js +++ b/app/livechat/server/methods/facebook.js @@ -4,6 +4,7 @@ import { hasPermission } from '../../../authorization'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { settings } from '../../../settings'; import OmniChannel from '../lib/OmniChannel'; +import { Settings } from '../../../models/server'; Meteor.methods({ 'livechat:facebook'(options) { @@ -27,13 +28,13 @@ Meteor.methods({ return result; } - return settings.updateById('Livechat_Facebook_Enabled', true); + return Settings.updateValueById('Livechat_Facebook_Enabled', true); } case 'disable': { OmniChannel.disable(); - return settings.updateById('Livechat_Facebook_Enabled', false); + return Settings.updateValueById('Livechat_Facebook_Enabled', false); } case 'list-pages': { diff --git a/app/livechat/server/methods/saveAppearance.js b/app/livechat/server/methods/saveAppearance.js index 44a3749d81b4..473695317a07 100644 --- a/app/livechat/server/methods/saveAppearance.js +++ b/app/livechat/server/methods/saveAppearance.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization'; -import { settings as rcSettings } from '../../../settings'; +import { Settings } from '../../../models/server'; Meteor.methods({ 'livechat:saveAppearance'(settings) { @@ -38,7 +38,7 @@ Meteor.methods({ } settings.forEach((setting) => { - rcSettings.updateById(setting._id, setting.value); + Settings.updateValueById(setting._id, setting.value); }); }, }); diff --git a/app/livechat/server/methods/saveIntegration.js b/app/livechat/server/methods/saveIntegration.js index b28ab334256f..38288eab9e0d 100644 --- a/app/livechat/server/methods/saveIntegration.js +++ b/app/livechat/server/methods/saveIntegration.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; +import { Settings } from '../../../models/server'; import { settings } from '../../../settings'; Meteor.methods({ @@ -11,43 +12,43 @@ Meteor.methods({ } if (typeof values.Livechat_webhookUrl !== 'undefined') { - settings.updateById('Livechat_webhookUrl', s.trim(values.Livechat_webhookUrl)); + Settings.updateValueById('Livechat_webhookUrl', s.trim(values.Livechat_webhookUrl)); } if (typeof values.Livechat_secret_token !== 'undefined') { - settings.updateById('Livechat_secret_token', s.trim(values.Livechat_secret_token)); + settings.updateValueById('Livechat_secret_token', s.trim(values.Livechat_secret_token)); } if (typeof values.Livechat_webhook_on_start !== 'undefined') { - settings.updateById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start); + settings.updateValueById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start); } if (typeof values.Livechat_webhook_on_close !== 'undefined') { - settings.updateById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close); + settings.updateValueById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close); } if (typeof values.Livechat_webhook_on_chat_taken !== 'undefined') { - settings.updateById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken); + settings.updateValueById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken); } if (typeof values.Livechat_webhook_on_chat_queued !== 'undefined') { - settings.updateById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued); + settings.updateValueById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued); } if (typeof values.Livechat_webhook_on_forward !== 'undefined') { - settings.updateById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward); + settings.updateValueById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward); } if (typeof values.Livechat_webhook_on_offline_msg !== 'undefined') { - settings.updateById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg); + settings.updateValueById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg); } if (typeof values.Livechat_webhook_on_visitor_message !== 'undefined') { - settings.updateById('Livechat_webhook_on_visitor_message', !!values.Livechat_webhook_on_visitor_message); + settings.updateValueById('Livechat_webhook_on_visitor_message', !!values.Livechat_webhook_on_visitor_message); } if (typeof values.Livechat_webhook_on_agent_message !== 'undefined') { - settings.updateById('Livechat_webhook_on_agent_message', !!values.Livechat_webhook_on_agent_message); + settings.updateValueById('Livechat_webhook_on_agent_message', !!values.Livechat_webhook_on_agent_message); } }, }); diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js index edbbb6fecf36..50b9bd3dc0ec 100644 --- a/app/livechat/server/startup.js +++ b/app/livechat/server/startup.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { roomTypes } from '../../utils'; import { LivechatRooms } from '../../models'; import { callbacks } from '../../callbacks'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import { LivechatAgentActivityMonitor } from './statistics/LivechatAgentActivityMonitor'; import { businessHourManager } from './business-hour'; import { createDefaultBusinessHourIfNotExists } from './business-hour/Helper'; @@ -39,7 +39,7 @@ Meteor.startup(async () => { const monitor = new LivechatAgentActivityMonitor(); let TroubleshootDisableLivechatActivityMonitor; - settings.get('Troubleshoot_Disable_Livechat_Activity_Monitor', (key, value) => { + settings.watch('Troubleshoot_Disable_Livechat_Activity_Monitor', (value) => { if (TroubleshootDisableLivechatActivityMonitor === value) { return; } TroubleshootDisableLivechatActivityMonitor = value; @@ -51,14 +51,14 @@ Meteor.startup(async () => { }); await createDefaultBusinessHourIfNotExists(); - settings.get('Livechat_enable_business_hours', async (key, value) => { + settings.watch('Livechat_enable_business_hours', async (value) => { if (value) { return businessHourManager.startManager(); } return businessHourManager.stopManager(); }); - settings.get('Livechat_Routing_Method', function(key, value) { + settings.watch('Livechat_Routing_Method', function(value) { RoutingManager.setMethodNameAndStartQueue(value); }); diff --git a/app/livestream/server/settings.js b/app/livestream/server/settings.ts similarity index 89% rename from app/livestream/server/settings.js rename to app/livestream/server/settings.ts index 5fc796efa87d..75658206347d 100644 --- a/app/livestream/server/settings.js +++ b/app/livestream/server/settings.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; Meteor.startup(function() { - settings.addGroup('LiveStream & Broadcasting', function() { + settingsRegistry.addGroup('LiveStream & Broadcasting', function() { this.add('Livestream_enabled', false, { type: 'boolean', public: true, diff --git a/app/mail-messages/server/index.js b/app/mail-messages/server/index.js index a05bc57cc61e..25c66f5dafd6 100644 --- a/app/mail-messages/server/index.js +++ b/app/mail-messages/server/index.js @@ -1,4 +1,3 @@ -import './startup'; import './methods/sendMail'; import './methods/unsubscribe'; import { Mailer } from './lib/Mailer'; diff --git a/app/mail-messages/server/startup.js b/app/mail-messages/server/startup.js deleted file mode 100644 index eadfc796e776..000000000000 --- a/app/mail-messages/server/startup.js +++ /dev/null @@ -1,7 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Permissions } from '../../models'; - -Meteor.startup(function() { - return Permissions.create('access-mailer', ['admin']); -}); diff --git a/app/mailer/server/api.js b/app/mailer/server/api.js index 1405841f1b45..4be41b050884 100644 --- a/app/mailer/server/api.js +++ b/app/mailer/server/api.js @@ -21,7 +21,7 @@ let Settings = { // define server language for email translations // @TODO: change TAPi18n.__ function to use the server language by default let lng = 'en'; -settings.get('Language', (key, value) => { +settings.watch('Language', (value) => { lng = value || 'en'; }); @@ -39,7 +39,7 @@ export const replace = function replace(str, data = {}) { const options = { Site_Name: Settings.get('Site_Name'), Site_URL: Settings.get('Site_Url'), - Site_URL_Slash: Settings.get('Site_Url').replace(/\/?$/, '/'), + Site_URL_Slash: Settings.get('Site_Url')?.replace(/\/?$/, '/'), ...data.name && { fname: s.strLeft(data.name, ' '), lname: s.strRightBack(data.name, ' '), @@ -67,7 +67,10 @@ export const wrap = (html, data = {}) => { return replaceEscaped(body.replace('{{body}}', html), data); }; -export const inlinecss = (html) => juice.inlineContent(html, Settings.get('email_style')); +export const inlinecss = (html) => { + const css = Settings.get('email_style'); + return css ? juice.inlineContent(html, css) : html; +}; export const getTemplate = (template, fn, escape = true) => { let html = ''; Settings.get(template, (key, value) => { @@ -92,7 +95,6 @@ export const getTemplateWrapped = (template, fn) => { }; export const setSettings = (s) => { Settings = s; - getTemplate('Email_Header', (value) => { contentHeader = replace(value || ''); body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); diff --git a/app/mapview/server/settings.js b/app/mapview/server/settings.js deleted file mode 100644 index 96c604de513e..000000000000 --- a/app/mapview/server/settings.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.add('MapView_Enabled', false, { type: 'boolean', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_Enabled', i18nDescription: 'MapView_Enabled_Description' }); - return settings.add('MapView_GMapsAPIKey', '', { type: 'string', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_GMapsAPIKey', i18nDescription: 'MapView_GMapsAPIKey_Description', secret: true }); -}); diff --git a/app/mapview/server/settings.ts b/app/mapview/server/settings.ts new file mode 100644 index 000000000000..9fccbb864783 --- /dev/null +++ b/app/mapview/server/settings.ts @@ -0,0 +1,8 @@ +import { Meteor } from 'meteor/meteor'; + +import { settingsRegistry } from '../../settings/server'; + +Meteor.startup(function() { + settingsRegistry.add('MapView_Enabled', false, { type: 'boolean', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_Enabled', i18nDescription: 'MapView_Enabled_Description' }); + settingsRegistry.add('MapView_GMapsAPIKey', '', { type: 'string', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_GMapsAPIKey', i18nDescription: 'MapView_GMapsAPIKey_Description', secret: true }); +}); diff --git a/app/markdown/server/settings.js b/app/markdown/server/settings.js deleted file mode 100644 index 71f3dc02c7f6..000000000000 --- a/app/markdown/server/settings.js +++ /dev/null @@ -1,89 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(() => { - settings.add('Markdown_Parser', 'original', { - type: 'select', - values: [{ - key: 'disabled', - i18nLabel: 'Disabled', - }, { - key: 'original', - i18nLabel: 'Original', - }, { - key: 'marked', - i18nLabel: 'Marked', - }], - group: 'Message', - section: 'Markdown', - public: true, - }); - - const enableQueryOriginal = { _id: 'Markdown_Parser', value: 'original' }; - settings.add('Markdown_Headers', false, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryOriginal, - }); - settings.add('Markdown_SupportSchemesForLink', 'http,https', { - type: 'string', - group: 'Message', - section: 'Markdown', - public: true, - i18nDescription: 'Markdown_SupportSchemesForLink_Description', - enableQuery: enableQueryOriginal, - }); - - const enableQueryMarked = { _id: 'Markdown_Parser', value: 'marked' }; - settings.add('Markdown_Marked_GFM', true, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryMarked, - }); - settings.add('Markdown_Marked_Tables', true, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryMarked, - }); - settings.add('Markdown_Marked_Breaks', true, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryMarked, - }); - settings.add('Markdown_Marked_Pedantic', false, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: [{ - _id: 'Markdown_Parser', - value: 'marked', - }, { - _id: 'Markdown_Marked_GFM', - value: false, - }], - }); - settings.add('Markdown_Marked_SmartLists', true, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryMarked, - }); - settings.add('Markdown_Marked_Smartypants', true, { - type: 'boolean', - group: 'Message', - section: 'Markdown', - public: true, - enableQuery: enableQueryMarked, - }); -}); diff --git a/app/markdown/server/settings.ts b/app/markdown/server/settings.ts new file mode 100644 index 000000000000..6a1b5262d411 --- /dev/null +++ b/app/markdown/server/settings.ts @@ -0,0 +1,85 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.add('Markdown_Parser', 'original', { + type: 'select', + values: [{ + key: 'disabled', + i18nLabel: 'Disabled', + }, { + key: 'original', + i18nLabel: 'Original', + }, { + key: 'marked', + i18nLabel: 'Marked', + }], + group: 'Message', + section: 'Markdown', + public: true, +}); + +const enableQueryOriginal = { _id: 'Markdown_Parser', value: 'original' }; +settingsRegistry.add('Markdown_Headers', false, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryOriginal, +}); +settingsRegistry.add('Markdown_SupportSchemesForLink', 'http,https', { + type: 'string', + group: 'Message', + section: 'Markdown', + public: true, + i18nDescription: 'Markdown_SupportSchemesForLink_Description', + enableQuery: enableQueryOriginal, +}); + +const enableQueryMarked = { _id: 'Markdown_Parser', value: 'marked' }; +settingsRegistry.add('Markdown_Marked_GFM', true, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryMarked, +}); +settingsRegistry.add('Markdown_Marked_Tables', true, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryMarked, +}); +settingsRegistry.add('Markdown_Marked_Breaks', true, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryMarked, +}); +settingsRegistry.add('Markdown_Marked_Pedantic', false, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: [{ + _id: 'Markdown_Parser', + value: 'marked', + }, { + _id: 'Markdown_Marked_GFM', + value: false, + }], +}); +settingsRegistry.add('Markdown_Marked_SmartLists', true, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryMarked, +}); +settingsRegistry.add('Markdown_Marked_Smartypants', true, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true, + enableQuery: enableQueryMarked, +}); diff --git a/app/message-pin/server/settings.js b/app/message-pin/server/settings.js deleted file mode 100644 index c16f82a183c3..000000000000 --- a/app/message-pin/server/settings.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; -import { Permissions } from '../../models'; - -Meteor.startup(function() { - settings.add('Message_AllowPinning', true, { - type: 'boolean', - group: 'Message', - public: true, - }); - return Permissions.create('pin-message', ['owner', 'moderator', 'admin']); -}); diff --git a/app/message-pin/server/settings.ts b/app/message-pin/server/settings.ts new file mode 100644 index 000000000000..cc8b96197895 --- /dev/null +++ b/app/message-pin/server/settings.ts @@ -0,0 +1,7 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.add('Message_AllowPinning', true, { + type: 'boolean', + group: 'Message', + public: true, +}); diff --git a/app/message-snippet/server/startup/settings.js b/app/message-snippet/server/startup/settings.js deleted file mode 100644 index 15f8c349e8e6..000000000000 --- a/app/message-snippet/server/startup/settings.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../../settings'; -import { Permissions } from '../../../models'; - -Meteor.startup(function() { - settings.add('Message_AllowSnippeting', false, { - type: 'boolean', - public: true, - group: 'Message', - }); - Permissions.create('snippet-message', ['owner', 'moderator', 'admin']); -}); diff --git a/app/message-snippet/server/startup/settings.ts b/app/message-snippet/server/startup/settings.ts new file mode 100644 index 000000000000..c7de88a07d01 --- /dev/null +++ b/app/message-snippet/server/startup/settings.ts @@ -0,0 +1,7 @@ +import { settingsRegistry } from '../../../settings/server'; + +settingsRegistry.add('Message_AllowSnippeting', false, { + type: 'boolean', + public: true, + group: 'Message', +}); diff --git a/app/message-star/server/settings.js b/app/message-star/server/settings.js deleted file mode 100644 index a951d82fc273..000000000000 --- a/app/message-star/server/settings.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - return settings.add('Message_AllowStarring', true, { - type: 'boolean', - group: 'Message', - public: true, - }); -}); diff --git a/app/message-star/server/settings.ts b/app/message-star/server/settings.ts new file mode 100644 index 000000000000..5eeb4cb4a0fd --- /dev/null +++ b/app/message-star/server/settings.ts @@ -0,0 +1,7 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.add('Message_AllowStarring', true, { + type: 'boolean', + group: 'Message', + public: true, +}); diff --git a/app/meteor-accounts-saml/server/lib/settings.ts b/app/meteor-accounts-saml/server/lib/settings.ts index 38b2c8e9aa9b..6459a243d8e6 100644 --- a/app/meteor-accounts-saml/server/lib/settings.ts +++ b/app/meteor-accounts-saml/server/lib/settings.ts @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { settings } from '../../../settings/server'; +import { settings, settingsRegistry } from '../../../settings/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; -import { SettingComposedValue } from '../../../settings/lib/settings'; import { IServiceProviderOptions } from '../definition/IServiceProviderOptions'; import { SAMLUtils } from './Utils'; import { @@ -104,16 +103,16 @@ export const configureSamlService = function(samlConfigs: Record): export const loadSamlServiceProviders = function(): void { const serviceName = 'saml'; - const services = settings.get(/^(SAML_Custom_)[a-z]+$/i) as SettingComposedValue[] | undefined; + const services = settings.getByRegexp(/^(SAML_Custom_)[a-z]+$/i); if (!services) { return SAMLUtils.setServiceProvidersList([]); } - const providers = services.map((service) => { - if (service.value === true) { - const samlConfigs = getSamlConfigs(service.key); - SAMLUtils.log(service.key); + const providers = services.map(([key, value]) => { + if (value === true) { + const samlConfigs = getSamlConfigs(key); + SAMLUtils.log(key); ServiceConfiguration.configurations.upsert({ service: serviceName.toLowerCase(), }, { @@ -135,8 +134,8 @@ export const addSamlService = function(name: string): void { }; export const addSettings = function(name: string): void { - settings.addGroup('SAML', function() { - this.set({ + settingsRegistry.addGroup('SAML', function() { + this.with({ tab: 'SAML_Connection', }, function() { this.add(`SAML_Custom_${ name }`, false, { @@ -196,7 +195,7 @@ export const addSettings = function(name: string): void { }); }); - this.set({ + this.with({ tab: 'SAML_General', }, function() { this.section('SAML_Section_1_User_Interface', function() { diff --git a/app/meteor-accounts-saml/server/startup.ts b/app/meteor-accounts-saml/server/startup.ts index b915ad7b60f2..3417589e48d8 100644 --- a/app/meteor-accounts-saml/server/startup.ts +++ b/app/meteor-accounts-saml/server/startup.ts @@ -1,5 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; import { settings } from '../../settings/server'; import { loadSamlServiceProviders, addSettings } from './lib/settings'; @@ -8,12 +7,7 @@ import { SAMLUtils } from './lib/Utils'; export const logger = new Logger('steffo:meteor-accounts-saml'); SAMLUtils.setLoggerInstance(logger); - -const updateServices = _.debounce(Meteor.bindEnvironment(() => { - loadSamlServiceProviders(); -}), 2000); - Meteor.startup(() => { addSettings('Default'); - settings.get(/^SAML_.+/, updateServices); + settings.watchByRegex(/^SAML_.+/, loadSamlServiceProviders); }); diff --git a/app/metrics/server/lib/collectMetrics.js b/app/metrics/server/lib/collectMetrics.js index 022d9d476eb1..98267f9ca855 100644 --- a/app/metrics/server/lib/collectMetrics.js +++ b/app/metrics/server/lib/collectMetrics.js @@ -182,5 +182,5 @@ const updatePrometheusConfig = async () => { }; Meteor.startup(async () => { - settings.get(/^Prometheus_.+/, updatePrometheusConfig); + settings.watchByRegex(/^Prometheus_.+/, updatePrometheusConfig); }); diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js index 53d5dba3cf37..a58ea9f1c3e7 100644 --- a/app/models/server/models/Settings.js +++ b/app/models/server/models/Settings.js @@ -247,6 +247,10 @@ export class Settings extends Base { } } } + + insert(record, ...args) { + return super.insert({ createdAt: new Date(), ...record }, ...args); + } } export default new Settings('settings', true); diff --git a/app/nextcloud/server/addWebdavServer.js b/app/nextcloud/server/addWebdavServer.js index 89622e54fd79..22cdd337da5d 100644 --- a/app/nextcloud/server/addWebdavServer.js +++ b/app/nextcloud/server/addWebdavServer.js @@ -5,7 +5,7 @@ import { settings } from '../../settings/server'; import { SystemLogger } from '../../../server/lib/logger/system'; Meteor.startup(() => { - settings.get('Webdav_Integration_Enabled', (key, value) => { + settings.watch('Webdav_Integration_Enabled', (value) => { if (value) { return callbacks.add('afterValidateLogin', (login) => { const { user } = login; diff --git a/app/nextcloud/server/startup.js b/app/nextcloud/server/startup.ts similarity index 86% rename from app/nextcloud/server/startup.js rename to app/nextcloud/server/startup.ts index f995a2a460ae..9a5f3cff0e14 100644 --- a/app/nextcloud/server/startup.js +++ b/app/nextcloud/server/startup.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('Nextcloud', function() { const enableQuery = { _id: 'Accounts_OAuth_Nextcloud', @@ -11,7 +11,7 @@ settings.addGroup('OAuth', function() { this.add('Accounts_OAuth_Nextcloud_URL', '', { type: 'string', enableQuery, public: true }); this.add('Accounts_OAuth_Nextcloud_id', '', { type: 'string', enableQuery }); this.add('Accounts_OAuth_Nextcloud_secret', '', { type: 'string', enableQuery }); - this.add('Accounts_OAuth_Nextcloud_callback_url', '_oauth/nextcloud', { type: 'relativeUrl', readonly: true, force: true, enableQuery }); + this.add('Accounts_OAuth_Nextcloud_callback_url', '_oauth/nextcloud', { type: 'relativeUrl', readonly: true, enableQuery }); this.add('Accounts_OAuth_Nextcloud_button_label_text', 'Nextcloud', { type: 'string', public: true, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true }); this.add('Accounts_OAuth_Nextcloud_button_label_color', '#ffffff', { type: 'string', public: true, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true }); this.add('Accounts_OAuth_Nextcloud_button_color', '#0082c9', { type: 'string', public: true, i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true }); diff --git a/app/oembed/server/server.js b/app/oembed/server/server.js index b859b4ed80de..05f95cfbc0dc 100644 --- a/app/oembed/server/server.js +++ b/app/oembed/server/server.js @@ -302,7 +302,7 @@ OEmbed.rocketUrlParser = function(message) { return message; }; -settings.get('API_Embed', function(key, value) { +settings.watch('API_Embed', function(value) { if (value) { return callbacks.add('afterSaveMessage', OEmbed.rocketUrlParser, callbacks.priority.LOW, 'API_Embed'); } diff --git a/app/otr/server/settings.js b/app/otr/server/settings.js deleted file mode 100644 index fbecb9c7519f..000000000000 --- a/app/otr/server/settings.js +++ /dev/null @@ -1,9 +0,0 @@ -import { settings } from '../../settings'; - -settings.addGroup('OTR', function() { - this.add('OTR_Enable', true, { - type: 'boolean', - i18nLabel: 'Enabled', - public: true, - }); -}); diff --git a/app/otr/server/settings.ts b/app/otr/server/settings.ts new file mode 100644 index 000000000000..6d42a3e29fd2 --- /dev/null +++ b/app/otr/server/settings.ts @@ -0,0 +1,9 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.addGroup('OTR', function() { + this.add('OTR_Enable', true, { + type: 'boolean', + i18nLabel: 'Enabled', + public: true, + }); +}); diff --git a/app/retention-policy/server/cronPruneMessages.js b/app/retention-policy/server/cronPruneMessages.js index 5508747d1f36..63cb51d20742 100644 --- a/app/retention-policy/server/cronPruneMessages.js +++ b/app/retention-policy/server/cronPruneMessages.js @@ -1,6 +1,4 @@ -import { Meteor } from 'meteor/meteor'; import { SyncedCron } from 'meteor/littledata:synced-cron'; -import { debounce } from 'underscore'; import { settings } from '../../settings/server'; import { Rooms } from '../../models/server'; @@ -79,7 +77,16 @@ function deployCron(precision) { }); } -const reloadPolicy = debounce(Meteor.bindEnvironment(function reloadPolicy() { +settings.watchMultiple(['RetentionPolicy_Enabled', + 'RetentionPolicy_AppliesToChannels', + 'RetentionPolicy_AppliesToGroups', + 'RetentionPolicy_AppliesToDMs', + 'RetentionPolicy_MaxAge_Channels', + 'RetentionPolicy_MaxAge_Groups', + 'RetentionPolicy_MaxAge_DMs', + 'RetentionPolicy_Advanced_Precision', + 'RetentionPolicy_Advanced_Precision_Cron', + 'RetentionPolicy_Precision'], function reloadPolicy() { types = []; if (!settings.get('RetentionPolicy_Enabled')) { @@ -105,11 +112,4 @@ const reloadPolicy = debounce(Meteor.bindEnvironment(function reloadPolicy() { const precision = (settings.get('RetentionPolicy_Advanced_Precision') && settings.get('RetentionPolicy_Advanced_Precision_Cron')) || getSchedule(settings.get('RetentionPolicy_Precision')); return deployCron(precision); -}), 500); - -Meteor.startup(function() { - Meteor.defer(function() { - settings.get(/^RetentionPolicy_/, reloadPolicy); - reloadPolicy(); - }); }); diff --git a/app/retention-policy/server/startup/settings.js b/app/retention-policy/server/startup/settings.ts similarity index 96% rename from app/retention-policy/server/startup/settings.js rename to app/retention-policy/server/startup/settings.ts index 94696f7651f4..327c5e438fb7 100644 --- a/app/retention-policy/server/startup/settings.js +++ b/app/retention-policy/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('RetentionPolicy', function() { +settingsRegistry.addGroup('RetentionPolicy', function() { const globalQuery = { _id: 'RetentionPolicy_Enabled', value: true, diff --git a/app/search/server/search.internalService.ts b/app/search/server/search.internalService.ts index b41e988c2eef..1e5efaa43569 100644 --- a/app/search/server/search.internalService.ts +++ b/app/search/server/search.internalService.ts @@ -1,5 +1,3 @@ -import _ from 'underscore'; - import { Users } from '../../models/server'; import { settings } from '../../settings/server'; import { searchProviderService } from './service/providerService'; @@ -38,10 +36,10 @@ class Search extends ServiceClass { const service = new Search(); -settings.get('Search.Provider', _.debounce(() => { +settings.watch('Search.Provider', () => { if (searchProviderService.activeProvider?.on) { api.registerService(service); } else { api.destroyService(service); } -}, 1000)); +}); diff --git a/app/search/server/service/providerService.js b/app/search/server/service/providerService.js index 2288a06e390b..504c78067aa8 100644 --- a/app/search/server/service/providerService.js +++ b/app/search/server/service/providerService.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { validationService } from './validationService'; -import { settings } from '../../../settings'; +import { settings, settingsRegistry } from '../../../settings/server'; import SearchLogger from '../logger/logger'; class SearchProviderService { @@ -72,7 +72,7 @@ class SearchProviderService { const { providers } = this; // add settings for admininistration - settings.addGroup('Search', function() { + settingsRegistry.addGroup('Search', function() { const self = this; self.add('Search.Provider', 'defaultProvider', { @@ -114,7 +114,7 @@ class SearchProviderService { } }), 1000); - settings.get(/^Search\./, configProvider); + settings.watchByRegex(/^Search\./, configProvider); } } diff --git a/app/settings/client/lib/settings.ts b/app/settings/client/lib/settings.ts index c0e8e0219185..1c413cbc7848 100644 --- a/app/settings/client/lib/settings.ts +++ b/app/settings/client/lib/settings.ts @@ -12,10 +12,13 @@ class Settings extends SettingsBase { dict = new ReactiveDict('settings'); - get(_id: string | RegExp): any { + get(_id: string | RegExp, ...args: []): any { if (_id instanceof RegExp) { throw new Error('RegExp Settings.get(RegExp)'); } + if (args.length > 0) { + throw new Error('settings.get(String, callback) only works on backend'); + } return this.dict.get(_id); } diff --git a/app/settings/server/CachedSettings.ts b/app/settings/server/CachedSettings.ts new file mode 100644 index 000000000000..95665e3e53d9 --- /dev/null +++ b/app/settings/server/CachedSettings.ts @@ -0,0 +1,439 @@ +import { Emitter } from '@rocket.chat/emitter'; +import _ from 'underscore'; + +import { ISetting, SettingValue } from '../../../definition/ISetting'; +import { SystemLogger } from '../../../server/lib/logger/system'; + +const warn = process.env.NODE_ENV === 'development' || process.env.TEST_MODE; + +type SettingsConfig = { + debounce: number; +} + +type OverCustomSettingsConfig = Partial; + +export interface ICachedSettings { + /* + * @description: The settings object as ready + */ + initilized(): void; + + /* + * returns if the setting is defined + * @param _id - The setting id + * @returns {boolean} + */ + has(_id: ISetting['_id']): boolean; + + + /* + * Gets the current Object of the setting + * @param _id - The setting id + * @returns {ISetting} - The current Object of the setting + */ + getSetting(_id: ISetting['_id']): ISetting | undefined; + + /* + * Gets the current value of the setting + * @remarks + * - In development mode if you are trying to get the value of a setting that is not defined, it will give an warning, in theory it makes sense, there no reason to do that + * @param _id - The setting id + * @returns {SettingValue} - The current value of the setting + */ + get(_id: ISetting['_id']): T; + + /* + * Gets the current value of the setting + * @remarks + * - In development mode if you are trying to get the value of a setting that is not defined, it will give an warning, in theory it makes sense, there no reason to do that + * @param _id - The setting id + * @returns {SettingValue} - The current value of the setting + * + */ + /* @deprecated */ + getByRegexp(_id: RegExp): [string, T][]; + + /* + * Get the current value of the settings, and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _ids - Array of setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + watchMultiple(_id: ISetting['_id'][], callback: (settings: T[]) => void): () => void; + + /* + * Get the current value of the setting, and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + watch(_id: ISetting['_id'], cb: (args: T) => void, config?: OverCustomSettingsConfig): () => void; + + /* + * Get the current value of the setting, or wait until the initialized + * @remarks + * - This is a one time run + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + watchOnce(_id: ISetting['_id'], cb: (args: T) => void, config?: OverCustomSettingsConfig): () => void; + + + /* + * Observes the given setting by id and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + change(_id: ISetting['_id'], callback: (args: T) => void, config?: OverCustomSettingsConfig): () => void; + + /* + * Observes multiple settings and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _ids - Array of setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + changeMultiple(_ids: ISetting['_id'][], callback: (settings: T[]) => void, config?: OverCustomSettingsConfig): () => void; + + /* + * Observes the setting and fires only if there is a change. Runs only once + * @remarks + * - This is a one time run + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + changeOnce(_id: ISetting['_id'], callback: (args: T) => void, config?: OverCustomSettingsConfig): () => void; + + /* + * Sets the value of the setting + * @remarks + * - if the value set is the same as the current value, the change will not be fired + * - if the value is set before the initialization, the emit will be queued and will be fired after initialization + * @param _id - The setting id + * @param value - The value to set + * @returns {void} + */ + set(record: ISetting): void ; + + getConfig(config?: OverCustomSettingsConfig): SettingsConfig; + + /* @deprecated */ + watchByRegex(regex: RegExp, cb: (...args: [string, SettingValue]) => void, config?: OverCustomSettingsConfig): () => void; + + /* @deprecated */ + changeByRegex(regex: RegExp, callback: (...args: [string, SettingValue]) => void, config?: OverCustomSettingsConfig): () => void; + + /* + * @description: Wait until the settings get ready then run the callback + */ + onReady(cb: () => void): void; +} + + +/** + * Class responsible for setting up the settings, cache and propagation changes + * Should be agnostic to the actual settings implementation, running on meteor or standalone + * + * You should not instantiate this class directly, only for testing purposes + * + * @extends Emitter + * @alpha + */ +export class CachedSettings extends Emitter< +{ + '*': [string, SettingValue]; +} +& +{ + ready: undefined; + [k: string]: SettingValue; +}> implements ICachedSettings { + ready = false; + + store = new Map(); + + initilized(): void { + if (this.ready) { + return; + } + this.ready = true; + this.emit('ready'); + SystemLogger.debug('Settings initalized'); + } + + /* + * returns if the setting is defined + * @param _id - The setting id + * @returns {boolean} + */ + public has(_id: ISetting['_id']): boolean { + if (!this.ready && warn) { + SystemLogger.warn(`Settings not initialized yet. getting: ${ _id }`); + } + return this.store.has(_id); + } + + public getSetting(_id: ISetting['_id']): ISetting | undefined { + if (!this.ready && warn) { + SystemLogger.warn(`Settings not initialized yet. getting: ${ _id }`); + } + return this.store.get(_id); + } + + + /* + * Gets the current value of the setting + * @remarks + * - In development mode if you are trying to get the value of a setting that is not defined, it will give an warning, in theory it makes sense, there no reason to do that + * @param _id - The setting id + * @returns {SettingValue} - The current value of the setting + */ + public get(_id: ISetting['_id']): T { + if (!this.ready && warn) { + SystemLogger.warn(`Settings not initialized yet. getting: ${ _id }`); + } + return this.store.get(_id)?.value as T; + } + + /* + * Gets the current value of the setting + * @remarks + * - In development mode if you are trying to get the value of a setting that is not defined, it will give an warning, in theory it makes sense, there no reason to do that + * @param _id - The setting id + * @returns {SettingValue} - The current value of the setting + * + */ + /* @deprecated */ + public getByRegexp(_id: RegExp): [string, T][] { + if (!this.ready && warn) { + SystemLogger.warn(`Settings not initialized yet. getting: ${ _id }`); + } + + return [...this.store.entries()].filter(([key]) => _id.test(key)).map(([key, setting]) => [key, setting.value]) as [string, T][]; + } + + + /* + * Get the current value of the settings, and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _ids - Array of setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public watchMultiple(_id: ISetting['_id'][], callback: (settings: T[]) => void): () => void { + if (!this.ready) { + const cancel = new Set<() => void>(); + + cancel.add(this.once('ready', (): void => { + cancel.clear(); + cancel.add(this.watchMultiple(_id, callback)); + })); + return (): void => { + cancel.forEach((fn) => fn()); + }; + } + + if (_id.every((id) => this.store.has(id))) { + const settings = _id.map((id) => this.store.get(id)?.value); + callback(settings as T[]); + } + const mergeFunction = _.debounce((): void => { + callback(_id.map((id) => this.store.get(id)?.value) as T[]); + }, 100); + + const fns = _id.map((id) => this.on(id, mergeFunction)); + return (): void => { + fns.forEach((fn) => fn()); + }; + } + + /* + * Get the current value of the setting, and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public watch(_id: ISetting['_id'], cb: (args: T) => void, config?: OverCustomSettingsConfig): () => void { + if (!this.ready) { + const cancel = new Set<() => void>(); + cancel.add(this.once('ready', (): void => { + cancel.clear(); + cancel.add(this.watch(_id, cb, config)); + })); + return (): void => { + cancel.forEach((fn) => fn()); + }; + } + + this.store.has(_id) && cb(this.store.get(_id)?.value as T); + return this.change(_id, cb, config); + } + + /* + * Get the current value of the setting, or wait until the initialized + * @remarks + * - This is a one time run + * - This callback is debounced + * - The callback is not fire until the settings got initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public watchOnce(_id: ISetting['_id'], cb: (args: T) => void, config?: OverCustomSettingsConfig): () => void { + if (this.store.has(_id)) { + cb(this.store.get(_id)?.value as T); + return (): void => undefined; + } + return this.changeOnce(_id, cb, config); + } + + + /* + * Observes the given setting by id and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public change(_id: ISetting['_id'], callback: (args: T) => void, config?: OverCustomSettingsConfig): () => void { + const { debounce } = this.getConfig(config); + return this.on(_id, _.debounce(callback, debounce) as any); + } + + /* + * Observes multiple settings and keep track of changes + * @remarks + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _ids - Array of setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public changeMultiple(_ids: ISetting['_id'][], callback: (settings: T[]) => void, config?: OverCustomSettingsConfig): () => void { + const fns = _ids.map((id) => this.change(id, (): void => { + callback(_ids.map((id) => this.store.get(id)?.value) as T[]); + }, config)); + return (): void => { + fns.forEach((fn) => fn()); + }; + } + + /* + * Observes the setting and fires only if there is a change. Runs only once + * @remarks + * - This is a one time run + * - This callback is debounced + * - The callback is not fire until the setting is changed + * - The callback is not fire until all the settings get initialized + * @param _id - The setting id + * @param callback - The callback to run + * @returns {() => void} - A function that can be used to cancel the observe + */ + public changeOnce(_id: ISetting['_id'], callback: (args: T) => void, config?: OverCustomSettingsConfig): () => void { + const { debounce } = this.getConfig(config); + return this.once(_id, _.debounce(callback, debounce) as any); + } + + /* + * Sets the value of the setting + * @remarks + * - if the value set is the same as the current value, the change will not be fired + * - if the value is set before the initialization, the emit will be queued and will be fired after initialization + * @param _id - The setting id + * @param value - The value to set + * @returns {void} + */ + public set(record: ISetting): void { + if (this.store.has(record._id) && this.store.get(record._id)?.value === record.value) { + return; + } + + this.store.set(record._id, record); + if (!this.ready) { + this.once('ready', () => { + this.emit(record._id, this.store.get(record._id)?.value); + this.emit('*', [record._id, this.store.get(record._id)?.value]); + }); + return; + } + this.emit(record._id, this.store.get(record._id)?.value); + this.emit('*', [record._id, this.store.get(record._id)?.value]); + } + + public getConfig = (config?: OverCustomSettingsConfig): SettingsConfig => ({ + debounce: 500, + ...config, + }) + + /* @deprecated */ + public watchByRegex(regex: RegExp, cb: (...args: [string, SettingValue]) => void, config?: OverCustomSettingsConfig): () => void { + if (!this.ready) { + const cancel = new Set<() => void>(); + cancel.add(this.once('ready', (): void => { + cancel.clear(); + cancel.add(this.watchByRegex(regex, cb, config)); + })); + return (): void => { + cancel.forEach((fn) => fn()); + }; + } + [...this.store.entries()].forEach(([key, setting]) => { + if (regex.test(key)) { + cb(key, setting.value); + } + }); + + return this.changeByRegex(regex, cb, config); + } + + /* @deprecated */ + public changeByRegex(regex: RegExp, callback: (...args: [string, SettingValue]) => void, config?: OverCustomSettingsConfig): () => void { + const store: Map void> = new Map(); + return this.on('*', ([_id, value]) => { + if (regex.test(_id)) { + const { debounce } = this.getConfig(config); + const cb = store.get(_id) || _.debounce(callback, debounce); + cb(_id, value); + store.set(_id, cb); + } + regex.lastIndex = 0; + }); + } + + public onReady(cb: () => void): void { + if (this.ready) { + return cb(); + } + this.once('ready', cb); + } +} diff --git a/app/settings/server/Middleware.ts b/app/settings/server/Middleware.ts new file mode 100644 index 000000000000..6f523feb6fbc --- /dev/null +++ b/app/settings/server/Middleware.ts @@ -0,0 +1,10 @@ +type Next any> = (...args: Parameters) => ReturnType + +type Middleware any> = (context: Parameters, next: Next) => ReturnType + +const pipe = any>(fn: T) => (...args: Parameters): ReturnType => fn(...args); + + +export const use = any>(fn: T, middleware: Middleware): T => function(this: unknown, ...context: Parameters): ReturnType { + return middleware(context, pipe(fn.bind(this))); +} as T; diff --git a/app/settings/server/SettingsRegistry.ts b/app/settings/server/SettingsRegistry.ts new file mode 100644 index 000000000000..1b7e81f93c04 --- /dev/null +++ b/app/settings/server/SettingsRegistry.ts @@ -0,0 +1,214 @@ +import { Emitter } from '@rocket.chat/emitter'; +import { isEqual } from 'underscore'; + +import type SettingsModel from '../../models/server/models/Settings'; +import { ISetting, ISettingGroup, isSettingEnterprise, SettingValue } from '../../../definition/ISetting'; +import { SystemLogger } from '../../../server/lib/logger/system'; +import { overwriteSetting } from './functions/overwriteSetting'; +import { overrideSetting } from './functions/overrideSetting'; +import { getSettingDefaults } from './functions/getSettingDefaults'; +import { validateSetting } from './functions/validateSetting'; +import type { ICachedSettings } from './CachedSettings'; + +export const blockedSettings = new Set(); +export const hiddenSettings = new Set(); +export const wizardRequiredSettings = new Set(); + +if (process.env.SETTINGS_BLOCKED) { + process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => blockedSettings.add(settingId.trim())); +} + +if (process.env.SETTINGS_HIDDEN) { + process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => hiddenSettings.add(settingId.trim())); +} + +if (process.env.SETTINGS_REQUIRED_ON_WIZARD) { + process.env.SETTINGS_REQUIRED_ON_WIZARD.split(',').forEach((settingId) => wizardRequiredSettings.add(settingId.trim())); +} + +const IS_DEVELOPMENT = process.env.NODE_ENV === 'development'; + +/* +* @deprecated +* please do not use event emitter to mutate values +*/ +export const SettingsEvents = new Emitter<{ + 'store-setting-value': [ISetting, { value: SettingValue }]; + 'fetch-settings': ISetting[]; + 'remove-setting-value': ISetting; +}>(); + + +const getGroupDefaults = (_id: string, options: ISettingAddGroupOptions = {}): ISettingGroup => ({ + _id, + i18nLabel: _id, + i18nDescription: `${ _id }_Description`, + ...options, + sorter: options.sorter || 0, + blocked: blockedSettings.has(_id), + hidden: hiddenSettings.has(_id), + type: 'group', + ...options.displayQuery && { displayQuery: JSON.stringify(options.displayQuery) }, +}); + +export type ISettingAddGroupOptions = Partial; + +type addSectionCallback = (this: { + add(id: string, value: SettingValue, options: ISettingAddOptions): void; + with(options: ISettingAddOptions, cb: addSectionCallback): void; +}) => void; + +type addGroupCallback = (this: { + add(id: string, value: SettingValue, options: ISettingAddOptions): void; + section(section: string, cb: addSectionCallback): void; + with(options: ISettingAddOptions, cb: addGroupCallback): void; +}) => void; + + +type ISettingAddOptions = Partial; + + +const compareSettingsIgnoringKeys = (keys: Array) => + (a: ISetting, b: ISetting): boolean => + [...new Set([...Object.keys(a), ...Object.keys(b)])] + .filter((key) => !keys.includes(key as keyof ISetting)) + .every((key) => isEqual(a[key as keyof ISetting], b[key as keyof ISetting])); + +const compareSettings = compareSettingsIgnoringKeys(['value', 'ts', 'createdAt', 'valueSource', 'packageValue', 'processEnvValue', '_updatedAt']); +export class SettingsRegistry { + private model: typeof SettingsModel; + + private store: ICachedSettings; + + private _sorter: {[key: string]: number} = {}; + + constructor({ store, model }: { store: ICachedSettings; model: typeof SettingsModel }) { + this.store = store; + this.model = model; + } + + /* + * Add a setting + */ + add(_id: string, value: SettingValue, { sorter, section, group, ...options }: ISettingAddOptions = {}): void { + if (!_id || value == null) { + throw new Error('Invalid arguments'); + } + + const sorterKey = group && section ? `${ group }_${ section }` : group; + + if (sorterKey) { + this._sorter[sorterKey] = this._sorter[sorterKey] ?? -1; + this._sorter[sorterKey]++; + } + + const settingFromCode = getSettingDefaults({ _id, type: 'string', section, value, sorter: sorter ?? (sorterKey?.length && this._sorter[sorterKey]), group, ...options }, blockedSettings, hiddenSettings, wizardRequiredSettings); + + if (isSettingEnterprise(settingFromCode) && !('invalidValue' in settingFromCode)) { + SystemLogger.error(`Enterprise setting ${ _id } is missing the invalidValue option`); + throw new Error(`Enterprise setting ${ _id } is missing the invalidValue option`); + } + + const settingStored = this.store.getSetting(_id); + const settingOverwritten = overwriteSetting(settingFromCode); + try { + validateSetting(settingFromCode._id, settingFromCode.type, settingFromCode.value); + } catch (e) { + IS_DEVELOPMENT && SystemLogger.error(`Invalid setting code ${ _id }: ${ e.message }`); + } + + const isOverwritten = settingFromCode !== settingOverwritten; + + const { _id: _, ...settingProps } = settingOverwritten; + + if (settingStored && !compareSettings(settingStored, settingOverwritten)) { + const { value: _value, ...settingOverwrittenProps } = settingOverwritten; + this.model.upsert({ _id }, { $set: { ...settingOverwrittenProps } }); + return; + } + + if (settingStored && isOverwritten) { + if (settingStored.value !== settingOverwritten.value) { + this.model.upsert({ _id }, settingProps); + } + return; + } + + if (settingStored) { + try { + validateSetting(settingFromCode._id, settingFromCode.type, settingStored?.value); + } catch (e) { + IS_DEVELOPMENT && SystemLogger.error(`Invalid setting stored ${ _id }: ${ e.message }`); + } + return; + } + + const settingOverwrittenDefault = overrideSetting(settingFromCode); + + const setting = isOverwritten ? settingOverwritten : settingOverwrittenDefault; + + this.model.insert(setting); // no need to emit unless we remove the oplog + + this.store.set(setting); + } + + /* + * Add a setting group + */ + addGroup(_id: string, cb: addGroupCallback): void; + + // eslint-disable-next-line no-dupe-class-members + addGroup(_id: string, groupOptions: ISettingAddGroupOptions | addGroupCallback = {}, cb?: addGroupCallback): void { + if (!_id || (groupOptions instanceof Function && cb)) { + throw new Error('Invalid arguments'); + } + + this._sorter[_id] = this._sorter[_id] || -1; + this._sorter[_id]++; + + const callback = groupOptions instanceof Function ? groupOptions : cb; + + const options = groupOptions instanceof Function ? getGroupDefaults(_id, { sorter: this._sorter[_id] }) : getGroupDefaults(_id, { sorter: this._sorter[_id], ...groupOptions }); + + if (!this.store.has(_id)) { + options.ts = new Date(); + this.model.insert(options); + this.store.set(options as ISetting); + } + + if (!callback) { + return; + } + + const addWith = (preset: ISettingAddOptions) => (id: string, value: SettingValue, options: ISettingAddOptions = {}): void => { + const mergedOptions = { ...preset, ...options }; + this.add(id, value, mergedOptions); + }; + const sectionSetWith = (preset: ISettingAddOptions) => (options: ISettingAddOptions, cb: addSectionCallback): void => { + const mergedOptions = { ...preset, ...options }; + cb.call({ + add: addWith(mergedOptions), + with: sectionSetWith(mergedOptions), + }); + }; + const sectionWith = (preset: ISettingAddOptions) => (section: string, cb: addSectionCallback): void => { + const mergedOptions = { ...preset, section }; + cb.call({ + add: addWith(mergedOptions), + with: sectionSetWith(mergedOptions), + }); + }; + + const groupSetWith = (preset: ISettingAddOptions) => (options: ISettingAddOptions, cb: addGroupCallback): void => { + const mergedOptions = { ...preset, ...options }; + + cb.call({ + add: addWith(mergedOptions), + section: sectionWith(mergedOptions), + with: groupSetWith(mergedOptions), + }); + }; + + return groupSetWith({ group: _id })({}, callback); + } +} diff --git a/app/settings/server/functions/convertValue.ts b/app/settings/server/functions/convertValue.ts new file mode 100644 index 000000000000..0b8782bb0213 --- /dev/null +++ b/app/settings/server/functions/convertValue.ts @@ -0,0 +1,14 @@ +import { ISetting, SettingValue } from '../../../../definition/ISetting'; + +export const convertValue = (value: 'true' | 'false' | string, type: ISetting['type']): SettingValue => { + if (value.toLowerCase() === 'true') { + return true; + } + if (value.toLowerCase() === 'false') { + return false; + } + if (type === 'int') { + return parseInt(value); + } + return value; +}; diff --git a/app/settings/server/functions/getSettingDefaults.tests.ts b/app/settings/server/functions/getSettingDefaults.tests.ts new file mode 100644 index 000000000000..1ed12b81a672 --- /dev/null +++ b/app/settings/server/functions/getSettingDefaults.tests.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-env mocha */ +import { expect } from 'chai'; + +import { getSettingDefaults } from './getSettingDefaults'; + +describe('getSettingDefaults', () => { + it('should return based on _id type value', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string' }); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('i18nDescription').to.be.equal('test_Description'); + + expect(setting).to.have.property('value').to.be.equal('test'); + expect(setting).to.have.property('packageValue').to.be.equal('test'); + + expect(setting).to.have.property('type').to.be.equal('string'); + + expect(setting).to.have.property('blocked').to.be.equal(false); + + + expect(setting).to.not.have.property('multiline'); + expect(setting).to.not.have.property('values'); + + + expect(setting).to.not.have.property('group'); + expect(setting).to.not.have.property('section'); + expect(setting).to.not.have.property('tab'); + }); + + it('should return a sorter value', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string', sorter: 1 }); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('sorter').to.be.equal(1); + }); + + it('should return a private setting', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string', public: false }); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('public').to.be.equal(false); + }); + + it('should return a public setting', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string', public: true }); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('public').to.be.equal(true); + }); + + it('should return a blocked setting', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string', blocked: true }); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + + expect(setting).to.have.property('blocked').to.be.equal(true); + }); + + it('should return a blocked setting set by env', () => { + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string' }, new Set(['test'])); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('blocked').to.be.equal(true); + }); + + + it('should return a package value', () => { + const setting = getSettingDefaults({ _id: 'test', value: true, type: 'string' }, new Set(['test'])); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.have.property('blocked').to.be.equal(true); + }); +}); diff --git a/app/settings/server/functions/getSettingDefaults.ts b/app/settings/server/functions/getSettingDefaults.ts new file mode 100644 index 000000000000..3530840fd56e --- /dev/null +++ b/app/settings/server/functions/getSettingDefaults.ts @@ -0,0 +1,31 @@ +import { ISetting, ISettingColor, isSettingColor } from '../../../../definition/ISetting'; + +export const getSettingDefaults = (setting: Partial & Pick, blockedSettings: Set = new Set(), hiddenSettings: Set = new Set(), wizardRequiredSettings: Set = new Set()): ISetting => { + const { _id, value, sorter, ...options } = setting; + return { + _id, + value, + packageValue: value, + valueSource: 'packageValue', + secret: false, + enterprise: false, + i18nDescription: `${ _id }_Description`, + autocomplete: true, + sorter: sorter || 0, + ts: new Date(), + createdAt: new Date(), + ...options, + ...options.enableQuery && { enableQuery: JSON.stringify(options.enableQuery) }, + i18nLabel: options.i18nLabel || _id, + hidden: options.hidden || hiddenSettings.has(_id), + blocked: options.blocked || blockedSettings.has(_id), + requiredOnWizard: options.requiredOnWizard || wizardRequiredSettings.has(_id), + type: options.type || 'string', + env: options.env || false, + public: options.public || false, + ...options.displayQuery && { displayQuery: JSON.stringify(options.displayQuery) }, + ...isSettingColor(setting as ISetting) && { + packageEditor: (setting as ISettingColor).editor, + }, + }; +}; diff --git a/app/settings/server/functions/overrideGenerator.tests.ts b/app/settings/server/functions/overrideGenerator.tests.ts new file mode 100644 index 000000000000..a1881527ed84 --- /dev/null +++ b/app/settings/server/functions/overrideGenerator.tests.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-env mocha */ +import { expect } from 'chai'; + +import { getSettingDefaults } from './getSettingDefaults'; +import { overrideGenerator } from './overrideGenerator'; + +describe('overrideGenerator', () => { + it('should return a new object with the new value', () => { + const overwrite = overrideGenerator(() => 'value'); + + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string' }); + const overwritten = overwrite(setting); + + + expect(overwritten).to.be.an('object'); + expect(overwritten).to.have.property('_id'); + + expect(setting).to.be.not.equal(overwritten); + + expect(overwritten).to.have.property('value').that.equals('value'); + expect(overwritten).to.have.property('valueSource').that.equals('processEnvValue'); + }); + + + it('should return the same object since the value didnt change', () => { + const overwrite = overrideGenerator(() => 'test'); + + const setting = getSettingDefaults({ _id: 'test', value: 'test', type: 'string' }); + const overwritten = overwrite(setting); + + expect(setting).to.be.equal(overwritten); + }); +}); diff --git a/app/settings/server/functions/overrideGenerator.ts b/app/settings/server/functions/overrideGenerator.ts new file mode 100644 index 000000000000..c4875286ec80 --- /dev/null +++ b/app/settings/server/functions/overrideGenerator.ts @@ -0,0 +1,22 @@ +import { ISetting } from '../../../../definition/ISetting'; +import { convertValue } from './convertValue'; + +export const overrideGenerator = (fn: (key: string) => string | undefined) => (setting: ISetting): ISetting => { + const overwriteValue = fn(setting._id); + if (overwriteValue === null || overwriteValue === undefined) { + return setting; + } + + const value = convertValue(overwriteValue, setting.type); + + if (value === setting.value) { + return setting; + } + + return { + ...setting, + value, + processEnvValue: value, + valueSource: 'processEnvValue', + }; +}; diff --git a/app/settings/server/functions/overrideSetting.ts b/app/settings/server/functions/overrideSetting.ts new file mode 100644 index 000000000000..d51b74e4e60b --- /dev/null +++ b/app/settings/server/functions/overrideSetting.ts @@ -0,0 +1,3 @@ +import { overrideGenerator } from './overrideGenerator'; + +export const overrideSetting = overrideGenerator((key: string) => process.env[key]); diff --git a/app/settings/server/functions/overwriteSetting.ts b/app/settings/server/functions/overwriteSetting.ts new file mode 100644 index 000000000000..990b55edd9d1 --- /dev/null +++ b/app/settings/server/functions/overwriteSetting.ts @@ -0,0 +1,3 @@ +import { overrideGenerator } from './overrideGenerator'; + +export const overwriteSetting = overrideGenerator((key: string) => process.env[`OVERWRITE_SETTING_${ key }`]); diff --git a/app/settings/server/functions/settings.mocks.ts b/app/settings/server/functions/settings.mocks.ts index 8db74e3285d1..3e22e52bbe47 100644 --- a/app/settings/server/functions/settings.mocks.ts +++ b/app/settings/server/functions/settings.mocks.ts @@ -1,15 +1,26 @@ -import { Meteor } from 'meteor/meteor'; import mock from 'mock-require'; +import { ISetting } from '../../../../definition/ISetting'; +import { ICachedSettings } from '../CachedSettings'; + type Dictionary = { [index: string]: any; } class SettingsClass { + settings: ICachedSettings; + + find(): any[] { + return []; + } + + public data = new Map() public upsertCalls = 0; + public insertCalls = 0; + private checkQueryMatch(key: string, data: Dictionary, queryValue: any): boolean { if (typeof queryValue === 'object') { if (queryValue.$exists !== undefined) { @@ -24,10 +35,17 @@ class SettingsClass { return [...this.data.values()].find((data) => Object.entries(query).every(([key, value]) => this.checkQueryMatch(key, data, value))); } + insert(doc: any): void { + this.data.set(doc._id, doc); + // eslint-disable-next-line @typescript-eslint/no-var-requires + this.settings.set(doc); + this.insertCalls++; + } + upsert(query: any, update: any): void { const existent = this.findOne(query); - const data = { ...existent, ...query, ...update.$set }; + const data = { ...existent, ...query, ...update, ...update.$set }; if (!existent) { Object.assign(data, update.$setOnInsert); @@ -35,12 +53,10 @@ class SettingsClass { // console.log(query, data); this.data.set(query._id, data); - Meteor.settings[query._id] = data.value; // Can't import before the mock command on end of this file! // eslint-disable-next-line @typescript-eslint/no-var-requires - const { settings } = require('./settings'); - settings.load(query._id, data.value, !existent); + this.settings.set(data); this.upsertCalls++; } @@ -50,9 +66,7 @@ class SettingsClass { // Can't import before the mock command on end of this file! // eslint-disable-next-line @typescript-eslint/no-var-requires - const { settings } = require('./settings'); - Meteor.settings[id] = value; - settings.load(id, value, false); + this.settings.set(this.data.get(id) as ISetting); } } diff --git a/app/settings/server/functions/settings.tests.ts b/app/settings/server/functions/settings.tests.ts index 65eb7ae8324e..45743a6bf7d5 100644 --- a/app/settings/server/functions/settings.tests.ts +++ b/app/settings/server/functions/settings.tests.ts @@ -1,24 +1,27 @@ /* eslint-disable @typescript-eslint/camelcase */ /* eslint-env mocha */ -import { Meteor } from 'meteor/meteor'; import chai, { expect } from 'chai'; import spies from 'chai-spies'; import { Settings } from './settings.mocks'; -import { settings } from './settings'; +import { SettingsRegistry } from '../SettingsRegistry'; +import { CachedSettings } from '../CachedSettings'; chai.use(spies); describe('Settings', () => { beforeEach(() => { + Settings.insertCalls = 0; Settings.upsertCalls = 0; - Settings.data.clear(); - Meteor.settings = { public: {} }; process.env = {}; }); it('should not insert the same setting twice', () => { - settings.addGroup('group', function() { + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', true, { type: 'boolean', @@ -27,8 +30,9 @@ describe('Settings', () => { }); }); - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.findOne({ _id: 'my_setting' })).to.be.include({ type: 'boolean', sorter: 0, @@ -45,7 +49,7 @@ describe('Settings', () => { autocomplete: true, }); - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', true, { type: 'boolean', @@ -54,11 +58,12 @@ describe('Settings', () => { }); }); - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.findOne({ _id: 'my_setting' }).value).to.be.equal(true); - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting2', false, { type: 'boolean', @@ -67,16 +72,22 @@ describe('Settings', () => { }); }); - expect(Settings.data.size).to.be.equal(3); - expect(Settings.upsertCalls).to.be.equal(3); + expect(Settings.upsertCalls).to.be.equal(0); + expect(Settings.insertCalls).to.be.equal(3); + expect(Settings.findOne({ _id: 'my_setting' }).value).to.be.equal(true); expect(Settings.findOne({ _id: 'my_setting2' }).value).to.be.equal(false); }); it('should respect override via environment as int', () => { + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); + process.env.OVERWRITE_SETTING_my_setting = '1'; - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', 0, { type: 'int', @@ -102,13 +113,13 @@ describe('Settings', () => { autocomplete: true, }; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings).to.have.property('insertCalls').to.be.equal(2); + expect(Settings).to.have.property('upsertCalls').to.be.equal(0); expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); process.env.OVERWRITE_SETTING_my_setting = '2'; - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', 0, { type: 'int', @@ -117,18 +128,19 @@ describe('Settings', () => { }); }); - expectedSetting.value = 2; - expectedSetting.processEnvValue = 2; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(3); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); + expect(Settings).to.have.property('insertCalls').to.be.equal(2); + expect(Settings).to.have.property('upsertCalls').to.be.equal(1); + expect(Settings.findOne({ _id: 'my_setting' })).to.include({ ...expectedSetting, value: 2, processEnvValue: 2 }); }); it('should respect override via environment as boolean', () => { process.env.OVERWRITE_SETTING_my_setting_bool = 'true'; - settings.addGroup('group', function() { + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting_bool', false, { type: 'boolean', @@ -154,33 +166,39 @@ describe('Settings', () => { autocomplete: true, }; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); expect(Settings.findOne({ _id: 'my_setting_bool' })).to.include(expectedSetting); process.env.OVERWRITE_SETTING_my_setting_bool = 'false'; - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { - this.add('my_setting_bool', false, { + this.add('my_setting_bool', true, { type: 'boolean', sorter: 0, }); }); }); - expectedSetting.value = false; - expectedSetting.processEnvValue = false; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(3); - expect(Settings.findOne({ _id: 'my_setting_bool' })).to.include(expectedSetting); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(1); + expect(Settings.findOne({ _id: 'my_setting_bool' })).to.include({ + value: false, + processEnvValue: false, + packageValue: true, + }); }); it('should respect override via environment as string', () => { process.env.OVERWRITE_SETTING_my_setting_str = 'hey'; - settings.addGroup('group', function() { + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting_str', '', { type: 'string', @@ -206,13 +224,13 @@ describe('Settings', () => { autocomplete: true, }; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); expect(Settings.findOne({ _id: 'my_setting_str' })).to.include(expectedSetting); process.env.OVERWRITE_SETTING_my_setting_str = 'hey ho'; - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting_str', 'hey', { type: 'string', @@ -221,19 +239,23 @@ describe('Settings', () => { }); }); - expectedSetting.value = 'hey ho'; - expectedSetting.processEnvValue = 'hey ho'; - expectedSetting.packageValue = 'hey'; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(3); - expect(Settings.findOne({ _id: 'my_setting_str' })).to.include(expectedSetting); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(1); + expect(Settings.findOne({ _id: 'my_setting_str' })).to.include({ ...expectedSetting, + value: 'hey ho', + processEnvValue: 'hey ho', + packageValue: 'hey', + }); }); it('should respect initial value via environment', () => { process.env.my_setting = '1'; + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', 0, { type: 'int', @@ -259,13 +281,11 @@ describe('Settings', () => { autocomplete: true, }; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - process.env.my_setting = '2'; - - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('my_setting', 0, { type: 'int', @@ -274,160 +294,22 @@ describe('Settings', () => { }); }); - expectedSetting.processEnvValue = 2; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(3); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); + expect(Settings.insertCalls).to.be.equal(2); + expect(Settings.upsertCalls).to.be.equal(0); + expect(Settings.findOne({ _id: 'my_setting' })).to.include({ ...expectedSetting }); }); - it('should respect initial value via Meteor.settings', () => { - Meteor.settings.my_setting = 1; - - settings.addGroup('group', function() { - this.section('section', function() { - this.add('my_setting', 0, { - type: 'int', - sorter: 0, - }); - }); - }); - - const expectedSetting = { - value: 1, - meteorSettingsValue: 1, - valueSource: 'meteorSettingsValue', - type: 'int', - sorter: 0, - group: 'group', - section: 'section', - packageValue: 0, - hidden: false, - blocked: false, - secret: false, - i18nLabel: 'my_setting', - i18nDescription: 'my_setting_Description', - autocomplete: true, - }; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - - Meteor.settings.my_setting = 2; - - settings.addGroup('group', function() { - this.section('section', function() { - this.add('my_setting', 0, { - type: 'int', - sorter: 0, - }); - }); - }); - - expectedSetting.meteorSettingsValue = 2; - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(3); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - }); + it('should call `settings.get` callback on setting added', (done) => { + const settings = new CachedSettings(); + Settings.settings = settings; + settings.initilized(); + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); - it('should keep original value if value on code was changed', () => { - settings.addGroup('group', function() { - this.section('section', function() { - this.add('my_setting', 0, { - type: 'int', - sorter: 0, - }); - }); - }); - - const expectedSetting = { - value: 0, - valueSource: 'packageValue', - type: 'int', - sorter: 0, - group: 'group', - section: 'section', - packageValue: 0, - hidden: false, - blocked: false, - secret: false, - i18nLabel: 'my_setting', - i18nDescription: 'my_setting_Description', - autocomplete: true, - }; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - - // Can't reset setting because the Meteor.setting will have the first value and will act to enforce his value - // settings.addGroup('group', function() { - // this.section('section', function() { - // this.add('my_setting', 1, { - // type: 'int', - // sorter: 0, - // }); - // }); - // }); - - // expectedSetting.packageValue = 1; - - // expect(Settings.data.size).to.be.equal(2); - // expect(Settings.upsertCalls).to.be.equal(3); - // expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - }); - - it('should change group and section', () => { - settings.addGroup('group', function() { - this.section('section', function() { - this.add('my_setting', 0, { - type: 'int', - sorter: 0, - }); - }); - }); - - const expectedSetting = { - value: 0, - valueSource: 'packageValue', - type: 'int', - sorter: 0, - group: 'group', - section: 'section', - packageValue: 0, - hidden: false, - blocked: false, - secret: false, - i18nLabel: 'my_setting', - i18nDescription: 'my_setting_Description', - autocomplete: true, - }; - - expect(Settings.data.size).to.be.equal(2); - expect(Settings.upsertCalls).to.be.equal(2); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - - settings.addGroup('group2', function() { - this.section('section2', function() { - this.add('my_setting', 0, { - type: 'int', - sorter: 0, - }); - }); - }); - - expectedSetting.group = 'group2'; - expectedSetting.section = 'section2'; - - expect(Settings.data.size).to.be.equal(3); - expect(Settings.upsertCalls).to.be.equal(4); - expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting); - }); + const spy = chai.spy(); + const spy2 = chai.spy(); - it('should call `settings.get` callback on setting added', () => { - settings.addGroup('group', function() { + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('setting_callback', 'value1', { type: 'string', @@ -435,20 +317,30 @@ describe('Settings', () => { }); }); - const spy = chai.spy(); - settings.get('setting_callback', spy); - settings.get(/setting_callback/, spy); + settings.watch('setting_callback', spy, { debounce: 10 }); + settings.watchByRegex(/setting_callback/, spy2, { debounce: 10 }); - expect(spy).to.have.been.called.exactly(2); - expect(spy).to.have.been.called.always.with('setting_callback', 'value1'); + setTimeout(() => { + expect(spy).to.have.been.called.exactly(1); + expect(spy2).to.have.been.called.exactly(1); + expect(spy).to.have.been.called.always.with('value1'); + expect(spy2).to.have.been.called.always.with('setting_callback', 'value1'); + done(); + }, settings.getConfig({ debounce: 10 }).debounce); }); - it('should call `settings.get` callback on setting changed', () => { + it('should call `settings.watch` callback on setting changed registering before initialized', (done) => { const spy = chai.spy(); - settings.get('setting_callback', spy); - settings.get(/setting_callback/, spy); + const spy2 = chai.spy(); + const settings = new CachedSettings(); + Settings.settings = settings; + const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); + + settings.watch('setting_callback', spy, { debounce: 10 }); + settings.watchByRegex(/setting_callback/ig, spy2, { debounce: 10 }); - settings.addGroup('group', function() { + settings.initilized(); + settingsRegistry.addGroup('group', function() { this.section('section', function() { this.add('setting_callback', 'value2', { type: 'string', @@ -456,10 +348,18 @@ describe('Settings', () => { }); }); - settings.updateById('setting_callback', 'value3'); - - expect(spy).to.have.been.called.exactly(6); - expect(spy).to.have.been.called.with('setting_callback', 'value2'); - expect(spy).to.have.been.called.with('setting_callback', 'value3'); + setTimeout(() => { + settings.on('*', () => setTimeout(() => { + done(); + }, settings.getConfig({ debounce: 10 }).debounce)); + + Settings.updateValueById('setting_callback', 'value3'); + setTimeout(() => { + expect(spy).to.have.been.called.exactly(2); + expect(spy2).to.have.been.called.exactly(2); + expect(spy).to.have.been.called.with('value2'); + expect(spy).to.have.been.called.with('value3'); + }, settings.getConfig({ debounce: 10 }).debounce); + }, settings.getConfig({ debounce: 10 }).debounce); }); }); diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index ffd25230cc55..32dd0a0da864 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -1,441 +1,14 @@ -import { EventEmitter } from 'events'; - -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; - -import { SettingsBase } from '../../lib/settings'; import SettingsModel from '../../../models/server/models/Settings'; -import { updateValue } from '../raw'; -import { ISetting, SettingValue } from '../../../../definition/ISetting'; -import { SystemLogger } from '../../../../server/lib/logger/system'; - -const blockedSettings = new Set(); -const hiddenSettings = new Set(); -const wizardRequiredSettings = new Set(); - -if (process.env.SETTINGS_BLOCKED) { - process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => blockedSettings.add(settingId.trim())); -} - -if (process.env.SETTINGS_HIDDEN) { - process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => hiddenSettings.add(settingId.trim())); -} - -if (process.env.SETTINGS_REQUIRED_ON_WIZARD) { - process.env.SETTINGS_REQUIRED_ON_WIZARD.split(',').forEach((settingId) => wizardRequiredSettings.add(settingId.trim())); -} - -export const SettingsEvents = new EventEmitter(); - -const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddOptions): SettingValue => { - const envValue = process.env[_id]; - if (envValue) { - if (envValue.toLowerCase() === 'true') { - value = true; - } else if (envValue.toLowerCase() === 'false') { - value = false; - } else if (options.type === 'int') { - value = parseInt(envValue); - } else { - value = envValue; - } - options.processEnvValue = value; - options.valueSource = 'processEnvValue'; - } else if (Meteor.settings[_id] != null && Meteor.settings[_id] !== value) { - value = Meteor.settings[_id]; - options.meteorSettingsValue = value; - options.valueSource = 'meteorSettingsValue'; - } - - const overwriteValue = process.env[`OVERWRITE_SETTING_${ _id }`]; - if (overwriteValue) { - if (overwriteValue.toLowerCase() === 'true') { - value = true; - } else if (overwriteValue.toLowerCase() === 'false') { - value = false; - } else if (options.type === 'int') { - value = parseInt(overwriteValue); - } else { - value = overwriteValue; - } - options.value = value; - options.processEnvValue = value; - options.valueSource = 'processEnvValue'; - } - - return value; -}; - -export interface ISettingAddOptions extends Partial { - force?: boolean; - actionText?: string; - code?: 'application/json'; -} - -export interface ISettingAddGroupOptions { - hidden?: boolean; - blocked?: boolean; - ts?: Date; - i18nLabel?: string; - i18nDescription?: string; -} - - -interface IUpdateOperator { - $set: ISettingAddOptions; - $setOnInsert: ISettingAddOptions & { - createdAt: Date; - }; - $unset?: { - section?: 1; - tab?: 1; - }; -} - -type QueryExpression = { - $exists: boolean; -} - -type Query = { - [P in keyof T]?: T[P] | QueryExpression; -} - -type addSectionCallback = (this: { - add(id: string, value: SettingValue, options: ISettingAddOptions): void; - set(options: ISettingAddOptions, cb: addSectionCallback): void; -}) => void; - -type addGroupCallback = (this: { - add(id: string, value: SettingValue, options: ISettingAddOptions): void; - section(section: string, cb: addSectionCallback): void; - set(options: ISettingAddOptions, cb: addGroupCallback): void; -}) => void; - -class Settings extends SettingsBase { - private afterInitialLoad: Array<(settings: Meteor.Settings) => void> = []; - - private _sorter: {[key: string]: number} = {}; - - private initialLoad = false; - - private validateOptions(_id: string, value: SettingValue, options: ISettingAddOptions): void { - const sorterKey = options.group && options.section ? `${ options.group }_${ options.section }` : options.group; - if (sorterKey && this._sorter[sorterKey] == null) { - if (options.group && options.section) { - const currentGroupValue = this._sorter[options.group] || 0; - this._sorter[sorterKey] = currentGroupValue * 1000; - } else { - this._sorter[sorterKey] = 0; - } - } - options.packageValue = value; - options.valueSource = 'packageValue'; - options.hidden = options.hidden || false; - options.requiredOnWizard = options.requiredOnWizard || false; - options.secret = options.secret || false; - options.enterprise = options.enterprise || false; - - if (options.enterprise && !('invalidValue' in options)) { - SystemLogger.error(`Enterprise setting ${ _id } is missing the invalidValue option`); - throw new Error(`Enterprise setting ${ _id } is missing the invalidValue option`); - } - - if (sorterKey && options.sorter == null) { - options.sorter = this._sorter[sorterKey]++; - } - if (options.enableQuery != null) { - options.enableQuery = JSON.stringify(options.enableQuery); - } - if (options.displayQuery != null) { - options.displayQuery = JSON.stringify(options.displayQuery); - } - if (options.i18nDescription == null) { - options.i18nDescription = `${ _id }_Description`; - } - if (blockedSettings.has(_id)) { - options.blocked = true; - } - if (hiddenSettings.has(_id)) { - options.hidden = true; - } - if (wizardRequiredSettings.has(_id)) { - options.requiredOnWizard = true; - } - if (options.autocomplete == null) { - options.autocomplete = true; - } - } - - /* - * Add a setting - */ - add(_id: string, value: SettingValue, { editor, ...options }: ISettingAddOptions = {}): boolean { - if (!_id || value == null) { - return false; - } - - this.validateOptions(_id, value, options); - options.blocked = options.blocked || false; - if (options.i18nLabel == null) { - options.i18nLabel = _id; - } - - value = overrideSetting(_id, value, options); - - const updateOperations: IUpdateOperator = { - $set: options, - $setOnInsert: { - createdAt: new Date(), - }, - }; - if (editor != null) { - updateOperations.$setOnInsert.editor = editor; - updateOperations.$setOnInsert.packageEditor = editor; - } - - if (options.value == null) { - if (options.force === true) { - updateOperations.$set.value = options.packageValue; - } else { - updateOperations.$setOnInsert.value = value; - } - } - - const query: Query = { - _id, - ...updateOperations.$set, - }; - - if (options.section == null) { - updateOperations.$unset = { - section: 1, - }; - query.section = { - $exists: false, - }; - } - - if (!options.tab) { - updateOperations.$unset = { - tab: 1, - }; - query.tab = { - $exists: false, - }; - } - - const existentSetting = SettingsModel.findOne(query); - if (existentSetting) { - if (existentSetting.editor || !updateOperations.$setOnInsert.editor) { - return true; - } - - updateOperations.$set.editor = updateOperations.$setOnInsert.editor; - delete updateOperations.$setOnInsert.editor; - } - - updateOperations.$set.ts = new Date(); - - SettingsModel.upsert({ - _id, - }, updateOperations); - - const record = { - _id, - value, - type: options.type || 'string', - env: options.env || false, - i18nLabel: options.i18nLabel, - public: options.public || false, - packageValue: options.packageValue, - blocked: options.blocked, - }; - - this.storeSettingValue(record, this.initialLoad); - - return true; - } - - /* - * Add a setting group - */ - addGroup(_id: string, cb?: addGroupCallback): boolean; - - // eslint-disable-next-line no-dupe-class-members - addGroup(_id: string, options: ISettingAddGroupOptions | addGroupCallback = {}, cb?: addGroupCallback): boolean { - if (!_id) { - return false; - } - if (_.isFunction(options)) { - cb = options; - options = {}; - } - if (options.i18nLabel == null) { - options.i18nLabel = _id; - } - if (options.i18nDescription == null) { - options.i18nDescription = `${ _id }_Description`; - } - - options.blocked = false; - options.hidden = false; - if (blockedSettings.has(_id)) { - options.blocked = true; - } - if (hiddenSettings.has(_id)) { - options.hidden = true; - } - - const existentGroup = SettingsModel.findOne({ - _id, - type: 'group', - ...options, - }); - - if (!existentGroup) { - options.ts = new Date(); - - SettingsModel.upsert({ - _id, - }, { - $set: options, - $setOnInsert: { - type: 'group', - createdAt: new Date(), - }, - }); - } - - if (cb != null) { - const addWith = (preset: ISettingAddOptions) => (id: string, value: SettingValue, options: ISettingAddOptions = {}): void => { - const mergedOptions = Object.assign({}, preset, options); - this.add(id, value, mergedOptions); - }; - const sectionSetWith = (preset: ISettingAddOptions) => (options: ISettingAddOptions, cb: addSectionCallback): void => { - const mergedOptions = Object.assign({}, preset, options); - cb.call({ - add: addWith(mergedOptions), - set: sectionSetWith(mergedOptions), - }); - }; - const sectionWith = (preset: ISettingAddOptions) => (section: string, cb: addSectionCallback): void => { - const mergedOptions = Object.assign({}, preset, { section }); - cb.call({ - add: addWith(mergedOptions), - set: sectionSetWith(mergedOptions), - }); - }; - - const groupSetWith = (preset: ISettingAddOptions) => (options: ISettingAddOptions, cb: addGroupCallback): void => { - const mergedOptions = Object.assign({}, preset, options); - - cb.call({ - add: addWith(mergedOptions), - section: sectionWith(mergedOptions), - set: groupSetWith(mergedOptions), - }); - }; - - groupSetWith({ group: _id })({}, cb); - } - return true; - } - - /* - * Remove a setting by id - */ - removeById(_id: string): boolean { - if (!_id) { - return false; - } - return SettingsModel.removeById(_id); - } - - /* - * Update a setting by id - */ - updateById(_id: string, value: SettingValue, editor?: string): boolean { - if (!_id || value == null) { - return false; - } - if (editor != null) { - return SettingsModel.updateValueAndEditorById(_id, value, editor); - } - return SettingsModel.updateValueById(_id, value); - } - - /* - * Update options of a setting by id - */ - updateOptionsById(_id: string, options: ISettingAddOptions): boolean { - if (!_id || options == null) { - return false; - } - - return SettingsModel.updateOptionsById(_id, options); - } - - /* - * Update a setting by id - */ - clearById(_id: string): boolean { - if (_id == null) { - return false; - } - return SettingsModel.updateValueById(_id, undefined); - } - - /* - * Change a setting value on the Meteor.settings object - */ - storeSettingValue(record: ISetting, initialLoad: boolean): void { - const newData = { - value: record.value, - }; - SettingsEvents.emit('store-setting-value', record, newData); - const { value } = newData; - - Meteor.settings[record._id] = value; - if (record.env === true) { - process.env[record._id] = String(value); - } - - this.load(record._id, value, initialLoad); - } - - /* - * Remove a setting value on the Meteor.settings object - */ - removeSettingValue(record: ISetting, initialLoad: boolean): void { - SettingsEvents.emit('remove-setting-value', record); - - delete Meteor.settings[record._id]; - if (record.env === true) { - delete process.env[record._id]; - } +import { CachedSettings } from '../CachedSettings'; +import { SettingsRegistry } from '../SettingsRegistry'; +import { ISetting } from '../../../../definition/ISetting'; - this.load(record._id, undefined, initialLoad); - } - /* - * Update a setting by id - */ - init(): void { - this.initialLoad = true; - SettingsModel.find().fetch().forEach((record: ISetting) => { - this.storeSettingValue(record, this.initialLoad); - updateValue(record._id, { value: record.value }); - }); - this.initialLoad = false; - this.afterInitialLoad.forEach((fn) => fn(Meteor.settings)); - } +export const settings = new CachedSettings(); +SettingsModel.find().forEach((record: ISetting) => { + settings.set(record); +}); - onAfterInitialLoad(fn: (settings: Meteor.Settings) => void): void { - this.afterInitialLoad.push(fn); - if (this.initialLoad === false) { - fn(Meteor.settings); - } - } -} +settings.initilized(); -export const settings = new Settings(); +export const settingsRegistry = new SettingsRegistry({ store: settings, model: SettingsModel }); diff --git a/app/settings/server/functions/validateSetting.ts b/app/settings/server/functions/validateSetting.ts new file mode 100644 index 000000000000..10667d00ece1 --- /dev/null +++ b/app/settings/server/functions/validateSetting.ts @@ -0,0 +1,57 @@ + + +import { ISetting } from '../../../../definition/ISetting'; + +export const validateSetting = (_id: T['_id'], type: T['type'], value: T['value'] | unknown): boolean => { + switch (type) { + case 'asset': + if (typeof value !== 'object') { + throw new Error(`Setting ${ _id } is of type ${ type } but got ${ typeof value }`); + } + break; + case 'string': + case 'relativeUrl': + case 'password': + case 'language': + case 'color': + case 'font': + case 'code': + case 'action': + case 'roomPick': + case 'group': + if (typeof value !== 'string') { + throw new Error(`Setting ${ _id } is of type ${ type } but got ${ typeof value }`); + } + break; + case 'boolean': + if (typeof value !== 'boolean') { + throw new Error(`Setting ${ _id } is of type boolean but got ${ typeof value }`); + } + break; + case 'int': + if (typeof value !== 'number') { + throw new Error(`Setting ${ _id } is of type int but got ${ typeof value }`); + } + break; + case 'multiSelect': + if (!Array.isArray(value)) { + throw new Error(`Setting ${ _id } is of type array but got ${ typeof value }`); + } + break; + case 'select': + + if (typeof value !== 'string' && typeof value !== 'number') { + throw new Error(`Setting ${ _id } is of type select but got ${ typeof value }`); + } + break; + case 'date': + if (!(value instanceof Date)) { + throw new Error(`Setting ${ _id } is of type date but got ${ typeof value }`); + } + break; + default: + return true; + } + + return true; +}; diff --git a/app/settings/server/functions/validateSettings.tests.ts b/app/settings/server/functions/validateSettings.tests.ts new file mode 100644 index 000000000000..8891afcf6947 --- /dev/null +++ b/app/settings/server/functions/validateSettings.tests.ts @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-env mocha */ +import { expect } from 'chai'; + +import { validateSetting } from './validateSetting'; + +describe('validateSettings', () => { + it('should validate the type string', () => { + expect(() => validateSetting('test', 'string', 'value')).to.not.throw(); + }); + it('should throw an error expecting string receiving int', () => { + expect(() => validateSetting('test', 'string', 10)).to.throw(); + }); + it('should validate the type int', () => { + expect(() => validateSetting('test', 'int', 10)).to.not.throw(); + }); + it('should throw an error expecting int receiving string', () => { + expect(() => validateSetting('test', 'int', '10')).to.throw(); + }); + it('should validate the type boolean', () => { + expect(() => validateSetting('test', 'boolean', true)).to.not.throw(); + }); + it('should throw an error expecting boolean receiving string', () => { + expect(() => validateSetting('test', 'boolean', 'true')).to.throw(); + }); + it('should validate the type date', () => { + expect(() => validateSetting('test', 'date', new Date())).to.not.throw(); + }); + it('should throw an error expecting date receiving string', () => { + expect(() => validateSetting('test', 'date', '2019-01-01')).to.throw(); + }); + it('should validate the type multiSelect', () => { + expect(() => validateSetting('test', 'multiSelect', [])).to.not.throw(); + }); + it('should throw an error expecting multiSelect receiving string', () => { + expect(() => validateSetting('test', 'multiSelect', '[]')).to.throw(); + }); +}); diff --git a/app/settings/server/index.ts b/app/settings/server/index.ts index 3adfad5409b3..f1a68d2790d0 100644 --- a/app/settings/server/index.ts +++ b/app/settings/server/index.ts @@ -1,6 +1,8 @@ -import { settings, SettingsEvents } from './functions/settings'; +import { settings, settingsRegistry } from './functions/settings'; +import { SettingsEvents } from './SettingsRegistry'; export { settings, + settingsRegistry, SettingsEvents, }; diff --git a/app/settings/server/startup.ts b/app/settings/server/startup.ts new file mode 100644 index 000000000000..2359e2d59163 --- /dev/null +++ b/app/settings/server/startup.ts @@ -0,0 +1,45 @@ +import { Meteor } from 'meteor/meteor'; + +import { use } from './Middleware'; +import { settings } from './functions/settings'; + + +const getProcessingTimeInMS = (time: [number, number]): number => time[0] * 1000 + time[1] / 1e6; + +settings.watch = use(settings.watch, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); + +if (process.env.DEBUG_SETTINGS === 'true') { + settings.watch = use(settings.watch, function watch(context, next) { + const [_id, callback, options] = context; + return next(_id, (...args) => { + const start = process.hrtime(); + callback(...args); + const elapsed = process.hrtime(start); + console.log(`settings.watch: ${ _id } ${ getProcessingTimeInMS(elapsed) }ms`); + }, options); + }); +} +settings.watchMultiple = use(settings.watchMultiple, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); +settings.watchOnce = use(settings.watchOnce, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); + +settings.change = use(settings.change, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); +settings.changeMultiple = use(settings.changeMultiple, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); +settings.changeOnce = use(settings.changeOnce, (context, next) => { + const [_id, callback] = context; + return next(_id, Meteor.bindEnvironment(callback)); +}); diff --git a/app/slackbridge/server/settings.js b/app/slackbridge/server/settings.js deleted file mode 100644 index c22042f72f63..000000000000 --- a/app/slackbridge/server/settings.js +++ /dev/null @@ -1,106 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.addGroup('SlackBridge', function() { - this.add('SlackBridge_Enabled', false, { - type: 'boolean', - i18nLabel: 'Enabled', - public: true, - }); - - this.add('SlackBridge_APIToken', '', { - type: 'string', - multiline: true, - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - i18nLabel: 'SlackBridge_APIToken', - i18nDescription: 'SlackBridge_APIToken_Description', - secret: true, - }); - - this.add('SlackBridge_FileUpload_Enabled', true, { - type: 'boolean', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - i18nLabel: 'FileUpload', - }); - - this.add('SlackBridge_Out_Enabled', false, { - type: 'boolean', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - }); - - this.add('SlackBridge_Out_All', false, { - type: 'boolean', - enableQuery: [{ - _id: 'SlackBridge_Enabled', - value: true, - }, { - _id: 'SlackBridge_Out_Enabled', - value: true, - }], - }); - - this.add('SlackBridge_Out_Channels', '', { - type: 'roomPick', - enableQuery: [{ - _id: 'SlackBridge_Enabled', - value: true, - }, { - _id: 'SlackBridge_Out_Enabled', - value: true, - }, { - _id: 'SlackBridge_Out_All', - value: false, - }], - }); - - this.add('SlackBridge_AliasFormat', '', { - type: 'string', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - i18nLabel: 'Alias_Format', - i18nDescription: 'Alias_Format_Description', - }); - - this.add('SlackBridge_ExcludeBotnames', '', { - type: 'string', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - i18nLabel: 'Exclude_Botnames', - i18nDescription: 'Exclude_Botnames_Description', - }); - - this.add('SlackBridge_Reactions_Enabled', true, { - type: 'boolean', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - i18nLabel: 'Reactions', - }); - - this.add('SlackBridge_Remove_Channel_Links', 'removeSlackBridgeChannelLinks', { - type: 'action', - actionText: 'Remove_Channel_Links', - i18nDescription: 'SlackBridge_Remove_Channel_Links_Description', - enableQuery: { - _id: 'SlackBridge_Enabled', - value: true, - }, - }); - }); -}); diff --git a/app/slackbridge/server/settings.ts b/app/slackbridge/server/settings.ts new file mode 100644 index 000000000000..2eb9f969a5ca --- /dev/null +++ b/app/slackbridge/server/settings.ts @@ -0,0 +1,102 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.addGroup('SlackBridge', function() { + this.add('SlackBridge_Enabled', false, { + type: 'boolean', + i18nLabel: 'Enabled', + public: true, + }); + + this.add('SlackBridge_APIToken', '', { + type: 'string', + multiline: true, + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + i18nLabel: 'SlackBridge_APIToken', + i18nDescription: 'SlackBridge_APIToken_Description', + secret: true, + }); + + this.add('SlackBridge_FileUpload_Enabled', true, { + type: 'boolean', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + i18nLabel: 'FileUpload', + }); + + this.add('SlackBridge_Out_Enabled', false, { + type: 'boolean', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + }); + + this.add('SlackBridge_Out_All', false, { + type: 'boolean', + enableQuery: [{ + _id: 'SlackBridge_Enabled', + value: true, + }, { + _id: 'SlackBridge_Out_Enabled', + value: true, + }], + }); + + this.add('SlackBridge_Out_Channels', '', { + type: 'roomPick', + enableQuery: [{ + _id: 'SlackBridge_Enabled', + value: true, + }, { + _id: 'SlackBridge_Out_Enabled', + value: true, + }, { + _id: 'SlackBridge_Out_All', + value: false, + }], + }); + + this.add('SlackBridge_AliasFormat', '', { + type: 'string', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + i18nLabel: 'Alias_Format', + i18nDescription: 'Alias_Format_Description', + }); + + this.add('SlackBridge_ExcludeBotnames', '', { + type: 'string', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + i18nLabel: 'Exclude_Botnames', + i18nDescription: 'Exclude_Botnames_Description', + }); + + this.add('SlackBridge_Reactions_Enabled', true, { + type: 'boolean', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + i18nLabel: 'Reactions', + }); + + this.add('SlackBridge_Remove_Channel_Links', 'removeSlackBridgeChannelLinks', { + type: 'action', + actionText: 'Remove_Channel_Links', + i18nDescription: 'SlackBridge_Remove_Channel_Links_Description', + enableQuery: { + _id: 'SlackBridge_Enabled', + value: true, + }, + }); +}); diff --git a/app/slackbridge/server/slackbridge.js b/app/slackbridge/server/slackbridge.js index addb0c1a2e9a..a40449ad700f 100644 --- a/app/slackbridge/server/slackbridge.js +++ b/app/slackbridge/server/slackbridge.js @@ -1,7 +1,7 @@ import SlackAdapter from './SlackAdapter.js'; import RocketAdapter from './RocketAdapter.js'; import { classLogger, connLogger } from './logger'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; /** * SlackBridge interfaces between this Rocket installation and a remote Slack installation. @@ -62,7 +62,7 @@ class SlackBridgeClass { processSettings() { // Slack installation API token - settings.get('SlackBridge_APIToken', (key, value) => { + settings.watch('SlackBridge_APIToken', (value) => { if (value !== this.apiTokens) { this.apiTokens = value; if (this.connected) { @@ -71,35 +71,35 @@ class SlackBridgeClass { } } - classLogger.debug(`Setting: ${ key }`, value); + classLogger.debug('Setting: SlackBridge_APIToken', value); }); // Import messages from Slack with an alias; %s is replaced by the username of the user. If empty, no alias will be used. - settings.get('SlackBridge_AliasFormat', (key, value) => { + settings.watch('SlackBridge_AliasFormat', (value) => { this.aliasFormat = value; - classLogger.debug(`Setting: ${ key }`, value); + classLogger.debug('Setting: SlackBridge_AliasFormat', value); }); // Do not propagate messages from bots whose name matches the regular expression above. If left empty, all messages from bots will be propagated. - settings.get('SlackBridge_ExcludeBotnames', (key, value) => { + settings.watch('SlackBridge_ExcludeBotnames', (value) => { this.excludeBotnames = value; - classLogger.debug(`Setting: ${ key }`, value); + classLogger.debug('Setting: SlackBridge_ExcludeBotnames', value); }); // Reactions - settings.get('SlackBridge_Reactions_Enabled', (key, value) => { + settings.watch('SlackBridge_Reactions_Enabled', (value) => { this.isReactionsEnabled = value; - classLogger.debug(`Setting: ${ key }`, value); + classLogger.debug('Setting: SlackBridge_Reactions_Enabled', value); }); // Is this entire SlackBridge enabled - settings.get('SlackBridge_Enabled', (key, value) => { + settings.watch('SlackBridge_Enabled', (value) => { if (value && this.apiTokens) { this.connect(); } else { this.disconnect(); } - classLogger.debug(`Setting: ${ key }`, value); + classLogger.debug('Setting: SlackBridge_Enabled', value); }); } } diff --git a/app/smarsh-connector/server/settings.js b/app/smarsh-connector/server/settings.js index 38ad04755a6f..04e0bf5de33e 100644 --- a/app/smarsh-connector/server/settings.js +++ b/app/smarsh-connector/server/settings.js @@ -1,9 +1,9 @@ import moment from 'moment'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; import 'moment-timezone'; -settings.addGroup('Smarsh', function addSettings() { +settingsRegistry.addGroup('Smarsh', function addSettings() { this.add('Smarsh_Enabled', false, { type: 'boolean', i18nLabel: 'Smarsh_Enabled', diff --git a/app/smarsh-connector/server/startup.js b/app/smarsh-connector/server/startup.js index d0d356322da4..32842fe8048c 100644 --- a/app/smarsh-connector/server/startup.js +++ b/app/smarsh-connector/server/startup.js @@ -1,13 +1,12 @@ -import { Meteor } from 'meteor/meteor'; import { SyncedCron } from 'meteor/littledata:synced-cron'; -import _ from 'underscore'; import { smarsh } from './lib/rocketchat'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; const smarshJobName = 'Smarsh EML Connector'; -const _addSmarshSyncedCronJob = _.debounce(Meteor.bindEnvironment(function __addSmarshSyncedCronJobDebounced() { + +settings.watchMultiple(['Smarsh_Enabled', 'Smarsh_Email', 'From_Email', 'Smarsh_Interval'], function __addSmarshSyncedCronJobDebounced() { if (SyncedCron.nextScheduledAtDate(smarshJobName)) { SyncedCron.remove(smarshJobName); } @@ -19,15 +18,4 @@ const _addSmarshSyncedCronJob = _.debounce(Meteor.bindEnvironment(function __add job: smarsh.generateEml, }); } -}), 500); - -Meteor.startup(() => { - Meteor.defer(() => { - _addSmarshSyncedCronJob(); - - settings.get('Smarsh_Interval', _addSmarshSyncedCronJob); - settings.get('Smarsh_Enabled', _addSmarshSyncedCronJob); - settings.get('Smarsh_Email', _addSmarshSyncedCronJob); - settings.get('From_Email', _addSmarshSyncedCronJob); - }); }); diff --git a/app/sms/server/SMS.js b/app/sms/server/SMS.js index 9ee7c90b10e7..9f5dcc93574b 100644 --- a/app/sms/server/SMS.js +++ b/app/sms/server/SMS.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; export const SMS = { enabled: false, @@ -22,10 +22,10 @@ export const SMS = { }, }; -settings.get('SMS_Enabled', function(key, value) { +settings.watch('SMS_Enabled', function(value) { SMS.enabled = value; }); -settings.get('SMS_Default_Omnichannel_Department', function(key, value) { +settings.watch('SMS_Default_Omnichannel_Department', function(value) { SMS.department = value; }); diff --git a/app/sms/server/settings.js b/app/sms/server/settings.js deleted file mode 100644 index 4a5ffae29bf9..000000000000 --- a/app/sms/server/settings.js +++ /dev/null @@ -1,170 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.addGroup('SMS', function() { - this.add('SMS_Enabled', false, { - type: 'boolean', - i18nLabel: 'Enabled', - }); - - this.add('SMS_Service', 'twilio', { - type: 'select', - values: [ - { - key: 'twilio', - i18nLabel: 'Twilio', - }, - { - key: 'voxtelesys', - i18nLabel: 'Voxtelesys', - }, - { - key: 'mobex', - i18nLabel: 'Mobex', - }, - ], - i18nLabel: 'Service', - }); - - this.add('SMS_Default_Omnichannel_Department', '', { - type: 'string', - }); - - this.section('Twilio', function() { - this.add('SMS_Twilio_Account_SID', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'twilio', - }, - i18nLabel: 'Account_SID', - secret: true, - }); - this.add('SMS_Twilio_authToken', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'twilio', - }, - i18nLabel: 'Auth_Token', - secret: true, - }); - this.add('SMS_Twilio_FileUpload_Enabled', true, { - type: 'boolean', - enableQuery: { - _id: 'SMS_Service', - value: 'twilio', - }, - i18nLabel: 'FileUpload_Enabled', - secret: true, - }); - this.add('SMS_Twilio_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'twilio', - }, - i18nLabel: 'FileUpload_MediaTypeWhiteList', - i18nDescription: 'FileUpload_MediaTypeWhiteListDescription', - secret: true, - }); - }); - - this.section('Voxtelesys', function() { - this.add('SMS_Voxtelesys_authToken', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'voxtelesys', - }, - i18nLabel: 'Auth_Token', - secret: true, - }); - this.add('SMS_Voxtelesys_URL', 'https://smsapi.voxtelesys.net/api/v1/sms', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'voxtelesys', - }, - i18nLabel: 'URL', - secret: true, - }); - this.add('SMS_Voxtelesys_FileUpload_Enabled', true, { - type: 'boolean', - enableQuery: { - _id: 'SMS_Service', - value: 'voxtelesys', - }, - i18nLabel: 'FileUpload_Enabled', - secret: true, - }); - this.add('SMS_Voxtelesys_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'voxtelesys', - }, - i18nLabel: 'FileUpload_MediaTypeWhiteList', - i18nDescription: 'FileUpload_MediaTypeWhiteListDescription', - secret: true, - }); - }); - - this.section('Mobex', function() { - this.add('SMS_Mobex_gateway_address', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_address', - i18nDescription: 'Mobex_sms_gateway_address_desc', - }); - this.add('SMS_Mobex_restful_address', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_restful_address', - i18nDescription: 'Mobex_sms_gateway_restful_address_desc', - }); - this.add('SMS_Mobex_username', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_username', - }); - this.add('SMS_Mobex_password', '', { - type: 'password', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_password', - }); - this.add('SMS_Mobex_from_number', '', { - type: 'int', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_from_number', - i18nDescription: 'Mobex_sms_gateway_from_number_desc', - }); - this.add('SMS_Mobex_from_numbers_list', '', { - type: 'string', - enableQuery: { - _id: 'SMS_Service', - value: 'mobex', - }, - i18nLabel: 'Mobex_sms_gateway_from_numbers_list', - i18nDescription: 'Mobex_sms_gateway_from_numbers_list_desc', - }); - }); - }); -}); diff --git a/app/sms/server/settings.ts b/app/sms/server/settings.ts new file mode 100644 index 000000000000..448934362ab2 --- /dev/null +++ b/app/sms/server/settings.ts @@ -0,0 +1,167 @@ +import { settingsRegistry } from '../../settings/server'; + + +settingsRegistry.addGroup('SMS', function() { + this.add('SMS_Enabled', false, { + type: 'boolean', + i18nLabel: 'Enabled', + }); + + this.add('SMS_Service', 'twilio', { + type: 'select', + values: [ + { + key: 'twilio', + i18nLabel: 'Twilio', + }, + { + key: 'voxtelesys', + i18nLabel: 'Voxtelesys', + }, + { + key: 'mobex', + i18nLabel: 'Mobex', + }, + ], + i18nLabel: 'Service', + }); + + this.add('SMS_Default_Omnichannel_Department', '', { + type: 'string', + }); + + this.section('Twilio', function() { + this.add('SMS_Twilio_Account_SID', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'twilio', + }, + i18nLabel: 'Account_SID', + secret: true, + }); + this.add('SMS_Twilio_authToken', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'twilio', + }, + i18nLabel: 'Auth_Token', + secret: true, + }); + this.add('SMS_Twilio_FileUpload_Enabled', true, { + type: 'boolean', + enableQuery: { + _id: 'SMS_Service', + value: 'twilio', + }, + i18nLabel: 'FileUpload_Enabled', + secret: true, + }); + this.add('SMS_Twilio_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'twilio', + }, + i18nLabel: 'FileUpload_MediaTypeWhiteList', + i18nDescription: 'FileUpload_MediaTypeWhiteListDescription', + secret: true, + }); + }); + + this.section('Voxtelesys', function() { + this.add('SMS_Voxtelesys_authToken', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'voxtelesys', + }, + i18nLabel: 'Auth_Token', + secret: true, + }); + this.add('SMS_Voxtelesys_URL', 'https://smsapi.voxtelesys.net/api/v1/sms', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'voxtelesys', + }, + i18nLabel: 'URL', + secret: true, + }); + this.add('SMS_Voxtelesys_FileUpload_Enabled', true, { + type: 'boolean', + enableQuery: { + _id: 'SMS_Service', + value: 'voxtelesys', + }, + i18nLabel: 'FileUpload_Enabled', + secret: true, + }); + this.add('SMS_Voxtelesys_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'voxtelesys', + }, + i18nLabel: 'FileUpload_MediaTypeWhiteList', + i18nDescription: 'FileUpload_MediaTypeWhiteListDescription', + secret: true, + }); + }); + + this.section('Mobex', function() { + this.add('SMS_Mobex_gateway_address', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_address', + i18nDescription: 'Mobex_sms_gateway_address_desc', + }); + this.add('SMS_Mobex_restful_address', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_restful_address', + i18nDescription: 'Mobex_sms_gateway_restful_address_desc', + }); + this.add('SMS_Mobex_username', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_username', + }); + this.add('SMS_Mobex_password', '', { + type: 'password', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_password', + }); + this.add('SMS_Mobex_from_number', '', { + type: 'int', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_from_number', + i18nDescription: 'Mobex_sms_gateway_from_number_desc', + }); + this.add('SMS_Mobex_from_numbers_list', '', { + type: 'string', + enableQuery: { + _id: 'SMS_Service', + value: 'mobex', + }, + i18nLabel: 'Mobex_sms_gateway_from_numbers_list', + i18nDescription: 'Mobex_sms_gateway_from_numbers_list_desc', + }); + }); +}); diff --git a/app/statistics/server/lib/getServicesStatistics.ts b/app/statistics/server/lib/getServicesStatistics.ts index 2bdb07b698f0..faf9a07928c3 100644 --- a/app/statistics/server/lib/getServicesStatistics.ts +++ b/app/statistics/server/lib/getServicesStatistics.ts @@ -6,8 +6,8 @@ function getCustomOAuthServices(): Record { - const customOauth = settings.get(/Accounts_OAuth_Custom-[^-]+$/mi); - return Object.fromEntries(customOauth.map(({ key, value }) => { + const customOauth = settings.getByRegexp(/Accounts_OAuth_Custom-[^-]+$/mi); + return Object.fromEntries(Object.entries(customOauth).map(([key, value]) => { const name = key.replace('Accounts_OAuth_Custom-', ''); return [name, { enabled: Boolean(value), diff --git a/app/statistics/server/startup/monitor.js b/app/statistics/server/startup/monitor.js index 70cd64bf5c6c..ab85ddbe835e 100644 --- a/app/statistics/server/startup/monitor.js +++ b/app/statistics/server/startup/monitor.js @@ -8,7 +8,7 @@ const SAUMonitor = new SAUMonitorClass(); Meteor.startup(() => { let TroubleshootDisableSessionsMonitor; - settings.get('Troubleshoot_Disable_Sessions_Monitor', (key, value) => { + settings.watch('Troubleshoot_Disable_Sessions_Monitor', (value) => { if (TroubleshootDisableSessionsMonitor === value) { return; } TroubleshootDisableSessionsMonitor = value; diff --git a/app/theme/server/server.js b/app/theme/server/server.js index fbdc4fcee936..b571936817d6 100644 --- a/app/theme/server/server.js +++ b/app/theme/server/server.js @@ -6,9 +6,10 @@ import Autoprefixer from 'less-plugin-autoprefixer'; import { WebApp } from 'meteor/webapp'; import { Meteor } from 'meteor/meteor'; -import { settings } from '../../settings'; +import { settings, settingsRegistry } from '../../settings/server'; import { Logger } from '../../logger'; import { addStyle } from '../../ui-master/server/inject'; +import { Settings } from '../../models/server'; const logger = new Logger('rocketchat:theme'); @@ -20,34 +21,27 @@ export const theme = new class { this.variables = {}; this.packageCallbacks = []; this.customCSS = ''; - settings.add('css', ''); - settings.addGroup('Layout'); - settings.onload('css', Meteor.bindEnvironment((key, value, initialLoad) => { - if (!initialLoad) { - Meteor.startup(function() { - process.emit('message', { - refresh: 'client', - }); - }); - } - })); - this.compileDelayed = _.debounce(Meteor.bindEnvironment(this.compile.bind(this)), 100); - Meteor.startup(() => { - settings.onAfterInitialLoad(() => { - settings.get(/^theme-./, Meteor.bindEnvironment((key, value) => { - if (key === 'theme-custom-css' && value != null) { - this.customCSS = value; - } else { - const name = key.replace(/^theme-[a-z]+-/, ''); - if (this.variables[name] != null) { - this.variables[name].value = value; - } - } - - this.compileDelayed(); - })); + settingsRegistry.add('css', ''); + settingsRegistry.addGroup('Layout'); + settings.change('css', () => { + process.emit('message', { + refresh: 'client', }); }); + + this.compileDelayed = _.debounce(Meteor.bindEnvironment(this.compile.bind(this)), 100); + settings.watchByRegex(/^theme-./, (key, value) => { + if (key === 'theme-custom-css' && value != null) { + this.customCSS = value; + } else { + const name = key.replace(/^theme-[a-z]+-/, ''); + if (this.variables[name] != null) { + this.variables[name].value = value; + } + } + + this.compileDelayed(); + }); } compile() { @@ -67,7 +61,7 @@ export const theme = new class { if (err != null) { return logger.error(err); } - settings.updateById('css', data.css); + Settings.updateValueById('css', data.css); return Meteor.startup(function() { return Meteor.setTimeout(function() { @@ -89,7 +83,7 @@ export const theme = new class { section, }; - return settings.add(`theme-color-${ name }`, value, config); + return settingsRegistry.add(`theme-color-${ name }`, value, config); } addVariable(type, name, value, section, persist = true, editor, allowedTypes, property) { @@ -108,7 +102,7 @@ export const theme = new class { allowedTypes, property, }; - return settings.add(`theme-${ type }-${ name }`, value, config); + return settingsRegistry.add(`theme-${ type }-${ name }`, value, config); } } @@ -130,7 +124,7 @@ export const theme = new class { }(); Meteor.startup(() => { - settings.get('css', (key, value = '') => { + settings.watch('css', (value = '') => { currentHash = crypto.createHash('sha1').update(value).digest('hex'); currentSize = value.length; addStyle('css-theme', value); diff --git a/app/theme/server/variables.js b/app/theme/server/variables.js index c95c243ffeb6..d609508aaf5f 100644 --- a/app/theme/server/variables.js +++ b/app/theme/server/variables.js @@ -1,5 +1,5 @@ import { theme } from './server'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; // TODO: Define registers/getters/setters for packages to work with established // heirarchy of colors instead of making duplicate definitions // TODO: Settings pages to show simple separation of major/minor/addon colors @@ -48,7 +48,7 @@ for (let matches = regionRegex.exec(variablesContent); matches; matches = region }); } -settings.add('theme-custom-css', '', { +settingsRegistry.add('theme-custom-css', '', { group: 'Layout', type: 'code', code: 'text/css', diff --git a/app/threads/server/hooks/aftersavemessage.js b/app/threads/server/hooks/aftersavemessage.js index f08f922b6fec..db9bbc3364bf 100644 --- a/app/threads/server/hooks/aftersavemessage.js +++ b/app/threads/server/hooks/aftersavemessage.js @@ -64,7 +64,7 @@ export const processThreads = (message, room) => { }; Meteor.startup(function() { - settings.get('Threads_enabled', function(key, value) { + settings.watch('Threads_enabled', function(value) { if (!value) { callbacks.remove('afterSaveMessage', 'threads-after-save-message'); return; diff --git a/app/threads/server/settings.js b/app/threads/server/settings.js deleted file mode 100644 index edc965aaca80..000000000000 --- a/app/threads/server/settings.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(() => { - settings.addGroup('Threads', function() { - this.add('Threads_enabled', true, { - group: 'Threads', - i18nLabel: 'Enable', - type: 'boolean', - public: true, - }); - }); -}); diff --git a/app/threads/server/settings.ts b/app/threads/server/settings.ts new file mode 100644 index 000000000000..4762d0170299 --- /dev/null +++ b/app/threads/server/settings.ts @@ -0,0 +1,10 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.addGroup('Threads', function() { + this.add('Threads_enabled', true, { + group: 'Threads', + i18nLabel: 'Enable', + type: 'boolean', + public: true, + }); +}); diff --git a/app/tokenpass/server/startup.js b/app/tokenpass/server/startup.js index 6ff1ae50ce86..974bfc6bbc14 100644 --- a/app/tokenpass/server/startup.js +++ b/app/tokenpass/server/startup.js @@ -2,12 +2,12 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { updateUserTokenpassBalances } from './functions/updateUserTokenpassBalances'; -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; import { callbacks } from '../../callbacks'; import { validateTokenAccess } from './roomAccessValidator.compatibility'; import './roomAccessValidator.internalService'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { this.section('Tokenpass', function() { const enableQuery = { _id: 'Accounts_OAuth_Tokenpass', diff --git a/app/ui-master/server/index.js b/app/ui-master/server/index.js index ec4dc5fd1700..454e165b9a82 100644 --- a/app/ui-master/server/index.js +++ b/app/ui-master/server/index.js @@ -17,7 +17,7 @@ Meteor.startup(() => { Inject.rawModHtml('headInjections', applyHeadInjections(injections)); }); - settings.get('Default_Referrer_Policy', (key, value) => { + settings.watch('Default_Referrer_Policy', (value) => { if (!value) { return injectIntoHead('noreferrer', ''); } @@ -35,63 +35,63 @@ Meteor.startup(() => { `); } - settings.get('Assets_SvgFavicon_Enable', (key, value) => { + settings.watch('Assets_SvgFavicon_Enable', (value) => { const standardFavicons = ` `; if (value) { - injectIntoHead(key, + injectIntoHead('Assets_SvgFavicon_Enable', `${ standardFavicons } `); } else { - injectIntoHead(key, standardFavicons); + injectIntoHead('Assets_SvgFavicon_Enable', standardFavicons); } }); - settings.get('theme-color-sidebar-background', (key, value) => { + settings.watch('theme-color-sidebar-background', (value) => { const escapedValue = escapeHTML(value); - injectIntoHead(key, `` + injectIntoHead('theme-color-sidebar-background', `` + ``); }); - settings.get('Site_Name', (key, value = 'Rocket.Chat') => { + settings.watch('Site_Name', (value = 'Rocket.Chat') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, + injectIntoHead('Site_Name', `${ escapedValue }` + `` + ``); }); - settings.get('Meta_language', (key, value = '') => { + settings.watch('Meta_language', (value = '') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, + injectIntoHead('Meta_language', `` + ``); }); - settings.get('Meta_robots', (key, value = '') => { + settings.watch('Meta_robots', (value = '') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, ``); + injectIntoHead('Meta_robots', ``); }); - settings.get('Meta_msvalidate01', (key, value = '') => { + settings.watch('Meta_msvalidate01', (value = '') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, ``); + injectIntoHead('Meta_msvalidate01', ``); }); - settings.get('Meta_google-site-verification', (key, value = '') => { + settings.watch('Meta_google-site-verification', (value = '') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, ``); + injectIntoHead('Meta_google-site-verification', ``); }); - settings.get('Meta_fb_app_id', (key, value = '') => { + settings.watch('Meta_fb_app_id', (value = '') => { const escapedValue = escapeHTML(value); - injectIntoHead(key, ``); + injectIntoHead('Meta_fb_app_id', ``); }); - settings.get('Meta_custom', (key, value = '') => { - injectIntoHead(key, value); + settings.watch('Meta_custom', (value = '') => { + injectIntoHead('Meta_custom', value); }); const baseUrl = ((prefix) => { @@ -135,7 +135,7 @@ renderDynamicCssList(); // changed: renderDynamicCssList // }); -settings.get(/theme-color-rc/i, () => renderDynamicCssList()); +settings.watchByRegex(/theme-color-rc/i, renderDynamicCssList); injectIntoBody('react-root', `
diff --git a/app/ui-master/server/scripts.ts b/app/ui-master/server/scripts.ts index 88ee3f500082..6104cd2e0d03 100644 --- a/app/ui-master/server/scripts.ts +++ b/app/ui-master/server/scripts.ts @@ -1,5 +1,3 @@ -import { debounce } from 'underscore'; - import { settings } from '../../settings/server'; import { addScript } from './inject'; @@ -37,7 +35,7 @@ window.addEventListener('load', function() { }); ` : '' }`; -settings.get(/(API_Use_REST_For_DDP_Calls|Custom_Script_Logged_Out|Custom_Script_Logged_In|Custom_Script_On_Logout|Accounts_ForgetUserSessionOnWindowClose|ECDH_Enabled)/, debounce(() => { +settings.watchMultiple(['API_Use_REST_For_DDP_Calls', 'Custom_Script_Logged_Out', 'Custom_Script_Logged_In', 'Custom_Script_On_Logout', 'Accounts_ForgetUserSessionOnWindowClose', 'ECDH_Enabled'], () => { const content = getContent(); addScript('scripts', content); -}, 1000)); +}); diff --git a/app/ui-vrecord/server/settings.js b/app/ui-vrecord/server/settings.ts similarity index 59% rename from app/ui-vrecord/server/settings.js rename to app/ui-vrecord/server/settings.ts index 829b5eb1e9cd..65c4d8d2e50a 100644 --- a/app/ui-vrecord/server/settings.js +++ b/app/ui-vrecord/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('Message', function() { +settingsRegistry.addGroup('Message', function() { this.add('Message_VideoRecorderEnabled', true, { type: 'boolean', public: true, diff --git a/app/user-data-download/server/cronProcessDownloads.js b/app/user-data-download/server/cronProcessDownloads.js index b917a9220687..8fb19b86b08c 100644 --- a/app/user-data-download/server/cronProcessDownloads.js +++ b/app/user-data-download/server/cronProcessDownloads.js @@ -586,21 +586,19 @@ async function processDataDownloads() { const name = 'Generate download files for user data'; Meteor.startup(function() { - Meteor.defer(function() { - let TroubleshootDisableDataExporterProcessor; - settings.get('Troubleshoot_Disable_Data_Exporter_Processor', (key, value) => { - if (TroubleshootDisableDataExporterProcessor === value) { return; } - TroubleshootDisableDataExporterProcessor = value; - - if (value) { - return SyncedCron.remove(name); - } + let TroubleshootDisableDataExporterProcessor; + settings.watch('Troubleshoot_Disable_Data_Exporter_Processor', (value) => { + if (TroubleshootDisableDataExporterProcessor === value) { return; } + TroubleshootDisableDataExporterProcessor = value; - SyncedCron.add({ - name, - schedule: (parser) => parser.cron(`*/${ processingFrequency } * * * *`), - job: processDataDownloads, - }); + if (value) { + return SyncedCron.remove(name); + } + + SyncedCron.add({ + name, + schedule: (parser) => parser.cron(`*/${ processingFrequency } * * * *`), + job: processDataDownloads, }); }); }); diff --git a/app/user-data-download/server/startup/settings.js b/app/user-data-download/server/startup/settings.ts similarity index 84% rename from app/user-data-download/server/startup/settings.js rename to app/user-data-download/server/startup/settings.ts index 08710ad58134..17fb1cfa6187 100644 --- a/app/user-data-download/server/startup/settings.js +++ b/app/user-data-download/server/startup/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../../settings'; +import { settingsRegistry } from '../../../settings/server'; -settings.addGroup('UserDataDownload', function() { +settingsRegistry.addGroup('UserDataDownload', function() { this.add('UserData_EnableDownload', true, { type: 'boolean', public: true, diff --git a/app/version-check/server/addSettings.js b/app/version-check/server/addSettings.ts similarity index 75% rename from app/version-check/server/addSettings.js rename to app/version-check/server/addSettings.ts index 19ea8233d869..40a1f3276543 100644 --- a/app/version-check/server/addSettings.js +++ b/app/version-check/server/addSettings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('General', function() { +settingsRegistry.addGroup('General', function() { this.section('Update', function() { this.add('Update_LatestAvailableVersion', '0.0.0', { type: 'string', diff --git a/app/version-check/server/functions/checkVersionUpdate.js b/app/version-check/server/functions/checkVersionUpdate.js index 1ea0e8b9c924..65cd246d663c 100644 --- a/app/version-check/server/functions/checkVersionUpdate.js +++ b/app/version-check/server/functions/checkVersionUpdate.js @@ -7,6 +7,7 @@ import { Info } from '../../../utils'; import { Users } from '../../../models'; import logger from '../logger'; import { sendMessagesToAdmins } from '../../../../server/lib/sendMessagesToAdmins'; +import { Settings } from '../../../models/server'; // import getNewUpdates from '../sampleUpdateData'; export default () => { @@ -39,7 +40,7 @@ export default () => { }); if (update.exists) { - settings.updateById('Update_LatestAvailableVersion', update.lastestVersion.version); + Settings.updateValueById('Update_LatestAvailableVersion', update.lastestVersion.version); sendMessagesToAdmins({ msgs: ({ adminUser }) => [{ msg: `*${ TAPi18n.__('Update_your_RocketChat', adminUser.language) }*\n${ TAPi18n.__('New_version_available_(s)', update.lastestVersion.version, adminUser.language) }\n${ update.lastestVersion.infoUrl }` }], diff --git a/app/version-check/server/index.js b/app/version-check/server/index.ts similarity index 80% rename from app/version-check/server/index.js rename to app/version-check/server/index.ts index d61748076e31..c953124e143e 100644 --- a/app/version-check/server/index.js +++ b/app/version-check/server/index.ts @@ -1,8 +1,7 @@ -import _ from 'underscore'; import { Meteor } from 'meteor/meteor'; import { SyncedCron } from 'meteor/littledata:synced-cron'; -import { settings } from '../../settings'; +import { settings } from '../../settings/server'; import checkVersionUpdate from './functions/checkVersionUpdate'; import './methods/banner_dismiss'; import './addSettings'; @@ -16,7 +15,7 @@ if (SyncedCron.nextScheduledAtDate(jobName)) { const addVersionCheckJob = Meteor.bindEnvironment(() => { SyncedCron.add({ name: jobName, - schedule: (parser) => parser.text('at 2:00 am'), + schedule: (parser: { text: (time: string) => string }) => parser.text('at 2:00 am'), job() { checkVersionUpdate(); }, @@ -32,7 +31,7 @@ Meteor.startup(() => { }); }); -settings.get(/Register_Server|Update_EnableChecker/, _.debounce(() => { +settings.watchMultiple(['Register_Server', 'Update_EnableChecker'], () => { const checkForUpdates = settings.get('Register_Server') && settings.get('Update_EnableChecker'); if (checkForUpdates && SyncedCron.nextScheduledAtDate(jobName)) { @@ -45,4 +44,4 @@ settings.get(/Register_Server|Update_EnableChecker/, _.debounce(() => { } SyncedCron.remove(jobName); -}, 1000)); +}); diff --git a/app/version-check/server/logger.js b/app/version-check/server/logger.js deleted file mode 100644 index d45a56a597af..000000000000 --- a/app/version-check/server/logger.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Logger } from '../../logger'; - -export default new Logger('VersionCheck'); diff --git a/app/version-check/server/logger.ts b/app/version-check/server/logger.ts new file mode 100644 index 000000000000..dc35a0b0d50d --- /dev/null +++ b/app/version-check/server/logger.ts @@ -0,0 +1,3 @@ +import { Logger } from '../../logger/server'; + +export default new Logger('VersionCheck'); diff --git a/app/videobridge/server/settings.js b/app/videobridge/server/settings.js deleted file mode 100644 index bbfe06dc9d52..000000000000 --- a/app/videobridge/server/settings.js +++ /dev/null @@ -1,224 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../settings'; - -Meteor.startup(function() { - settings.addGroup('Video Conference', function() { - this.section('BigBlueButton', function() { - this.add('bigbluebutton_Enabled', false, { - type: 'boolean', - i18nLabel: 'Enabled', - alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', - public: true, - }); - - this.add('bigbluebutton_server', '', { - type: 'string', - i18nLabel: 'Domain', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - }); - - this.add('bigbluebutton_sharedSecret', '', { - type: 'string', - i18nLabel: 'Shared_Secret', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - }); - - - this.add('bigbluebutton_Open_New_Window', false, { - type: 'boolean', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - i18nLabel: 'Always_open_in_new_window', - public: true, - }); - - - this.add('bigbluebutton_enable_d', true, { - type: 'boolean', - i18nLabel: 'WebRTC_Enable_Direct', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - public: true, - }); - - this.add('bigbluebutton_enable_p', true, { - type: 'boolean', - i18nLabel: 'WebRTC_Enable_Private', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - public: true, - }); - - this.add('bigbluebutton_enable_c', false, { - type: 'boolean', - i18nLabel: 'WebRTC_Enable_Channel', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - public: true, - }); - - this.add('bigbluebutton_enable_teams', false, { - type: 'boolean', - i18nLabel: 'BBB_Enable_Teams', - enableQuery: { - _id: 'bigbluebutton_Enabled', - value: true, - }, - public: true, - }); - }); - - this.section('Jitsi', function() { - this.add('Jitsi_Enabled', false, { - type: 'boolean', - i18nLabel: 'Enabled', - alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', - public: true, - }); - - this.add('Jitsi_Domain', 'meet.jit.si', { - type: 'string', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Domain', - public: true, - }); - - this.add('Jitsi_URL_Room_Prefix', 'RocketChat', { - type: 'string', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'URL_room_prefix', - public: true, - }); - - this.add('Jitsi_URL_Room_Suffix', '', { - type: 'string', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'URL_room_suffix', - public: true, - }); - - this.add('Jitsi_URL_Room_Hash', true, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'URL_room_hash', - i18nDescription: 'URL_room_hash_description', - public: true, - }); - - this.add('Jitsi_SSL', true, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'SSL', - public: true, - }); - - this.add('Jitsi_Open_New_Window', false, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Always_open_in_new_window', - public: true, - }); - - this.add('Jitsi_Enable_Channels', false, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Jitsi_Enable_Channels', - public: true, - }); - - this.add('Jitsi_Enable_Teams', false, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Jitsi_Enable_Teams', - public: true, - }); - - this.add('Jitsi_Chrome_Extension', 'nocfbnnmjnndkbipkabodnheejiegccf', { - type: 'string', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Jitsi_Chrome_Extension', - public: true, - }); - - this.add('Jitsi_Enabled_TokenAuth', false, { - type: 'boolean', - enableQuery: { - _id: 'Jitsi_Enabled', - value: true, - }, - i18nLabel: 'Jitsi_Enabled_TokenAuth', - public: true, - }); - - this.add('Jitsi_Application_ID', '', { - type: 'string', - enableQuery: [ - { _id: 'Jitsi_Enabled', value: true }, - { _id: 'Jitsi_Enabled_TokenAuth', value: true }, - ], - i18nLabel: 'Jitsi_Application_ID', - }); - - this.add('Jitsi_Application_Secret', '', { - type: 'string', - enableQuery: [ - { _id: 'Jitsi_Enabled', value: true }, - { _id: 'Jitsi_Enabled_TokenAuth', value: true }, - ], - i18nLabel: 'Jitsi_Application_Secret', - }); - - this.add('Jitsi_Limit_Token_To_Room', true, { - type: 'boolean', - enableQuery: [ - { _id: 'Jitsi_Enabled', value: true }, - { _id: 'Jitsi_Enabled_TokenAuth', value: true }, - ], - i18nLabel: 'Jitsi_Limit_Token_To_Room', - public: true, - }); - }); - }); -}); diff --git a/app/videobridge/server/settings.ts b/app/videobridge/server/settings.ts new file mode 100644 index 000000000000..09b14ddc9b65 --- /dev/null +++ b/app/videobridge/server/settings.ts @@ -0,0 +1,220 @@ +import { settingsRegistry } from '../../settings/server'; + +settingsRegistry.addGroup('Video Conference', function() { + this.section('BigBlueButton', function() { + this.add('bigbluebutton_Enabled', false, { + type: 'boolean', + i18nLabel: 'Enabled', + alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', + public: true, + }); + + this.add('bigbluebutton_server', '', { + type: 'string', + i18nLabel: 'Domain', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + }); + + this.add('bigbluebutton_sharedSecret', '', { + type: 'string', + i18nLabel: 'Shared_Secret', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + }); + + + this.add('bigbluebutton_Open_New_Window', false, { + type: 'boolean', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + i18nLabel: 'Always_open_in_new_window', + public: true, + }); + + + this.add('bigbluebutton_enable_d', true, { + type: 'boolean', + i18nLabel: 'WebRTC_Enable_Direct', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + public: true, + }); + + this.add('bigbluebutton_enable_p', true, { + type: 'boolean', + i18nLabel: 'WebRTC_Enable_Private', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + public: true, + }); + + this.add('bigbluebutton_enable_c', false, { + type: 'boolean', + i18nLabel: 'WebRTC_Enable_Channel', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + public: true, + }); + + this.add('bigbluebutton_enable_teams', false, { + type: 'boolean', + i18nLabel: 'BBB_Enable_Teams', + enableQuery: { + _id: 'bigbluebutton_Enabled', + value: true, + }, + public: true, + }); + }); + + this.section('Jitsi', function() { + this.add('Jitsi_Enabled', false, { + type: 'boolean', + i18nLabel: 'Enabled', + alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', + public: true, + }); + + this.add('Jitsi_Domain', 'meet.jit.si', { + type: 'string', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Domain', + public: true, + }); + + this.add('Jitsi_URL_Room_Prefix', 'RocketChat', { + type: 'string', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'URL_room_prefix', + public: true, + }); + + this.add('Jitsi_URL_Room_Suffix', '', { + type: 'string', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'URL_room_suffix', + public: true, + }); + + this.add('Jitsi_URL_Room_Hash', true, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'URL_room_hash', + i18nDescription: 'URL_room_hash_description', + public: true, + }); + + this.add('Jitsi_SSL', true, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'SSL', + public: true, + }); + + this.add('Jitsi_Open_New_Window', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Always_open_in_new_window', + public: true, + }); + + this.add('Jitsi_Enable_Channels', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Jitsi_Enable_Channels', + public: true, + }); + + this.add('Jitsi_Enable_Teams', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Jitsi_Enable_Teams', + public: true, + }); + + this.add('Jitsi_Chrome_Extension', 'nocfbnnmjnndkbipkabodnheejiegccf', { + type: 'string', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Jitsi_Chrome_Extension', + public: true, + }); + + this.add('Jitsi_Enabled_TokenAuth', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Jitsi_Enabled_TokenAuth', + public: true, + }); + + this.add('Jitsi_Application_ID', '', { + type: 'string', + enableQuery: [ + { _id: 'Jitsi_Enabled', value: true }, + { _id: 'Jitsi_Enabled_TokenAuth', value: true }, + ], + i18nLabel: 'Jitsi_Application_ID', + }); + + this.add('Jitsi_Application_Secret', '', { + type: 'string', + enableQuery: [ + { _id: 'Jitsi_Enabled', value: true }, + { _id: 'Jitsi_Enabled_TokenAuth', value: true }, + ], + i18nLabel: 'Jitsi_Application_Secret', + }); + + this.add('Jitsi_Limit_Token_To_Room', true, { + type: 'boolean', + enableQuery: [ + { _id: 'Jitsi_Enabled', value: true }, + { _id: 'Jitsi_Enabled_TokenAuth', value: true }, + ], + i18nLabel: 'Jitsi_Limit_Token_To_Room', + public: true, + }); + }); +}); diff --git a/app/webdav/server/startup/settings.js b/app/webdav/server/startup/settings.js deleted file mode 100644 index bd8cefe2185e..000000000000 --- a/app/webdav/server/startup/settings.js +++ /dev/null @@ -1,8 +0,0 @@ -import { settings } from '../../../settings'; - -settings.addGroup('Webdav Integration', function() { - this.add('Webdav_Integration_Enabled', false, { - type: 'boolean', - public: true, - }); -}); diff --git a/app/webdav/server/startup/settings.ts b/app/webdav/server/startup/settings.ts new file mode 100644 index 000000000000..303bde5ccee3 --- /dev/null +++ b/app/webdav/server/startup/settings.ts @@ -0,0 +1,8 @@ +import { settingsRegistry } from '../../../settings/server'; + +settingsRegistry.addGroup('Webdav Integration', function() { + this.add('Webdav_Integration_Enabled', false, { + type: 'boolean', + public: true, + }); +}); diff --git a/app/webrtc/server/settings.js b/app/webrtc/server/settings.ts similarity index 82% rename from app/webrtc/server/settings.js rename to app/webrtc/server/settings.ts index 0d5bea7c7923..b0cad64d4579 100644 --- a/app/webrtc/server/settings.js +++ b/app/webrtc/server/settings.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('WebRTC', function() { +settingsRegistry.addGroup('WebRTC', function() { this.add('WebRTC_Enable_Channel', false, { type: 'boolean', group: 'WebRTC', diff --git a/app/wordpress/lib/common.js b/app/wordpress/lib/common.js index 909342850d2e..60a7e57e5683 100644 --- a/app/wordpress/lib/common.js +++ b/app/wordpress/lib/common.js @@ -90,7 +90,7 @@ const fillSettings = _.debounce(Meteor.bindEnvironment(() => { if (Meteor.isServer) { Meteor.startup(function() { - return settings.get(/(API\_Wordpress\_URL)?(Accounts\_OAuth\_Wordpress\_)?/, () => fillSettings()); + return settings.watchByRegex(/(API\_Wordpress\_URL)?(Accounts\_OAuth\_Wordpress\_)?/, () => fillSettings()); }); } else { Meteor.startup(function() { diff --git a/app/wordpress/server/startup.js b/app/wordpress/server/startup.ts similarity index 94% rename from app/wordpress/server/startup.js rename to app/wordpress/server/startup.ts index 4f5248d9eae8..69d1eabdad1a 100644 --- a/app/wordpress/server/startup.js +++ b/app/wordpress/server/startup.ts @@ -1,6 +1,6 @@ -import { settings } from '../../settings'; +import { settingsRegistry } from '../../settings/server'; -settings.addGroup('OAuth', function() { +settingsRegistry.addGroup('OAuth', function() { return this.section('WordPress', function() { const enableQuery = { _id: 'Accounts_OAuth_Wordpress', @@ -82,7 +82,6 @@ settings.addGroup('OAuth', function() { return this.add('Accounts_OAuth_Wordpress_callback_url', '_oauth/wordpress', { type: 'relativeUrl', readonly: true, - force: true, enableQuery, }); }); diff --git a/client/contexts/EditableSettingsContext.ts b/client/contexts/EditableSettingsContext.ts index dc1d5bcc3771..db4695dbaa8b 100644 --- a/client/contexts/EditableSettingsContext.ts +++ b/client/contexts/EditableSettingsContext.ts @@ -1,10 +1,10 @@ import { createContext, useContext, useMemo } from 'react'; import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; -import { ISetting, SectionName, SettingId, GroupId, TabId } from '../../definition/ISetting'; +import { ISettingBase, SectionName, SettingId, GroupId, TabId } from '../../definition/ISetting'; import { SettingsContextQuery } from './SettingsContext'; -export interface IEditableSetting extends ISetting { +export interface IEditableSetting extends ISettingBase { disabled: boolean; changed: boolean; invisible: boolean; diff --git a/client/startup/theme.ts b/client/startup/theme.ts index f1ea2e56cceb..dd800f4edd8d 100644 --- a/client/startup/theme.ts +++ b/client/startup/theme.ts @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { settings } from '../../app/settings/client'; -import { ISetting } from '../../definition/ISetting'; +import { ISetting, ISettingColor } from '../../definition/ISetting'; const variables = new Map(); const lessExpressions = new Map([ @@ -72,7 +72,10 @@ const updateCssVariables = _.debounce(async () => { ].join('\n'); }, 50); -const handleThemeColorChanged = ({ _id, value, editor }: ISetting & { value: string }): void => { +const handleThemeColorChanged = ({ _id, value, editor }: ISettingColor): void => { + if (typeof value !== 'string') { + return; + } try { const name = /^theme-color-(.*)$/.exec(_id)?.[1]; diff --git a/definition/ISetting.ts b/definition/ISetting.ts index 257c86f74421..096d25bcae85 100644 --- a/definition/ISetting.ts +++ b/definition/ISetting.ts @@ -9,19 +9,21 @@ export enum SettingEditor { COLOR = 'color', EXPRESSION = 'expression' } - -export type SettingValueMultiSelect = string[]; +type AssetValue = { defaultUrl?: string }; +export type SettingValueMultiSelect = (string | number)[]; export type SettingValueRoomPick = Array<{_id: string; name: string}> | string; -export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined; +export type SettingValue = string | boolean | number | SettingValueMultiSelect | Date | AssetValue | undefined; export interface ISettingSelectOption { - key: string; + key: string | number; i18nLabel: string; } -export interface ISetting { +export type ISetting = ISettingBase | ISettingEnterprise | ISettingColor | ISettingCode | ISettingAction; + +export interface ISettingBase { _id: SettingId; - type: 'boolean' | 'string' | 'relativeUrl' | 'password' | 'int' | 'select' | 'multiSelect' | 'language' | 'color' | 'font' | 'code' | 'action' | 'asset' | 'roomPick' | 'group' | 'date'; + type: 'boolean' | 'timezone' |'string' | 'relativeUrl' | 'password' | 'int' | 'select' | 'multiSelect' | 'language' | 'color' | 'font' | 'code' | 'action' | 'asset' | 'roomPick' | 'group' | 'date'; public: boolean; env: boolean; group?: GroupId; @@ -30,12 +32,10 @@ export interface ISetting { i18nLabel: string; value: SettingValue; packageValue: SettingValue; - editor?: SettingEditor; - packageEditor?: SettingEditor; blocked: boolean; enableQuery?: string | FilterQuery | FilterQuery[]; displayQuery?: string | FilterQuery | FilterQuery[]; - sorter?: number; + sorter: number; properties?: unknown; enterprise?: boolean; requiredOnWizard?: boolean; @@ -48,7 +48,68 @@ export interface ISetting { autocomplete?: boolean; processEnvValue?: SettingValue; meteorSettingsValue?: SettingValue; - ts?: Date; + ts: Date; + createdAt: Date; + _updatedAt?: Date; multiline?: boolean; values?: Array; + placeholder?: string; + wizard?: { + step: number; + order: number; + }; + persistent?: boolean; // todo: remove + readonly?: boolean; // todo: remove + alert?: string; // todo: check if this is still used + private?: boolean; // todo: remove +} + +export interface ISettingGroup { + _id: string; + hidden: boolean; + blocked: boolean; + ts?: Date; + sorter: number; + i18nLabel: string; + displayQuery?: string | FilterQuery | FilterQuery[]; + i18nDescription: string; + value?: undefined; + type: 'group'; + + alert?: string; // todo: check if this is needed +} + + +export interface ISettingEnterprise extends ISettingBase { + enterprise: true; + invalidValue: SettingValue; +} + +export interface ISettingColor extends ISettingBase { + type: 'color'; + editor: SettingEditor; + packageEditor?: SettingEditor; +} +export interface ISettingCode extends ISettingBase { + type: 'code'; + code?: string; +} + +export interface ISettingAction extends ISettingBase { + type: 'action'; + actionText?: string; } +export interface ISettingAsset extends ISettingBase { + type: 'asset'; + value: AssetValue; +} + +export const isSettingEnterprise = (setting: ISettingBase): setting is ISettingEnterprise => setting.enterprise === true; + +export const isSettingColor = (setting: ISettingBase): setting is ISettingColor => setting.type === 'color'; + +export const isSettingCode = (setting: ISettingBase): setting is ISettingCode => setting.type === 'code'; + +export const isSettingAction = (setting: ISettingBase): setting is ISettingAction => setting.type === 'action'; + +export const isSettingAsset = (setting: ISettingBase): setting is ISettingAsset => setting.type === 'asset'; diff --git a/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts b/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts index f76f5a988d36..7afe45463a93 100644 --- a/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts +++ b/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts @@ -59,7 +59,7 @@ const handleBeforeSaveMessage = (message: IMessage, room: IOmnichannelRoom): any return message; }; -settings.get('Canned_Responses_Enable', function(_, value) { +settings.watch('Canned_Responses_Enable', function(value) { if (!value) { callbacks.remove('beforeSaveMessage', 'canned-responses-replace-placeholders'); return; diff --git a/ee/app/canned-responses/server/permissions.js b/ee/app/canned-responses/server/permissions.js index 65143c8d5b6d..26e838540df1 100644 --- a/ee/app/canned-responses/server/permissions.js +++ b/ee/app/canned-responses/server/permissions.js @@ -1,15 +1,13 @@ import { Meteor } from 'meteor/meteor'; -import { Permissions } from '../../../../app/models'; +import Permissions from '../../../../app/models/server/models/Permissions'; Meteor.startup(() => { - if (Permissions) { - Permissions.create('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); - Permissions.create('view-all-canned-responses', ['livechat-manager', 'admin']); - Permissions.create('view-agent-canned-responses', ['livechat-agent']); - Permissions.create('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); - Permissions.create('save-all-canned-responses', ['livechat-manager', 'admin']); - Permissions.create('save-department-canned-responses', ['livechat-monitor']); - Permissions.create('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); - } + Permissions.create('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); + Permissions.create('view-all-canned-responses', ['livechat-manager', 'admin']); + Permissions.create('view-agent-canned-responses', ['livechat-agent']); + Permissions.create('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); + Permissions.create('save-all-canned-responses', ['livechat-manager', 'admin']); + Permissions.create('save-department-canned-responses', ['livechat-monitor']); + Permissions.create('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); }); diff --git a/ee/app/canned-responses/server/settings.js b/ee/app/canned-responses/server/settings.ts similarity index 63% rename from ee/app/canned-responses/server/settings.js rename to ee/app/canned-responses/server/settings.ts index 5ba1e4cb35d6..fec9708d1390 100644 --- a/ee/app/canned-responses/server/settings.js +++ b/ee/app/canned-responses/server/settings.ts @@ -1,7 +1,7 @@ -import { settings } from '../../../../app/settings'; +import { settingsRegistry } from '../../../../app/settings/server'; export const createSettings = () => { - settings.add('Canned_Responses_Enable', true, { + settingsRegistry.add('Canned_Responses_Enable', true, { group: 'Omnichannel', section: 'Canned_Responses', type: 'boolean', diff --git a/ee/app/license/server/settings.js b/ee/app/license/server/settings.js index 939e327f4c6e..1fa774747867 100644 --- a/ee/app/license/server/settings.js +++ b/ee/app/license/server/settings.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../../../app/settings/server'; +import { settings, settingsRegistry } from '../../../../app/settings/server'; import { Settings } from '../../../../app/models/server'; import { addLicense } from './license'; Meteor.startup(function() { - settings.addGroup('Enterprise', function() { + settingsRegistry.addGroup('Enterprise', function() { this.section('License', function() { this.add('Enterprise_License', '', { type: 'string', @@ -20,7 +20,7 @@ Meteor.startup(function() { }); }); -settings.get('Enterprise_License', (key, license) => { +settings.watch('Enterprise_License', (license) => { if (!license || String(license).trim() === '') { return; } diff --git a/ee/app/license/server/startup.js b/ee/app/license/server/startup.js index af6384fc7153..cab8f58aa5af 100644 --- a/ee/app/license/server/startup.js +++ b/ee/app/license/server/startup.js @@ -2,7 +2,7 @@ import { settings } from '../../../../app/settings/server'; import { callbacks } from '../../../../app/callbacks'; import { addLicense, setURL } from './license'; -settings.get('Site_Url', (key, value) => { +settings.watch('Site_Url', (value) => { if (value) { setURL(value); } diff --git a/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts b/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts index 0f9c7661b910..0c5739b7b55d 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts +++ b/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts @@ -18,11 +18,11 @@ const setQueueTimer = (inquiry: any): void => { (LivechatInquiry as any).setEstimatedInactivityCloseTime(inquiry?._id, newQueueTime); }; -settings.get('Livechat_max_queue_wait_time', (_, value) => { +settings.watch('Livechat_max_queue_wait_time', (value) => { timer = value as number; }); -settings.get('Livechat_max_queue_wait_time_action', (_, value) => { +settings.watch('Livechat_max_queue_wait_time_action', (value) => { if (!value || value === 'Nothing') { callbacks.remove('livechat:afterReturnRoomAsInquiry', 'livechat-after-return-room-as-inquiry-set-queue-timer'); return; diff --git a/ee/app/livechat-enterprise/server/hooks/afterOnHold.ts b/ee/app/livechat-enterprise/server/hooks/afterOnHold.ts index f42e19bbebc4..c927b5f0b6a1 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterOnHold.ts +++ b/ee/app/livechat-enterprise/server/hooks/afterOnHold.ts @@ -27,7 +27,7 @@ const handleAfterOnHold = async (room: any = {}): Promise => { await AutoCloseOnHoldScheduler.scheduleRoom(room._id, autoCloseOnHoldChatTimeout, customCloseMessage); }; -settings.get('Livechat_auto_close_on_hold_chats_timeout', (_, value) => { +settings.watch('Livechat_auto_close_on_hold_chats_timeout', (value) => { autoCloseOnHoldChatTimeout = value as number; if (!value || value <= 0) { callbacks.remove('livechat:afterOnHold', 'livechat-auto-close-on-hold'); @@ -35,6 +35,6 @@ settings.get('Livechat_auto_close_on_hold_chats_timeout', (_, value) => { callbacks.add('livechat:afterOnHold', handleAfterOnHold, callbacks.priority.HIGH, 'livechat-auto-close-on-hold'); }); -settings.get('Livechat_auto_close_on_hold_chats_custom_message', (_, value) => { +settings.watch('Livechat_auto_close_on_hold_chats_custom_message', (value) => { customCloseMessage = value as string || DEFAULT_CLOSED_MESSAGE; }); diff --git a/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts b/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts index 79e25af766ef..3f019057b368 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts +++ b/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts @@ -12,7 +12,7 @@ const unsetPredictedVisitorAbandonment = ({ room }: { room: any }): void => { (LivechatRooms as any).unsetPredictedVisitorAbandonmentByRoomId(room._id); }; -settings.get('Livechat_abandoned_rooms_action', (_, value) => { +settings.watch('Livechat_abandoned_rooms_action', (value) => { if (!value || value === 'none') { callbacks.remove('livechat:afterReturnRoomAsInquiry', 'livechat-after-return-room-as-inquiry'); return; diff --git a/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js b/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js index 79addc40ad5a..d3ab8ae75a08 100644 --- a/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js +++ b/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js @@ -86,7 +86,7 @@ const afterTakeInquiry = (inquiry, agent) => { return inquiry; }; -settings.get('Livechat_last_chatted_agent_routing', function(key, value) { +settings.watch('Livechat_last_chatted_agent_routing', function(value) { lastChattedAgentPreferred = value; if (!lastChattedAgentPreferred) { callbacks.remove('livechat.onMaxNumberSimultaneousChatsReached', 'livechat-on-max-number-simultaneous-chats-reached'); @@ -98,7 +98,7 @@ settings.get('Livechat_last_chatted_agent_routing', function(key, value) { callbacks.add('livechat.onMaxNumberSimultaneousChatsReached', onMaxNumberSimultaneousChatsReached, callbacks.priority.MEDIUM, 'livechat-on-max-number-simultaneous-chats-reached'); }); -settings.get('Omnichannel_contact_manager_routing', function(key, value) { +settings.watch('Omnichannel_contact_manager_routing', function(value) { contactManagerPreferred = value; }); diff --git a/ee/app/livechat-enterprise/server/hooks/scheduleAutoTransfer.ts b/ee/app/livechat-enterprise/server/hooks/scheduleAutoTransfer.ts index 516452c5cc05..bb80a36015da 100644 --- a/ee/app/livechat-enterprise/server/hooks/scheduleAutoTransfer.ts +++ b/ee/app/livechat-enterprise/server/hooks/scheduleAutoTransfer.ts @@ -74,7 +74,7 @@ const handleAfterCloseRoom = async (room: any = {}): Promise => { return room; }; -settings.get('Livechat_auto_transfer_chat_timeout', function(_, value) { +settings.watch('Livechat_auto_transfer_chat_timeout', function(value) { autoTransferTimeout = value as number; if (!autoTransferTimeout || autoTransferTimeout === 0) { callbacks.remove('livechat.afterTakeInquiry', 'livechat-auto-transfer-job-inquiry'); diff --git a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js index 270a31218c8b..1da3c3ef1e38 100644 --- a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js +++ b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js @@ -259,8 +259,8 @@ const queueWorker = { }, }; -let omnichannelIsEnabled = false; +let omnichannelIsEnabled = false; function shouldQueueStart() { if (!omnichannelIsEnabled) { queueWorker.stop(); @@ -279,7 +279,7 @@ function shouldQueueStart() { RoutingManager.startQueue = shouldQueueStart; -settings.get('Livechat_enabled', (_, value) => { - omnichannelIsEnabled = value; +settings.watch('Livechat_enabled', (enabled) => { + omnichannelIsEnabled = enabled; omnichannelIsEnabled && RoutingManager.isMethodSet() ? shouldQueueStart() : queueWorker.stop(); }); diff --git a/ee/app/livechat-enterprise/server/settings.js b/ee/app/livechat-enterprise/server/settings.ts similarity index 87% rename from ee/app/livechat-enterprise/server/settings.js rename to ee/app/livechat-enterprise/server/settings.ts index 1c2faa307e24..97b7ea7c6c59 100644 --- a/ee/app/livechat-enterprise/server/settings.js +++ b/ee/app/livechat-enterprise/server/settings.ts @@ -1,8 +1,8 @@ -import { settings } from '../../../../app/settings'; +import { settingsRegistry } from '../../../../app/settings/server'; import { Settings } from '../../../../app/models/server'; export const createSettings = () => { - settings.add('Livechat_abandoned_rooms_action', 'none', { + settingsRegistry.add('Livechat_abandoned_rooms_action', 'none', { type: 'select', group: 'Omnichannel', section: 'Sessions', @@ -19,7 +19,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_abandoned_rooms_closed_custom_message', '', { + settingsRegistry.add('Livechat_abandoned_rooms_closed_custom_message', '', { type: 'string', group: 'Omnichannel', section: 'Sessions', @@ -32,7 +32,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_last_chatted_agent_routing', false, { + settingsRegistry.add('Livechat_last_chatted_agent_routing', false, { type: 'boolean', group: 'Omnichannel', section: 'Routing', @@ -43,7 +43,7 @@ export const createSettings = () => { ], }); - settings.addGroup('Omnichannel', function() { + settingsRegistry.addGroup('Omnichannel', function() { this.section('Business_Hours', function() { this.add('Livechat_business_hour_type', 'Single', { type: 'select', @@ -151,7 +151,7 @@ export const createSettings = () => { }); }); - settings.add('Omnichannel_contact_manager_routing', true, { + settingsRegistry.add('Omnichannel_contact_manager_routing', true, { type: 'boolean', group: 'Omnichannel', section: 'Routing', @@ -162,7 +162,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_auto_close_on_hold_chats_timeout', 3600, { + settingsRegistry.add('Livechat_auto_close_on_hold_chats_timeout', 3600, { type: 'int', group: 'Omnichannel', section: 'Sessions', @@ -173,7 +173,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_auto_close_on_hold_chats_custom_message', '', { + settingsRegistry.add('Livechat_auto_close_on_hold_chats_custom_message', '', { type: 'string', group: 'Omnichannel', section: 'Sessions', @@ -185,7 +185,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_allow_manual_on_hold', false, { + settingsRegistry.add('Livechat_allow_manual_on_hold', false, { type: 'boolean', group: 'Omnichannel', section: 'Sessions', @@ -197,7 +197,7 @@ export const createSettings = () => { ], }); - settings.add('Livechat_auto_transfer_chat_timeout', 0, { + settingsRegistry.add('Livechat_auto_transfer_chat_timeout', 0, { type: 'int', group: 'Omnichannel', section: 'Sessions', diff --git a/ee/app/livechat-enterprise/server/startup.js b/ee/app/livechat-enterprise/server/startup.ts similarity index 76% rename from ee/app/livechat-enterprise/server/startup.js rename to ee/app/livechat-enterprise/server/startup.ts index 4f8e6bf1d4f4..84ccdf37cc0a 100644 --- a/ee/app/livechat-enterprise/server/startup.js +++ b/ee/app/livechat-enterprise/server/startup.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { settings } from '../../../../app/settings'; +import { settings } from '../../../../app/settings/server'; import { updatePredictedVisitorAbandonment, updateQueueInactivityTimeout } from './lib/Helper'; import { VisitorInactivityMonitor } from './lib/VisitorInactivityMonitor'; import { OmnichannelQueueInactivityMonitor } from './lib/QueueInactivityMonitor'; @@ -17,23 +17,26 @@ const businessHours = { }; Meteor.startup(async function() { - settings.onload('Livechat_abandoned_rooms_action', function(_, value) { + settings.change('Livechat_abandoned_rooms_action', function(value) { updatePredictedVisitorAbandonment(); if (!value || value === 'none') { return visitorActivityMonitor.stop(); } visitorActivityMonitor.start(); }); - settings.onload('Livechat_visitor_inactivity_timeout', function() { + settings.change('Livechat_visitor_inactivity_timeout', function() { updatePredictedVisitorAbandonment(); }); - settings.onload('Livechat_business_hour_type', (_, value) => { - businessHourManager.registerBusinessHourBehavior(businessHours[value]); + settings.change('Livechat_business_hour_type', (value) => { + if (!Object.keys(businessHours).includes(value)) { + return; + } + businessHourManager.registerBusinessHourBehavior(businessHours[value as keyof typeof businessHours]); if (settings.get('Livechat_enable_business_hours')) { businessHourManager.startManager(); } }); - settings.onload('Livechat_max_queue_wait_time_action', function(_, value) { + settings.change('Livechat_max_queue_wait_time_action', function(value) { updateQueueInactivityTimeout(); if (!value || value === 'Nothing') { return Promise.await(OmnichannelQueueInactivityMonitor.stop()); @@ -41,7 +44,7 @@ Meteor.startup(async function() { return Promise.await(OmnichannelQueueInactivityMonitor.schedule()); }); - settings.onload('Livechat_max_queue_wait_time', function(_, value) { + settings.change('Livechat_max_queue_wait_time', function(value) { if (value <= 0) { return Promise.await(OmnichannelQueueInactivityMonitor.stop()); } diff --git a/ee/app/settings/server/settings.internalService.ts b/ee/app/settings/server/settings.internalService.ts index 3a3c6e96c60e..f0e37efdbcb1 100644 --- a/ee/app/settings/server/settings.internalService.ts +++ b/ee/app/settings/server/settings.internalService.ts @@ -9,7 +9,7 @@ class EnterpriseSettings extends ServiceClass implements IEnterpriseSettings { protected internal = true; - changeSettingValue(record: ISetting): undefined | { value: ISetting['value'] } { + changeSettingValue(record: ISetting): undefined | ISetting['value'] { return changeSettingValue(record); } } diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts index 0f1a63ddb944..dee2bcc020f2 100644 --- a/ee/app/settings/server/settings.ts +++ b/ee/app/settings/server/settings.ts @@ -1,17 +1,19 @@ import { Meteor } from 'meteor/meteor'; -import { SettingsEvents, settings } from '../../../../app/settings/server/functions/settings'; +import { settings } from '../../../../app/settings/server/functions/settings'; import { isEnterprise, hasLicense, onValidateLicenses } from '../../license/server/license'; import SettingsModel from '../../../../app/models/server/models/Settings'; import { ISetting, SettingValue } from '../../../../definition/ISetting'; +import { use } from '../../../../app/settings/server/Middleware'; +import { SettingsEvents } from '../../../../app/settings/server'; -export function changeSettingValue(record: ISetting): undefined | { value: SettingValue } { +export function changeSettingValue(record: ISetting): SettingValue { if (!record.enterprise) { return; } if (!isEnterprise()) { - return { value: record.invalidValue }; + return record.invalidValue; } if (!record.modules?.length) { @@ -20,31 +22,36 @@ export function changeSettingValue(record: ISetting): undefined | { value: Setti for (const moduleName of record.modules) { if (!hasLicense(moduleName)) { - return { value: record.invalidValue }; + return record.invalidValue; } } } -SettingsEvents.on('store-setting-value', (record: ISetting, newRecord: { value: SettingValue }) => { - const changedValue = changeSettingValue(record); - if (changedValue) { - newRecord.value = changedValue.value; +settings.set = use(settings.set, (context, next) => { + const [record] = context; + + if (!record.enterprise) { + return next(...context); } + const value = changeSettingValue(record); + + return next({ ...record, value }); }); SettingsEvents.on('fetch-settings', (settings: Array): void => { for (const setting of settings) { const changedValue = changeSettingValue(setting); - if (changedValue) { - setting.value = changedValue.value; + if (changedValue === undefined) { + continue; } + setting.value = changedValue; } }); function updateSettings(): void { const enterpriseSettings = SettingsModel.findEnterpriseSettings(); - enterpriseSettings.forEach((record: ISetting) => settings.storeSettingValue(record, false)); + enterpriseSettings.forEach((record: ISetting) => settings.set(record)); } diff --git a/ee/server/configuration/ldap.ts b/ee/server/configuration/ldap.ts index 86a953805e0e..29f19c8647ab 100644 --- a/ee/server/configuration/ldap.ts +++ b/ee/server/configuration/ldap.ts @@ -1,6 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import { Promise } from 'meteor/promise'; -import _ from 'underscore'; import { LDAPEE } from '../sdk'; import { settings } from '../../../app/settings/server'; @@ -11,9 +9,7 @@ import { LDAPEEManager } from '../lib/ldap/Manager'; import { callbacks } from '../../../app/callbacks/server'; import type { IImportUser } from '../../../definition/IImportUser'; import type { ILDAPEntry } from '../../../definition/ldap/ILDAPEntry'; -import type { SettingValue } from '../../../definition/ISetting'; import type { IUser } from '../../../definition/IUser'; -import type { SettingCallback } from '../../../app/settings/lib/settings'; import { onLicense } from '../../app/license/server'; import { addSettings } from '../settings/ldap'; @@ -21,10 +17,9 @@ Meteor.startup(() => onLicense('ldap-enterprise', () => { addSettings(); // Configure background sync cronjob - function configureBackgroundSync(jobName: string, enableSetting: string, intervalSetting: string, cb: () => {}): SettingCallback { + function configureBackgroundSync(jobName: string, enableSetting: string, intervalSetting: string, cb: () => {}): () => void { let lastSchedule: string; - - return _.debounce(Meteor.bindEnvironment(function addCronJobDebounced() { + return function addCronJobDebounced(): void { if (settings.get('LDAP_Enable') !== true || settings.get(enableSetting) !== true) { if (cronJobs.nextScheduledAtDate(jobName)) { logger.info({ msg: 'Disabling LDAP Background Sync', jobName }); @@ -43,34 +38,30 @@ Meteor.startup(() => onLicense('ldap-enterprise', () => { logger.info({ msg: 'Enabling LDAP Background Sync', jobName }); cronJobs.add(jobName, schedule, () => cb(), 'text'); } - }), 500); + }; } - const addCronJob = configureBackgroundSync('LDAP_Sync', 'LDAP_Background_Sync', 'LDAP_Background_Sync_Interval', () => Promise.await(LDAPEE.sync())); - const addAvatarCronJob = configureBackgroundSync('LDAP_AvatarSync', 'LDAP_Background_Sync_Avatars', 'LDAP_Background_Sync_Avatars_Interval', () => Promise.await(LDAPEE.syncAvatars())); - const addLogoutCronJob = configureBackgroundSync('LDAP_AutoLogout', 'LDAP_Sync_AutoLogout_Enabled', 'LDAP_Sync_AutoLogout_Interval', () => Promise.await(LDAPEE.syncLogout())); + const addCronJob = configureBackgroundSync('LDAP_Sync', 'LDAP_Background_Sync', 'LDAP_Background_Sync_Interval', () => LDAPEE.sync()); + const addAvatarCronJob = configureBackgroundSync('LDAP_AvatarSync', 'LDAP_Background_Sync_Avatars', 'LDAP_Background_Sync_Avatars_Interval', () => LDAPEE.syncAvatars()); + const addLogoutCronJob = configureBackgroundSync('LDAP_AutoLogout', 'LDAP_Sync_AutoLogout_Enabled', 'LDAP_Sync_AutoLogout_Interval', () => LDAPEE.syncLogout()); - Meteor.defer(() => { - settings.get('LDAP_Background_Sync', addCronJob); - settings.get('LDAP_Background_Sync_Interval', addCronJob); - settings.get('LDAP_Background_Sync_Avatars', addAvatarCronJob); - settings.get('LDAP_Background_Sync_Avatars_Interval', addAvatarCronJob); - settings.get('LDAP_Sync_AutoLogout_Enabled', addLogoutCronJob); - settings.get('LDAP_Sync_AutoLogout_Interval', addLogoutCronJob); - settings.get('LDAP_Enable', (key: string, value: SettingValue, initialLoad?: boolean) => { - addCronJob(key, value, initialLoad); - addAvatarCronJob(key, value, initialLoad); - addLogoutCronJob(key, value, initialLoad); - }); + settings.watchMultiple(['LDAP_Background_Sync', 'LDAP_Background_Sync_Interval'], addCronJob); + settings.watchMultiple(['LDAP_Background_Sync_Avatars', 'LDAP_Background_Sync_Avatars_Interval'], addAvatarCronJob); + settings.watchMultiple(['LDAP_Sync_AutoLogout_Enabled', 'LDAP_Sync_AutoLogout_Interval'], addLogoutCronJob); - settings.get('LDAP_Groups_To_Rocket_Chat_Teams', (_key, value) => { - try { - LDAPEEManager.validateLDAPTeamsMappingChanges(value as string); - } catch (error) { - logger.error(error); - } - }); + settings.watch('LDAP_Enable', () => { + addCronJob(); + addAvatarCronJob(); + addLogoutCronJob(); + }); + + settings.watch('LDAP_Groups_To_Rocket_Chat_Teams', (value) => { + try { + LDAPEEManager.validateLDAPTeamsMappingChanges(value); + } catch (error) { + logger.error(error); + } }); callbacks.add('mapLDAPUserData', (userData: IImportUser, ldapUser: ILDAPEntry) => { diff --git a/ee/server/settings/ldap.ts b/ee/server/settings/ldap.ts index af172127683a..18517bf6b620 100644 --- a/ee/server/settings/ldap.ts +++ b/ee/server/settings/ldap.ts @@ -1,10 +1,10 @@ -import { settings } from '../../../app/settings/server'; +import { settingsRegistry } from '../../../app/settings/server'; export function addSettings(): void { - settings.addGroup('LDAP', function() { + settingsRegistry.addGroup('LDAP', function() { const enableQuery = { _id: 'LDAP_Enable', value: true }; - this.set({ + this.with({ tab: 'LDAP_Enterprise', enterprise: true, modules: ['ldap-enterprise'], diff --git a/ee/server/settings/saml.ts b/ee/server/settings/saml.ts index 2cbd9566a2b4..b940a3dab989 100644 --- a/ee/server/settings/saml.ts +++ b/ee/server/settings/saml.ts @@ -1,4 +1,4 @@ -import { settings } from '../../../app/settings/server'; +import { settingsRegistry } from '../../../app/settings/server'; import { defaultAuthnContextTemplate, defaultAuthRequestTemplate, @@ -12,8 +12,8 @@ import { } from '../../../app/meteor-accounts-saml/server/lib/constants'; export const addSettings = function(name: string): void { - settings.addGroup('SAML', function() { - this.set({ + settingsRegistry.addGroup('SAML', function() { + this.with({ tab: 'SAML_Enterprise', enterprise: true, modules: ['saml-enterprise'], diff --git a/imports/message-read-receipt/server/settings.js b/imports/message-read-receipt/server/settings.js deleted file mode 100644 index be8b123b2dac..000000000000 --- a/imports/message-read-receipt/server/settings.js +++ /dev/null @@ -1,14 +0,0 @@ -import { settings } from '../../../app/settings'; - -settings.add('Message_Read_Receipt_Enabled', false, { - group: 'Message', - type: 'boolean', - public: true, -}); - -settings.add('Message_Read_Receipt_Store_Users', false, { - group: 'Message', - type: 'boolean', - public: true, - enableQuery: { _id: 'Message_Read_Receipt_Enabled', value: true }, -}); diff --git a/imports/message-read-receipt/server/settings.ts b/imports/message-read-receipt/server/settings.ts new file mode 100644 index 000000000000..8a20742847af --- /dev/null +++ b/imports/message-read-receipt/server/settings.ts @@ -0,0 +1,14 @@ +import { settingsRegistry } from '../../../app/settings/server'; + +settingsRegistry.add('Message_Read_Receipt_Enabled', false, { + group: 'Message', + type: 'boolean', + public: true, +}); + +settingsRegistry.add('Message_Read_Receipt_Store_Users', false, { + group: 'Message', + type: 'boolean', + public: true, + enableQuery: { _id: 'Message_Read_Receipt_Enabled', value: true }, +}); diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index dc0ee3a02d4a..9d6ee1c27bdc 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -26,7 +26,7 @@ export const setUserStatus = (user, status/* , statusConnection*/) => { }; let TroubleshootDisablePresenceBroadcast; -settings.get('Troubleshoot_Disable_Presence_Broadcast', (key, value) => { +settings.watch('Troubleshoot_Disable_Presence_Broadcast', (value) => { if (TroubleshootDisablePresenceBroadcast === value) { return; } TroubleshootDisablePresenceBroadcast = value; diff --git a/packages/rocketchat-google-natural-language/server/index.js b/packages/rocketchat-google-natural-language/server/index.js index 274d6d4b3ed2..e0a0d09cd41e 100644 --- a/packages/rocketchat-google-natural-language/server/index.js +++ b/packages/rocketchat-google-natural-language/server/index.js @@ -1,13 +1,13 @@ -import { Meteor } from 'meteor/meteor'; import { Rooms } from 'meteor/rocketchat:models'; -import { settings } from 'meteor/rocketchat:settings'; -import { callbacks } from 'meteor/rocketchat:callbacks'; -import './settings.js'; +import { Meteor } from 'meteor/meteor'; import googleLanguage from '@google-cloud/language'; +import { callbacks } from 'meteor/rocketchat:callbacks'; + +import { settings } from '../../../app/settings/server'; let languageClient; -settings.get('GoogleNaturalLanguage_ServiceAccount', (key, value) => { +settings.watch('GoogleNaturalLanguage_ServiceAccount', (value) => { if (value) { try { languageClient = googleLanguage({ @@ -34,7 +34,7 @@ const setRoomSentiment = function(message) { return message; }; -settings.get('GoogleNaturalLanguage_Enabled', (key, value) => { +settings.watch('GoogleNaturalLanguage_Enabled', (value) => { if (value) { callbacks.add('afterSaveMessage', setRoomSentiment, callbacks.priority.MEDIUM, 'GoogleNaturalLanguage'); } else { diff --git a/packages/rocketchat-google-natural-language/server/settings.js b/packages/rocketchat-google-natural-language/server/settings.js deleted file mode 100644 index 6f880cd40ca9..000000000000 --- a/packages/rocketchat-google-natural-language/server/settings.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { settings } from 'meteor/rocketchat:settings'; - -Meteor.startup(function() { - settings.add('GoogleNaturalLanguage_Enabled', false, { - type: 'boolean', - group: 'Message', - section: 'Google Natural Language', - public: true, - i18nLabel: 'Enabled', - }); - settings.add('GoogleNaturalLanguage_ServiceAccount', '', { - type: 'string', - group: 'Message', - section: 'Google Natural Language', - multiline: true, - enableQuery: { - _id: 'GoogleNaturalLanguage_Enabled', - value: true, - }, - i18nLabel: 'Service_account_key', - }); -}); diff --git a/packages/rocketchat-google-natural-language/server/settings.ts b/packages/rocketchat-google-natural-language/server/settings.ts new file mode 100644 index 000000000000..a7fd00635601 --- /dev/null +++ b/packages/rocketchat-google-natural-language/server/settings.ts @@ -0,0 +1,20 @@ +import { settingsRegistry } from '../../../app/settings/server'; + +settingsRegistry.add('GoogleNaturalLanguage_Enabled', false, { + type: 'boolean', + group: 'Message', + section: 'Google Natural Language', + public: true, + i18nLabel: 'Enabled', +}); +settingsRegistry.add('GoogleNaturalLanguage_ServiceAccount', '', { + type: 'string', + group: 'Message', + section: 'Google Natural Language', + multiline: true, + enableQuery: { + _id: 'GoogleNaturalLanguage_Enabled', + value: true, + }, + i18nLabel: 'Service_account_key', +}); diff --git a/server/configuration/ldap.ts b/server/configuration/ldap.ts index 7c170015108a..ea5f3547c95f 100644 --- a/server/configuration/ldap.ts +++ b/server/configuration/ldap.ts @@ -16,7 +16,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest: Record // Prevent password logins by LDAP users when LDAP is enabled let ldapEnabled: boolean; -settings.get('LDAP_Enable', (_key, value) => { +settings.watch('LDAP_Enable', (value) => { if (ldapEnabled === value) { return; } diff --git a/server/cron/federation.ts b/server/cron/federation.ts index beff2b253b97..89acc76beabf 100644 --- a/server/cron/federation.ts +++ b/server/cron/federation.ts @@ -1,22 +1,22 @@ import { resolveSRV, resolveTXT } from '../../app/federation/server/functions/resolveDNS'; -import { settings } from '../../app/settings/server'; +import { settings, settingsRegistry } from '../../app/settings/server'; import { SettingValue } from '../../definition/ISetting'; import { dispatchEvent } from '../../app/federation/server/handler'; import { getFederationDomain } from '../../app/federation/server/lib/getFederationDomain'; import { eventTypes } from '../../app/models/server/models/FederationEvents'; -import { Users } from '../../app/models/server/raw'; +import { Users, Settings } from '../../app/models/server/raw'; function updateSetting(id: string, value: SettingValue | null): void { if (value !== null) { const setting = settings.get(id); if (setting === undefined) { - settings.add(id, value); + settingsRegistry.add(id, value); } else { - settings.updateById(id, value); + Settings.updateValueById(id, value); } } else { - settings.clearById(id); + Settings.updateValueById(id, undefined); } } @@ -75,11 +75,16 @@ async function runFederation(): Promise { } export function federationCron(SyncedCron: any): void { - SyncedCron.add({ - name: 'Federation', - schedule(parser: any) { - return parser.cron('* * * * *'); - }, - job: runFederation, + settings.watch('FEDERATION_Enabled', (value) => { + if (!value) { + return SyncedCron.remove('Federation'); + } + SyncedCron.add({ + name: 'Federation', + schedule(parser: any) { + return parser.cron('* * * * *'); + }, + job: runFederation, + }); }); } diff --git a/server/cron/statistics.js b/server/cron/statistics.js index eb071e11688f..7c51e51963cf 100644 --- a/server/cron/statistics.js +++ b/server/cron/statistics.js @@ -3,7 +3,7 @@ import { HTTP } from 'meteor/http'; import { getWorkspaceAccessToken } from '../../app/cloud/server'; import { statistics } from '../../app/statistics'; -import { settings } from '../../app/settings'; +import { settings } from '../../app/settings/server'; function generateStatistics(logger) { const cronStatistics = statistics.save(); @@ -40,7 +40,7 @@ export function statsCron(SyncedCron, logger) { const name = 'Generate and save statistics'; let previousValue; - settings.get('Troubleshoot_Disable_Statistics_Generator', (key, value) => { + settings.watch('Troubleshoot_Disable_Statistics_Generator', (value) => { if (value === previousValue) { return; } diff --git a/server/lib/logger/startup.ts b/server/lib/logger/startup.ts index a0f57049baed..97d18252cea3 100644 --- a/server/lib/logger/startup.ts +++ b/server/lib/logger/startup.ts @@ -2,13 +2,13 @@ import { settings } from '../../../app/settings/server'; import { logLevel, LogLevelSetting } from './logLevel'; import { setQueueLimit } from './logQueue'; -settings.get('Log_Level', (_key, value) => { +settings.watch('Log_Level', (value) => { if (value != null) { logLevel.emit('changed', String(value) as LogLevelSetting); } }); -settings.get('Log_View_Limit', (_key, value) => { +settings.watch('Log_View_Limit', (value) => { if (typeof value === 'number') { setQueueLimit(value); } diff --git a/server/lib/migrations.ts b/server/lib/migrations.ts index 605e5ee98f3c..f83db8d418dd 100644 --- a/server/lib/migrations.ts +++ b/server/lib/migrations.ts @@ -270,3 +270,7 @@ export function migrateDatabase(targetVersion: 'latest' | number, subcommands?: return true; } + +export const onFreshInstall = getControl().version !== 0 + ? (): void => { /* noop */ } + : (fn: () => unknown): unknown => fn(); diff --git a/server/main.js b/server/main.js index cc4bf7b42da7..5ebfe2b3c8f4 100644 --- a/server/main.js +++ b/server/main.js @@ -1,4 +1,5 @@ import '../ee/server/broker'; +import '../app/settings/server/startup'; import './lib/logger/startup'; import './importPackages'; import '../imports/startup/server'; diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index 61d96a605f64..a1c420eb69a1 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -3,6 +3,7 @@ import { NotificationsModule } from '../notifications/notifications.module'; import { EnterpriseSettings, MeteorService } from '../../sdk/index'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; import { UserStatus } from '../../../definition/UserStatus'; +import { isSettingColor } from '../../../definition/ISetting'; const STATUS_MAP: {[k: string]: number} = { [UserStatus.OFFLINE]: 0, @@ -186,8 +187,8 @@ export class ListenersModule { if (clientAction !== 'removed') { const result = await EnterpriseSettings.changeSettingValue(setting); - if (result && !(result instanceof Error) && result.hasOwnProperty('value')) { - setting.value = result.value; + if (result !== undefined && !(result instanceof Error)) { + setting.value = result; } } @@ -198,7 +199,7 @@ export class ListenersModule { const value = { _id: setting._id, value: setting.value, - editor: setting.editor, + ...isSettingColor(setting) && { editor: setting.editor }, properties: setting.properties, enterprise: setting.enterprise, requiredOnWizard: setting.requiredOnWizard, diff --git a/server/publications/settings/index.js b/server/publications/settings/index.js index e14f2079ca30..3e9db1beab85 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Settings } from '../../../app/models/server'; import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/server'; import { getSettingPermissionId } from '../../../app/authorization/lib'; -import { SettingsEvents } from '../../../app/settings/server/functions/settings'; +import { SettingsEvents } from '../../../app/settings/server'; Meteor.methods({ 'public-settings/get'(updatedAt) { diff --git a/server/sdk/types/IEnterpriseSettings.ts b/server/sdk/types/IEnterpriseSettings.ts index 5fa2144c2d96..0eaae40d76a8 100644 --- a/server/sdk/types/IEnterpriseSettings.ts +++ b/server/sdk/types/IEnterpriseSettings.ts @@ -2,5 +2,5 @@ import { IServiceClass } from './ServiceClass'; import { ISetting } from '../../../definition/ISetting'; export interface IEnterpriseSettings extends IServiceClass { - changeSettingValue(record: ISetting): undefined | { value: ISetting['value'] }; + changeSettingValue(record: ISetting): undefined | ISetting['value']; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 473ea38be03a..ae182e3efd57 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -22,6 +22,7 @@ import { ListenersModule, minimongoChangeMap } from '../../modules/listeners/lis import notifications from '../../../app/notifications/server/lib/Notifications'; import { configureEmailInboxes } from '../../features/EmailInbox/EmailInbox'; import { isPresenceMonitorEnabled } from '../../lib/isPresenceMonitorEnabled'; +import { use } from '../../../app/settings/server/Middleware'; const autoUpdateRecords = new Map(); @@ -113,6 +114,12 @@ if (disableOplog) { }; } +settings.set = use(settings.set, (context, next) => { + next(...context); + const [record] = context; + updateValue(record._id, record); +}); + export class MeteorService extends ServiceClass implements IMeteor { protected name = 'meteor'; @@ -125,12 +132,11 @@ export class MeteorService extends ServiceClass implements IMeteor { this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { if (clientAction !== 'removed') { - settings.storeSettingValue(setting, false); - updateValue(setting._id, { value: setting.value }); + settings.set(setting); return; } - settings.removeSettingValue(setting, false); + settings.set({ ...setting, value: undefined }); setValue(setting._id, undefined); }); diff --git a/server/settings/ldap.ts b/server/settings/ldap.ts index cb9d235634ae..ef667bcc8842 100644 --- a/server/settings/ldap.ts +++ b/server/settings/ldap.ts @@ -1,11 +1,11 @@ -import { settings } from '../../app/settings/server'; +import { settingsRegistry } from '../../app/settings/server'; -settings.addGroup('LDAP', function() { +settingsRegistry.addGroup('LDAP', function() { const enableQuery = { _id: 'LDAP_Enable', value: true }; const adOnly = { _id: 'LDAP_Server_Type', value: 'ad' }; const ldapOnly = { _id: 'LDAP_Server_Type', value: '' }; - this.set({ tab: 'LDAP_Connection' }, function() { + this.with({ tab: 'LDAP_Connection' }, function() { this.add('LDAP_Enable', false, { type: 'boolean', public: true }); this.add('LDAP_Server_Type', 'ad', { @@ -62,7 +62,7 @@ settings.addGroup('LDAP', function() { }); }); - this.set({ tab: 'LDAP_UserSearch' }, function() { + this.with({ tab: 'LDAP_UserSearch' }, function() { this.add('LDAP_Find_User_After_Login', true, { type: 'boolean', enableQuery }); this.section('LDAP_UserSearch_Filter', function() { @@ -119,7 +119,7 @@ settings.addGroup('LDAP', function() { }); }); - this.set({ tab: 'LDAP_DataSync' }, function() { + this.with({ tab: 'LDAP_DataSync' }, function() { this.add('LDAP_Unique_Identifier_Field', 'objectGUID,ibm-entryUUID,GUID,dominoUNID,nsuniqueId,uidNumber,uid', { type: 'string', enableQuery, diff --git a/server/startup/initialData.js b/server/startup/initialData.js index fe47e2d6828a..7db252c92b03 100644 --- a/server/startup/initialData.js +++ b/server/startup/initialData.js @@ -10,193 +10,191 @@ import { settings } from '../../app/settings'; import { checkUsernameAvailability, addUserToDefaultChannels } from '../../app/lib'; Meteor.startup(function() { - Meteor.defer(() => { - if (settings.get('Show_Setup_Wizard') === 'pending' && !Rooms.findOneById('GENERAL')) { - Rooms.createWithIdTypeAndName('GENERAL', 'c', 'general', { - default: true, - }); - } - - if (!Users.findOneById('rocket.cat')) { - Users.create({ - _id: 'rocket.cat', - name: 'Rocket.Cat', - username: 'rocket.cat', - status: 'online', + if (settings.get('Show_Setup_Wizard') === 'pending' && !Rooms.findOneById('GENERAL')) { + Rooms.createWithIdTypeAndName('GENERAL', 'c', 'general', { + default: true, + }); + } + + if (!Users.findOneById('rocket.cat')) { + Users.create({ + _id: 'rocket.cat', + name: 'Rocket.Cat', + username: 'rocket.cat', + status: 'online', + statusDefault: 'online', + utcOffset: 0, + active: true, + type: 'bot', + }); + + addUserRoles('rocket.cat', 'bot'); + + const buffer = Buffer.from(Assets.getBinary('avatars/rocketcat.png')); + + const rs = RocketChatFile.bufferToStream(buffer, 'utf8'); + const fileStore = FileUpload.getStore('Avatars'); + fileStore.deleteByName('rocket.cat'); + + const file = { + userId: 'rocket.cat', + type: 'image/png', + size: buffer.length, + }; + + Meteor.runAsUser('rocket.cat', () => { + fileStore.insert(file, rs, () => Users.setAvatarData('rocket.cat', 'local', null)); + }); + } + + if (process.env.ADMIN_PASS) { + if (_.isEmpty(getUsersInRole('admin').fetch())) { + console.log('Inserting admin user:'.green); + const adminUser = { + name: 'Administrator', + username: 'admin', + status: 'offline', statusDefault: 'online', utcOffset: 0, active: true, - type: 'bot', - }); - - addUserRoles('rocket.cat', 'bot'); - - const buffer = Buffer.from(Assets.getBinary('avatars/rocketcat.png')); - - const rs = RocketChatFile.bufferToStream(buffer, 'utf8'); - const fileStore = FileUpload.getStore('Avatars'); - fileStore.deleteByName('rocket.cat'); - - const file = { - userId: 'rocket.cat', - type: 'image/png', - size: buffer.length, }; - Meteor.runAsUser('rocket.cat', () => { - fileStore.insert(file, rs, () => Users.setAvatarData('rocket.cat', 'local', null)); - }); - } - - if (process.env.ADMIN_PASS) { - if (_.isEmpty(getUsersInRole('admin').fetch())) { - console.log('Inserting admin user:'.green); - const adminUser = { - name: 'Administrator', - username: 'admin', - status: 'offline', - statusDefault: 'online', - utcOffset: 0, - active: true, - }; - - if (process.env.ADMIN_NAME) { - adminUser.name = process.env.ADMIN_NAME; - } + if (process.env.ADMIN_NAME) { + adminUser.name = process.env.ADMIN_NAME; + } - console.log(`Name: ${ adminUser.name }`.green); + console.log(`Name: ${ adminUser.name }`.green); - if (process.env.ADMIN_EMAIL) { - const re = /^[^@].*@[^@]+$/i; + if (process.env.ADMIN_EMAIL) { + const re = /^[^@].*@[^@]+$/i; - if (re.test(process.env.ADMIN_EMAIL)) { - if (!Users.findOneByEmailAddress(process.env.ADMIN_EMAIL)) { - adminUser.emails = [{ - address: process.env.ADMIN_EMAIL, - verified: process.env.ADMIN_EMAIL_VERIFIED === 'true', - }]; + if (re.test(process.env.ADMIN_EMAIL)) { + if (!Users.findOneByEmailAddress(process.env.ADMIN_EMAIL)) { + adminUser.emails = [{ + address: process.env.ADMIN_EMAIL, + verified: process.env.ADMIN_EMAIL_VERIFIED === 'true', + }]; - console.log(`Email: ${ process.env.ADMIN_EMAIL }`.green); - } else { - console.log('Email provided already exists; Ignoring environment variables ADMIN_EMAIL'.red); - } + console.log(`Email: ${ process.env.ADMIN_EMAIL }`.green); } else { - console.log('Email provided is invalid; Ignoring environment variables ADMIN_EMAIL'.red); + console.log('Email provided already exists; Ignoring environment variables ADMIN_EMAIL'.red); } + } else { + console.log('Email provided is invalid; Ignoring environment variables ADMIN_EMAIL'.red); } + } - if (process.env.ADMIN_USERNAME) { - let nameValidation; + if (process.env.ADMIN_USERNAME) { + let nameValidation; - try { - nameValidation = new RegExp(`^${ settings.get('UTF8_User_Names_Validation') }$`); - } catch (error) { - nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$'); - } + try { + nameValidation = new RegExp(`^${ settings.get('UTF8_User_Names_Validation') }$`); + } catch (error) { + nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$'); + } - if (nameValidation.test(process.env.ADMIN_USERNAME)) { - if (checkUsernameAvailability(process.env.ADMIN_USERNAME)) { - adminUser.username = process.env.ADMIN_USERNAME; - } else { - console.log('Username provided already exists; Ignoring environment variables ADMIN_USERNAME'.red); - } + if (nameValidation.test(process.env.ADMIN_USERNAME)) { + if (checkUsernameAvailability(process.env.ADMIN_USERNAME)) { + adminUser.username = process.env.ADMIN_USERNAME; } else { - console.log('Username provided is invalid; Ignoring environment variables ADMIN_USERNAME'.red); + console.log('Username provided already exists; Ignoring environment variables ADMIN_USERNAME'.red); } + } else { + console.log('Username provided is invalid; Ignoring environment variables ADMIN_USERNAME'.red); } + } - console.log(`Username: ${ adminUser.username }`.green); + console.log(`Username: ${ adminUser.username }`.green); - adminUser.type = 'user'; + adminUser.type = 'user'; - const id = Users.create(adminUser); + const id = Users.create(adminUser); - Accounts.setPassword(id, process.env.ADMIN_PASS); + Accounts.setPassword(id, process.env.ADMIN_PASS); - addUserRoles(id, 'admin'); - } else { - console.log('Users with admin role already exist; Ignoring environment variables ADMIN_PASS'.red); - } + addUserRoles(id, 'admin'); + } else { + console.log('Users with admin role already exist; Ignoring environment variables ADMIN_PASS'.red); } - - if (typeof process.env.INITIAL_USER === 'string' && process.env.INITIAL_USER.length > 0) { - try { - const initialUser = JSON.parse(process.env.INITIAL_USER); - - if (!initialUser._id) { - console.log('No _id provided; Ignoring environment variable INITIAL_USER'.red); - } else if (!Users.findOneById(initialUser._id)) { - console.log('Inserting initial user:'.green); - console.log(JSON.stringify(initialUser, null, 2).green); - Users.create(initialUser); - } - } catch (e) { - console.log('Error processing environment variable INITIAL_USER'.red, e); + } + + if (typeof process.env.INITIAL_USER === 'string' && process.env.INITIAL_USER.length > 0) { + try { + const initialUser = JSON.parse(process.env.INITIAL_USER); + + if (!initialUser._id) { + console.log('No _id provided; Ignoring environment variable INITIAL_USER'.red); + } else if (!Users.findOneById(initialUser._id)) { + console.log('Inserting initial user:'.green); + console.log(JSON.stringify(initialUser, null, 2).green); + Users.create(initialUser); } + } catch (e) { + console.log('Error processing environment variable INITIAL_USER'.red, e); } + } - if (_.isEmpty(getUsersInRole('admin').fetch())) { - const oldestUser = Users.getOldest({ _id: 1, username: 1, name: 1 }); + if (_.isEmpty(getUsersInRole('admin').fetch())) { + const oldestUser = Users.getOldest({ _id: 1, username: 1, name: 1 }); - if (oldestUser) { - addUserRoles(oldestUser._id, 'admin'); - console.log(`No admins are found. Set ${ oldestUser.username || oldestUser.name } as admin for being the oldest user`); - } + if (oldestUser) { + addUserRoles(oldestUser._id, 'admin'); + console.log(`No admins are found. Set ${ oldestUser.username || oldestUser.name } as admin for being the oldest user`); } + } - if (!_.isEmpty(getUsersInRole('admin').fetch())) { - if (settings.get('Show_Setup_Wizard') === 'pending') { - console.log('Setting Setup Wizard to "in_progress" because, at least, one admin was found'); - Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); - } + if (!_.isEmpty(getUsersInRole('admin').fetch())) { + if (settings.get('Show_Setup_Wizard') === 'pending') { + console.log('Setting Setup Wizard to "in_progress" because, at least, one admin was found'); + Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + } + } + + Users.removeById('rocketchat.internal.admin.test'); + + if (process.env.TEST_MODE === 'true') { + console.log('Inserting admin test user:'.green); + + const adminUser = { + _id: 'rocketchat.internal.admin.test', + name: 'RocketChat Internal Admin Test', + username: 'rocketchat.internal.admin.test', + emails: [ + { + address: 'rocketchat.internal.admin.test@rocket.chat', + verified: false, + }, + ], + status: 'offline', + statusDefault: 'online', + utcOffset: 0, + active: true, + type: 'user', + }; + + console.log(`Name: ${ adminUser.name }`.green); + console.log(`Email: ${ adminUser.emails[0].address }`.green); + console.log(`Username: ${ adminUser.username }`.green); + console.log(`Password: ${ adminUser._id }`.green); + + if (Users.findOneByEmailAddress(adminUser.emails[0].address)) { + throw new Meteor.Error(`Email ${ adminUser.emails[0].address } already exists`, 'Rocket.Chat can\'t run in test mode'); } - Users.removeById('rocketchat.internal.admin.test'); - - if (process.env.TEST_MODE === 'true') { - console.log('Inserting admin test user:'.green); - - const adminUser = { - _id: 'rocketchat.internal.admin.test', - name: 'RocketChat Internal Admin Test', - username: 'rocketchat.internal.admin.test', - emails: [ - { - address: 'rocketchat.internal.admin.test@rocket.chat', - verified: false, - }, - ], - status: 'offline', - statusDefault: 'online', - utcOffset: 0, - active: true, - type: 'user', - }; - - console.log(`Name: ${ adminUser.name }`.green); - console.log(`Email: ${ adminUser.emails[0].address }`.green); - console.log(`Username: ${ adminUser.username }`.green); - console.log(`Password: ${ adminUser._id }`.green); - - if (Users.findOneByEmailAddress(adminUser.emails[0].address)) { - throw new Meteor.Error(`Email ${ adminUser.emails[0].address } already exists`, 'Rocket.Chat can\'t run in test mode'); - } - - if (!checkUsernameAvailability(adminUser.username)) { - throw new Meteor.Error(`Username ${ adminUser.username } already exists`, 'Rocket.Chat can\'t run in test mode'); - } - - Users.create(adminUser); + if (!checkUsernameAvailability(adminUser.username)) { + throw new Meteor.Error(`Username ${ adminUser.username } already exists`, 'Rocket.Chat can\'t run in test mode'); + } - Accounts.setPassword(adminUser._id, adminUser._id); + Users.create(adminUser); - addUserRoles(adminUser._id, 'admin'); + Accounts.setPassword(adminUser._id, adminUser._id); - if (settings.get('Show_Setup_Wizard') === 'pending') { - Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); - } + addUserRoles(adminUser._id, 'admin'); - return addUserToDefaultChannels(adminUser, true); + if (settings.get('Show_Setup_Wizard') === 'pending') { + Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); } - }); + + return addUserToDefaultChannels(adminUser, true); + } }); diff --git a/server/startup/migrations/v238.ts b/server/startup/migrations/v238.ts index 5310898b56d7..1abd67a5dc01 100644 --- a/server/startup/migrations/v238.ts +++ b/server/startup/migrations/v238.ts @@ -36,12 +36,12 @@ addMigration({ up() { Apps.initialize(); - const apps = Apps._model.find().fetch(); + const apps = Apps._model?.find().fetch(); for (const app of apps) { const zipFile = isPreCompilerRemoval(app) ? repackageAppZip(app) : Buffer.from(app.zip, 'base64'); Promise.await((Apps._manager as AppManager).update(zipFile, app.permissionsGranted, { loadApp: false })); - Promise.await(Apps._model.update({ id: app.id }, { $unset: { zip: 1, compiled: 1 } })); + Promise.await(Apps._model?.update({ id: app.id }, { $unset: { zip: 1, compiled: 1 } })); } }, }); diff --git a/server/startup/migrations/xrun.js b/server/startup/migrations/xrun.js index 17c91e34f282..7e96744fd0fe 100644 --- a/server/startup/migrations/xrun.js +++ b/server/startup/migrations/xrun.js @@ -1,4 +1,5 @@ -import { migrateDatabase } from '../../lib/migrations'; +import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions'; +import { migrateDatabase, onFreshInstall } from '../../lib/migrations'; const { MIGRATION_VERSION = 'latest', @@ -7,3 +8,4 @@ const { const [version, ...subcommands] = MIGRATION_VERSION.split(','); migrateDatabase(version === 'latest' ? version : parseInt(version), subcommands); +onFreshInstall(upsertPermissions); diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index a1329f19da77..4c94ccc59ab4 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -6,7 +6,7 @@ import { DDP } from 'meteor/ddp'; import { Logger } from '../lib/logger/Logger'; import { hasPermission } from '../../app/authorization'; -import { settings } from '../../app/settings'; +import { settings } from '../../app/settings/server'; import { isDocker, getURL } from '../../app/utils'; import { Users } from '../../app/models/server'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; @@ -201,7 +201,7 @@ export function startStreamBroadcast() { logger.info('startStreamBroadcast'); - settings.get('Stream_Cast_Address', function(key, value) { + settings.watch('Stream_Cast_Address', function(value) { // var connection, fn, instance; const fn = function(instance, connection) { connection.disconnect(); @@ -272,7 +272,7 @@ export function startStreamBroadcast() { const onBroadcast = Meteor.bindEnvironment(broadcast); let TroubleshootDisableInstanceBroadcast; - settings.get('Troubleshoot_Disable_Instance_Broadcast', (key, value) => { + settings.watch('Troubleshoot_Disable_Instance_Broadcast', (value) => { if (TroubleshootDisableInstanceBroadcast === value) { return; } TroubleshootDisableInstanceBroadcast = value;