[FIX] Uncessary updates on Settings, Roles and Permissions on startup (#17160)

pull/17463/head^2
Rodrigo Nascimento 6 years ago committed by GitHub
parent 78c9a31116
commit b3f2ce7ce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/api/server/api.js
  2. 2
      app/api/server/v1/chat.js
  3. 2
      app/api/server/v1/permissions.js
  4. 2
      app/api/server/v1/settings.js
  5. 2
      app/apps/server/orchestrator.js
  6. 23
      app/authorization/server/startup.js
  7. 4
      app/channel-settings-mail-messages/server/lib/startup.js
  8. 6
      app/channel-settings/server/startup.js
  9. 2
      app/cloud/server/index.js
  10. 2
      app/custom-sounds/server/startup/permissions.js
  11. 4
      app/discussion/server/permissions.js
  12. 2
      app/lib/server/functions/createDirectRoom.js
  13. 2
      app/lib/server/startup/settings.js
  14. 7
      app/mail-messages/server/startup.js
  15. 6
      app/message-pin/server/settings.js
  16. 6
      app/message-snippet/server/startup/settings.js
  17. 23
      app/models/server/models/Permissions.js
  18. 29
      app/models/server/models/Roles.js
  19. 0
      app/settings/client/index.ts
  20. 46
      app/settings/client/lib/settings.js
  21. 50
      app/settings/client/lib/settings.ts
  22. 4
      app/settings/index.js
  23. 88
      app/settings/lib/settings.js
  24. 115
      app/settings/lib/settings.ts
  25. 4
      app/settings/server/functions/settings.d.ts
  26. 309
      app/settings/server/functions/settings.js
  27. 33
      app/settings/server/functions/settings.mocks.ts
  28. 319
      app/settings/server/functions/settings.tests.ts
  29. 364
      app/settings/server/functions/settings.ts
  30. 0
      app/settings/server/index.ts
  31. 16
      app/ui-cached-collection/client/models/CachedCollection.js
  32. 12
      client/main.d.ts
  33. 6
      ee/app/auditing/server/index.js
  34. 6
      ee/app/canned-responses/server/permissions.js
  35. 8
      ee/app/livechat-enterprise/server/permissions.js
  36. 4
      mocha.opts
  37. 76
      package-lock.json
  38. 20
      package.json
  39. 3
      tsconfig.json

@ -374,7 +374,7 @@ export class APIClass extends Restivus {
'error-unauthorized': 'unauthorized',
}[e.error] || 'failure';
result = API.v1[apiMethod](typeof e === 'string' ? e : e.message, e.error, undefined, e);
result = API.v1[apiMethod](typeof e === 'string' ? e : e.message, e.error, process.env.TEST_MODE ? e.stack : undefined, e);
} finally {
delete Accounts._accountData[connection.id];
}

@ -4,7 +4,7 @@ import { Match, check } from 'meteor/check';
import { Messages } from '../../../models';
import { canAccessRoom, hasPermission } from '../../../authorization';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { processWebhookMessage } from '../../../lib';
import { processWebhookMessage } from '../../../lib/server';
import { API } from '../api';
import Rooms from '../../../models/server/models/Rooms';
import Users from '../../../models/server/models/Users';

@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { hasPermission } from '../../../authorization';
import { Permissions, Roles } from '../../../models';
import { Permissions, Roles } from '../../../models/server';
import { API } from '../api';
/**

@ -3,7 +3,7 @@ import { Match, check } from 'meteor/check';
import { ServiceConfiguration } from 'meteor/service-configuration';
import _ from 'underscore';
import { Settings } from '../../../models';
import { Settings } from '../../../models/server';
import { hasPermission } from '../../../authorization';
import { API } from '../api';

@ -24,7 +24,7 @@ class AppServerOrchestrator {
initialize() {
this._rocketchatLogger = new Logger('Rocket.Chat Apps');
Permissions.createOrUpdate('manage-apps', ['admin']);
Permissions.create('manage-apps', ['admin']);
this._marketplaceUrl = 'https://marketplace.rocket.chat';

@ -1,7 +1,7 @@
/* eslint no-multi-spaces: 0 */
import { Meteor } from 'meteor/meteor';
import { Roles, Permissions, Settings } from '../../models';
import { Roles, Permissions, Settings } from '../../models/server';
import { settings } from '../../settings/server';
import { getSettingPermissionId, CONSTANTS } from '../lib';
import { clearCache } from './functions/hasPermission';
@ -114,9 +114,7 @@ Meteor.startup(function() {
];
for (const permission of permissions) {
if (!Permissions.findOneById(permission._id)) {
Permissions.upsert(permission._id, { $set: permission });
}
Permissions.create(permission._id, permission.roles);
}
const defaultRoles = [
@ -134,7 +132,7 @@ Meteor.startup(function() {
];
for (const role of defaultRoles) {
Roles.upsert({ _id: role.name }, { $setOnInsert: { scope: role.scope, description: role.description || '', protected: true, mandatory2fa: false } });
Roles.createOrUpdate(role.name, role.scope, role.description, true, false);
}
const getPreviousPermissions = function(settingId) {
@ -155,19 +153,17 @@ Meteor.startup(function() {
const createSettingPermission = function(setting, previousSettingPermissions) {
const permissionId = getSettingPermissionId(setting._id);
const permission = {
_id: permissionId,
level: CONSTANTS.SETTINGS_LEVEL,
// copy those setting-properties which are needed to properly publish the setting-based permissions
settingId: setting._id,
group: setting.group,
section: setting.section,
sorter: setting.sorter,
roles: [],
};
// copy previously assigned roles if available
if (previousSettingPermissions[permissionId] && previousSettingPermissions[permissionId].roles) {
permission.roles = previousSettingPermissions[permissionId].roles;
} else {
permission.roles = [];
}
if (setting.group) {
permission.groupPermissionId = getSettingPermissionId(setting.group);
@ -175,7 +171,16 @@ Meteor.startup(function() {
if (setting.section) {
permission.sectionPermissionId = getSettingPermissionId(setting.section);
}
Permissions.upsert(permission._id, { $set: permission });
const existent = Permissions.findOne({
_id: permissionId,
...permission,
}, { fields: { _id: 1 } });
if (!existent) {
Permissions.upsert({ _id: permissionId }, { $set: permission });
}
delete previousSettingPermissions[permissionId];
};

@ -7,7 +7,5 @@ Meteor.startup(function() {
_id: 'mail-messages',
roles: ['admin'],
};
return Permissions.upsert(permission._id, {
$setOnInsert: permission,
});
return Permissions.create(permission._id, permission.roles);
});

@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
import { Permissions } from '../../models';
Meteor.startup(function() {
Permissions.upsert('post-readonly', { $setOnInsert: { roles: ['admin', 'owner', 'moderator'] } });
Permissions.upsert('set-readonly', { $setOnInsert: { roles: ['admin', 'owner'] } });
Permissions.upsert('set-react-when-readonly', { $setOnInsert: { roles: ['admin', 'owner'] } });
Permissions.create('post-readonly', ['admin', 'owner', 'moderator']);
Permissions.create('set-readonly', ['admin', 'owner']);
Permissions.create('set-react-when-readonly', ['admin', 'owner']);
});

@ -11,7 +11,7 @@ import { Permissions } from '../../models';
import { settings } from '../../settings/server';
if (Permissions) {
Permissions.createOrUpdate('manage-cloud', ['admin']);
Permissions.create('manage-cloud', ['admin']);
}
const licenseCronName = 'Cloud Workspace Sync';

@ -4,6 +4,6 @@ import { Permissions } from '../../../models';
Meteor.startup(() => {
if (Permissions) {
Permissions.createOrUpdate('manage-sounds', ['admin']);
Permissions.create('manage-sounds', ['admin']);
}
});

@ -10,8 +10,6 @@ Meteor.startup(() => {
];
for (const permission of permissions) {
if (!Permissions.findOneById(permission._id)) {
Permissions.upsert(permission._id, { $set: permission });
}
Permissions.create(permission._id, permission.roles);
}
});

@ -1,5 +1,5 @@
import { Rooms, Subscriptions } from '../../../models/server';
import { settings } from '../../../settings/lib/settings';
import { settings } from '../../../settings/server';
import { getDefaultSubscriptionPref } from '../../../utils/server';
import { callbacks } from '../../../callbacks/server';

@ -2756,7 +2756,7 @@ settings.addGroup('Setup_Wizard', function() {
secret: true,
});
this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(), {
this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(0), {
type: 'date',
hidden: true,
readonly: true,

@ -3,10 +3,5 @@ import { Meteor } from 'meteor/meteor';
import { Permissions } from '../../models';
Meteor.startup(function() {
return Permissions.upsert('access-mailer', {
$setOnInsert: {
_id: 'access-mailer',
roles: ['admin'],
},
});
return Permissions.create('access-mailer', ['admin']);
});

@ -9,9 +9,5 @@ Meteor.startup(function() {
group: 'Message',
public: true,
});
return Permissions.upsert('pin-message', {
$setOnInsert: {
roles: ['owner', 'moderator', 'admin'],
},
});
return Permissions.create('pin-message', ['owner', 'moderator', 'admin']);
});

@ -9,9 +9,5 @@ Meteor.startup(function() {
public: true,
group: 'Message',
});
Permissions.upsert('snippet-message', {
$setOnInsert: {
roles: ['owner', 'moderator', 'admin'],
},
});
Permissions.create('snippet-message', ['owner', 'moderator', 'admin']);
});

@ -15,15 +15,34 @@ export class Permissions extends Base {
}
createOrUpdate(name, roles) {
const exists = this.findOne({
_id: name,
roles,
}, { fields: { _id: 1 } });
if (exists) {
return exists._id;
}
this.upsert({ _id: name }, { $set: { roles } });
}
create(name, roles) {
const exists = this.findOneById(name, { fields: { _id: 1 } });
if (exists) {
return exists._id;
}
this.upsert({ _id: name }, { $set: { roles } });
}
addRole(permission, role) {
this.update({ _id: permission }, { $addToSet: { roles: role } });
this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } });
}
removeRole(permission, role) {
this.update({ _id: permission }, { $pull: { roles: role } });
this.update({ _id: permission, roles: role }, { $pull: { roles: role } });
}
}

@ -29,21 +29,26 @@ export class Roles extends Base {
});
}
createOrUpdate(name, scope = 'Users', description, protectedRole, mandatory2fa) {
const updateData = {};
updateData.name = name;
updateData.scope = scope;
createOrUpdate(name, scope = 'Users', description = '', protectedRole = true, mandatory2fa = false) {
const queryData = {
name,
scope,
protected: protectedRole,
};
if (description != null) {
updateData.description = description;
}
const updateData = {
...queryData,
description,
mandatory2fa,
};
if (protectedRole) {
updateData.protected = protectedRole;
}
const exists = this.findOne({
_id: name,
...queryData,
}, { fields: { _id: 1 } });
if (mandatory2fa != null) {
updateData.mandatory2fa = mandatory2fa;
if (exists) {
return exists._id;
}
this.upsert({ _id: name }, { $set: updateData });

@ -1,46 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveDict } from 'meteor/reactive-dict';
import { CachedCollection } from '../../../ui-cached-collection';
import { settings } from '../../lib/settings';
settings.cachedCollection = new CachedCollection({
name: 'public-settings',
eventType: 'onAll',
userRelated: false,
listenChangesForLoggedUsersOnly: true,
});
settings.collection = settings.cachedCollection.collection;
settings.dict = new ReactiveDict('settings');
settings.get = function(_id) {
return settings.dict.get(_id);
};
settings.init = function() {
let initialLoad = true;
settings.collection.find().observe({
added(record) {
Meteor.settings[record._id] = record.value;
settings.dict.set(record._id, record.value);
settings.load(record._id, record.value, initialLoad);
},
changed(record) {
Meteor.settings[record._id] = record.value;
settings.dict.set(record._id, record.value);
settings.load(record._id, record.value, initialLoad);
},
removed(record) {
delete Meteor.settings[record._id];
settings.dict.set(record._id, null);
settings.load(record._id, null, initialLoad);
},
});
initialLoad = false;
};
settings.init();
export { settings };

@ -0,0 +1,50 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveDict } from 'meteor/reactive-dict';
import { CachedCollection } from '../../../ui-cached-collection';
import { SettingsBase, SettingValue } from '../../lib/settings';
const cachedCollection = new CachedCollection({
name: 'public-settings',
eventType: 'onAll',
userRelated: false,
listenChangesForLoggedUsersOnly: true,
});
class Settings extends SettingsBase {
cachedCollection = cachedCollection
collection = cachedCollection.collection;
dict = new ReactiveDict<any>('settings');
get(_id: string): any {
return this.dict.get(_id);
}
init(): void {
let initialLoad = true;
this.collection.find().observe({
added: (record: {_id: string; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
this.dict.set(record._id, record.value);
this.load(record._id, record.value, initialLoad);
},
changed: (record: {_id: string; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
this.dict.set(record._id, record.value);
this.load(record._id, record.value, initialLoad);
},
removed: (record: {_id: string}) => {
delete Meteor.settings[record._id];
this.dict.set(record._id, null);
this.load(record._id, undefined, initialLoad);
},
});
initialLoad = false;
}
}
export const settings = new Settings();
settings.init();

@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
if (Meteor.isClient) {
module.exports = require('./client/index.js');
module.exports = require('./client/index.ts');
}
if (Meteor.isServer) {
module.exports = require('./server/index.js');
module.exports = require('./server/index.ts');
}

@ -1,88 +0,0 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
export const settings = {
callbacks: {},
regexCallbacks: {},
ts: new Date(),
get(_id, callback) {
if (callback != null) {
settings.onload(_id, callback);
if (!Meteor.settings) {
return;
}
if (_id === '*') {
return Object.keys(Meteor.settings).forEach((key) => {
const value = Meteor.settings[key];
callback(key, value);
});
}
if (_.isRegExp(_id) && Meteor.settings) {
return Object.keys(Meteor.settings).forEach((key) => {
if (!_id.test(key)) {
return;
}
const value = Meteor.settings[key];
callback(key, value);
});
}
return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]);
}
if (!Meteor.settings) {
return;
}
if (_.isRegExp(_id)) {
return Object.keys(Meteor.settings).reduce((items, key) => {
const value = Meteor.settings[key];
if (_id.test(key)) {
items.push({
key,
value,
});
}
return items;
}, []);
}
return Meteor.settings && Meteor.settings[_id];
},
set(_id, value, callback) {
return Meteor.call('saveSetting', _id, value, callback);
},
batchSet(settings, callback) {
return Meteor.call('saveSettings', settings, callback);
},
load(key, value, initialLoad) {
['*', key].forEach((item) => {
if (settings.callbacks[item]) {
settings.callbacks[item].forEach((callback) => callback(key, value, initialLoad));
}
});
Object.keys(settings.regexCallbacks).forEach((cbKey) => {
const cbValue = settings.regexCallbacks[cbKey];
if (!cbValue.regex.test(key)) {
return;
}
cbValue.callbacks.forEach((callback) => callback(key, value, initialLoad));
});
},
onload(key, callback) {
// if key is '*'
// for key, value in Meteor.settings
// callback key, value, false
// else if Meteor.settings?[_id]?
// callback key, Meteor.settings[_id], false
const keys = [].concat(key);
keys.forEach((k) => {
if (_.isRegExp(k)) {
settings.regexCallbacks[name = k.source] = settings.regexCallbacks[name = k.source] || {
regex: k,
callbacks: [],
};
settings.regexCallbacks[k.source].callbacks.push(callback);
} else {
settings.callbacks[k] = settings.callbacks[k] || [];
settings.callbacks[k].push(callback);
}
});
},
};

@ -0,0 +1,115 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
export type SettingValueMultiSelect = Array<{key: string; i18nLabel: string}>
export type SettingValueRoomPick = Array<{_id: string; name: string}> | string
export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined;
export type SettingComposedValue = {key: string; value: SettingValue};
export type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void;
interface ISettingRegexCallbacks {
regex: RegExp;
callbacks: SettingCallback[];
}
export class SettingsBase {
private callbacks = new Map<string, SettingCallback[]>();
private regexCallbacks = new Map<string, ISettingRegexCallbacks>();
// private ts = new Date()
public get(_id: string, callback?: SettingCallback): SettingValue | SettingComposedValue[] | void {
if (callback != null) {
this.onload(_id, callback);
if (!Meteor.settings) {
return;
}
if (_id === '*') {
return Object.keys(Meteor.settings).forEach((key) => {
const value = Meteor.settings[key];
callback(key, value);
});
}
if (_.isRegExp(_id) && Meteor.settings) {
return Object.keys(Meteor.settings).forEach((key) => {
if (!_id.test(key)) {
return;
}
const value = Meteor.settings[key];
callback(key, value);
});
}
return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]);
}
if (!Meteor.settings) {
return;
}
if (_.isRegExp(_id)) {
return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => {
const value = Meteor.settings[key];
if (_id.test(key)) {
items.push({
key,
value,
});
}
return items;
}, []);
}
return Meteor.settings && Meteor.settings[_id];
}
set(_id: string, value: SettingValue, callback: () => void): void {
Meteor.call('saveSetting', _id, value, callback);
}
batchSet(settings: Array<{_id: string; value: SettingValue}>, callback: () => void): void {
Meteor.call('saveSettings', settings, callback);
}
load(key: string, value: SettingValue, initialLoad: boolean): void {
['*', key].forEach((item) => {
const callbacks = this.callbacks.get(item);
if (callbacks) {
callbacks.forEach((callback) => callback(key, value, initialLoad));
}
});
Object.keys(this.regexCallbacks).forEach((cbKey) => {
const cbValue = this.regexCallbacks.get(cbKey);
if (!cbValue?.regex.test(key)) {
return;
}
cbValue.callbacks.forEach((callback) => callback(key, value, initialLoad));
});
}
onload(key: string | string[] | RegExp | RegExp[], callback: SettingCallback): void {
// if key is '*'
// for key, value in Meteor.settings
// callback key, value, false
// else if Meteor.settings?[_id]?
// callback key, Meteor.settings[_id], false
const keys: Array<string | RegExp> = Array.isArray(key) ? key : [key];
keys.forEach((k) => {
if (_.isRegExp(k)) {
if (!this.regexCallbacks.has(k.source)) {
this.regexCallbacks.set(k.source, {
regex: k,
callbacks: [],
});
}
this.regexCallbacks.get(k.source)?.callbacks.push(callback);
} else {
if (!this.callbacks.has(k)) {
this.callbacks.set(k, []);
}
this.callbacks.get(k)?.push(callback);
}
});
}
}

@ -1,4 +0,0 @@
export namespace settings {
export function get(name: string, callback?: (key: string, value: any) => void): string;
export function updateById(_id: string, value: any, editor?: string): number;
}

@ -1,309 +0,0 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
import { settings } from '../../lib/settings';
import Settings from '../../../models/server/models/Settings';
const blockedSettings = {};
if (process.env.SETTINGS_BLOCKED) {
process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => { blockedSettings[settingId] = 1; });
}
const hiddenSettings = {};
if (process.env.SETTINGS_HIDDEN) {
process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => { hiddenSettings[settingId] = 1; });
}
settings._sorter = {};
const overrideSetting = (_id, value, options) => {
if (typeof process !== 'undefined' && process.env && process.env[_id]) {
value = process.env[_id];
if (value.toLowerCase() === 'true') {
value = true;
} else if (value.toLowerCase() === 'false') {
value = false;
} else if (options.type === 'int') {
value = parseInt(value);
}
options.processEnvValue = value;
options.valueSource = 'processEnvValue';
} else if (Meteor.settings && typeof Meteor.settings[_id] !== 'undefined') {
if (Meteor.settings[_id] == null) {
return false;
}
value = Meteor.settings[_id];
options.meteorSettingsValue = value;
options.valueSource = 'meteorSettingsValue';
}
if (typeof process !== 'undefined' && process.env && process.env[`OVERWRITE_SETTING_${ _id }`]) {
let value = process.env[`OVERWRITE_SETTING_${ _id }`];
if (value.toLowerCase() === 'true') {
value = true;
} else if (value.toLowerCase() === 'false') {
value = false;
} else if (options.type === 'int') {
value = parseInt(value);
}
options.value = value;
options.processEnvValue = value;
options.valueSource = 'processEnvValue';
}
return value;
};
/*
* Add a setting
* @param {String} _id
* @param {Mixed} value
* @param {Object} setting
*/
settings.add = function(_id, value, { editor, ...options } = {}) {
if (!_id || value == null) {
return false;
}
if (settings._sorter[options.group] == null) {
settings._sorter[options.group] = 0;
}
options.packageValue = value;
options.valueSource = 'packageValue';
options.hidden = options.hidden || false;
options.blocked = options.blocked || false;
options.secret = options.secret || false;
if (options.sorter == null) {
options.sorter = settings._sorter[options.group]++;
}
if (options.enableQuery != null) {
options.enableQuery = JSON.stringify(options.enableQuery);
}
if (options.i18nDefaultQuery != null) {
options.i18nDefaultQuery = JSON.stringify(options.i18nDefaultQuery);
}
if (options.i18nLabel == null) {
options.i18nLabel = _id;
}
if (options.i18nDescription == null) {
options.i18nDescription = `${ _id }_Description`;
}
if (blockedSettings[_id] != null) {
options.blocked = true;
}
if (hiddenSettings[_id] != null) {
options.hidden = true;
}
if (options.autocomplete == null) {
options.autocomplete = true;
}
value = overrideSetting(_id, value, options);
const updateOperations = {
$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 = _.extend({
_id,
}, updateOperations.$set);
if (options.section == null) {
updateOperations.$unset = {
section: 1,
};
query.section = {
$exists: false,
};
}
const existentSetting = Settings.db.findOne(query);
if (existentSetting != null) {
if (existentSetting.editor == null && updateOperations.$setOnInsert.editor != null) {
updateOperations.$set.editor = updateOperations.$setOnInsert.editor;
delete updateOperations.$setOnInsert.editor;
}
} else {
updateOperations.$set.ts = new Date();
}
return Settings.upsert({
_id,
}, updateOperations);
};
/*
* Add a setting group
* @param {String} _id
*/
settings.addGroup = function(_id, options = {}, cb) {
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.ts = new Date();
options.blocked = false;
options.hidden = false;
if (blockedSettings[_id] != null) {
options.blocked = true;
}
if (hiddenSettings[_id] != null) {
options.hidden = true;
}
Settings.upsert({
_id,
}, {
$set: options,
$setOnInsert: {
type: 'group',
createdAt: new Date(),
},
});
if (cb != null) {
cb.call({
add(id, value, options) {
if (options == null) {
options = {};
}
options.group = _id;
return settings.add(id, value, options);
},
section(section, cb) {
return cb.call({
add(id, value, options) {
if (options == null) {
options = {};
}
options.group = _id;
options.section = section;
return settings.add(id, value, options);
},
});
},
});
}
};
/*
* Remove a setting by id
* @param {String} _id
*/
settings.removeById = function(_id) {
if (!_id) {
return false;
}
return Settings.removeById(_id);
};
/*
* Update a setting by id
* @param {String} _id
*/
settings.updateById = function(_id, value, editor) {
if (!_id || value == null) {
return false;
}
if (editor != null) {
return Settings.updateValueAndEditorById(_id, value, editor);
}
return Settings.updateValueById(_id, value);
};
/*
* Update options of a setting by id
* @param {String} _id
*/
settings.updateOptionsById = function(_id, options) {
if (!_id || options == null) {
return false;
}
return Settings.updateOptionsById(_id, options);
};
/*
* Update a setting by id
* @param {String} _id
*/
settings.clearById = function(_id) {
if (_id == null) {
return false;
}
return Settings.updateValueById(_id, undefined);
};
/*
* Update a setting by id
*/
settings.init = function() {
settings.initialLoad = true;
Settings.find().observe({
added(record) {
Meteor.settings[record._id] = record.value;
if (record.env === true) {
process.env[record._id] = record.value;
}
return settings.load(record._id, record.value, settings.initialLoad);
},
changed(record) {
Meteor.settings[record._id] = record.value;
if (record.env === true) {
process.env[record._id] = record.value;
}
return settings.load(record._id, record.value, settings.initialLoad);
},
removed(record) {
delete Meteor.settings[record._id];
if (record.env === true) {
delete process.env[record._id];
}
return settings.load(record._id, undefined, settings.initialLoad);
},
});
settings.initialLoad = false;
settings.afterInitialLoad.forEach((fn) => fn(Meteor.settings));
};
settings.afterInitialLoad = [];
settings.onAfterInitialLoad = function(fn) {
settings.afterInitialLoad.push(fn);
if (settings.initialLoad === false) {
return fn(Meteor.settings);
}
};
export { settings };

@ -0,0 +1,33 @@
import mock from 'mock-require';
type Dictionary = {
[index: string]: any;
}
class SettingsClass {
public data = new Map<string, Dictionary>()
public upsertCalls = 0;
findOne(query: Dictionary): any {
return [...this.data.values()].find((data) => Object.entries(query).every(([key, value]) => data[key] === value));
}
upsert(query: any, update: any): void {
const existent = this.findOne(query);
const data = { ...existent, ...query, ...update.$set };
if (!existent) {
Object.assign(data, update.$setOnInsert);
}
// console.log(query, data);
this.data.set(query._id, data);
this.upsertCalls++;
}
}
export const Settings = new SettingsClass();
mock('../../../models/server/models/Settings', Settings);

@ -0,0 +1,319 @@
/* eslint-disable @typescript-eslint/camelcase */
/* eslint-env mocha */
import { Meteor } from 'meteor/meteor';
import { expect } from 'chai';
import { Settings } from './settings.mocks';
import { settings } from './settings';
describe('Settings', () => {
beforeEach(() => {
Settings.upsertCalls = 0;
Settings.data.clear();
Meteor.settings = { public: {} };
process.env = {};
});
it('should not insert the same setting twice', () => {
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', true, {
type: 'boolean',
sorter: 0,
});
});
});
expect(Settings.data.size).to.be.equal(2);
expect(Settings.upsertCalls).to.be.equal(2);
expect(Settings.findOne({ _id: 'my_setting' })).to.be.include({
type: 'boolean',
sorter: 0,
group: 'group',
section: 'section',
packageValue: true,
value: true,
valueSource: 'packageValue',
hidden: false,
blocked: false,
secret: false,
i18nLabel: 'my_setting',
i18nDescription: 'my_setting_Description',
autocomplete: true,
});
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', true, {
type: 'boolean',
sorter: 0,
});
});
});
expect(Settings.data.size).to.be.equal(2);
expect(Settings.upsertCalls).to.be.equal(2);
expect(Settings.findOne({ _id: 'my_setting' }).value).to.be.equal(true);
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting2', false, {
type: 'boolean',
sorter: 0,
});
});
});
expect(Settings.data.size).to.be.equal(3);
expect(Settings.upsertCalls).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', () => {
process.env.OVERWRITE_SETTING_my_setting = '1';
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', 0, {
type: 'int',
sorter: 0,
});
});
});
const expectedSetting = {
value: 1,
processEnvValue: 1,
valueSource: 'processEnvValue',
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);
process.env.OVERWRITE_SETTING_my_setting = '2';
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', 0, {
type: 'int',
sorter: 0,
});
});
});
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);
});
it('should respect initial value via environment', () => {
process.env.my_setting = '1';
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', 0, {
type: 'int',
sorter: 0,
});
});
});
const expectedSetting = {
value: 1,
processEnvValue: 1,
valueSource: 'processEnvValue',
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);
process.env.my_setting = '2';
settings.addGroup('group', function() {
this.section('section', function() {
this.add('my_setting', 0, {
type: 'int',
sorter: 0,
});
});
});
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);
});
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 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);
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);
});
});

@ -0,0 +1,364 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
import { SettingsBase, SettingValue } from '../../lib/settings';
import SettingsModel from '../../../models/server/models/Settings';
const blockedSettings = new Set<string>();
const hiddenSettings = new Set<string>();
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()));
}
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);
}
options.processEnvValue = value;
options.valueSource = 'processEnvValue';
} else if (typeof Meteor.settings[_id] !== 'undefined') {
if (Meteor.settings[_id] == null) {
return false;
}
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);
}
options.value = value;
options.processEnvValue = value;
options.valueSource = 'processEnvValue';
}
return value;
};
export interface ISettingAddOptions {
_id?: string;
type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect';
editor?: string;
packageEditor?: string;
packageValue?: SettingValue;
valueSource?: string;
hidden?: boolean;
blocked?: boolean;
secret?: boolean;
sorter?: number;
i18nLabel?: string;
i18nDescription?: string;
autocomplete?: boolean;
force?: boolean;
group?: string;
section?: string;
enableQuery?: any;
processEnvValue?: SettingValue;
meteorSettingsValue?: SettingValue;
value?: SettingValue;
ts?: Date;
}
export interface ISettingAddGroupOptions {
hidden?: boolean;
blocked?: boolean;
ts?: Date;
i18nLabel?: string;
i18nDescription?: string;
}
interface IUpdateOperator {
$set: ISettingAddOptions;
$setOnInsert: ISettingAddOptions & {
createdAt: Date;
};
$unset?: {
section?: 1;
};
}
type QueryExpression = {
$exists: boolean;
}
type Query<T> = {
[P in keyof T]?: T[P] | QueryExpression;
}
type addSectionCallback = (this: {
add(id: string, value: SettingValue, options: ISettingAddOptions): void;
}) => void;
type addGroupCallback = (this: {
add(id: string, value: SettingValue, options: ISettingAddOptions): void;
section(section: string, cb: addSectionCallback): void;
}) => void;
class Settings extends SettingsBase {
private afterInitialLoad: Array<(settings: Meteor.Settings) => void> = [];
private _sorter: {[key: string]: number} = {};
private initialLoad = false;
/*
* Add a setting
*/
add(_id: string, value: SettingValue, { editor, ...options }: ISettingAddOptions = {}): boolean {
if (!_id || value == null) {
return false;
}
if (options.group && this._sorter[options.group] == null) {
this._sorter[options.group] = 0;
}
options.packageValue = value;
options.valueSource = 'packageValue';
options.hidden = options.hidden || false;
options.blocked = options.blocked || false;
options.secret = options.secret || false;
if (options.group && options.sorter == null) {
options.sorter = this._sorter[options.group]++;
}
if (options.enableQuery != null) {
options.enableQuery = JSON.stringify(options.enableQuery);
}
if (options.i18nLabel == null) {
options.i18nLabel = _id;
}
if (options.i18nDescription == null) {
options.i18nDescription = `${ _id }_Description`;
}
if (blockedSettings.has(_id)) {
options.blocked = true;
}
if (hiddenSettings.has(_id)) {
options.hidden = true;
}
if (options.autocomplete == null) {
options.autocomplete = true;
}
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<ISettingAddOptions> = {
_id,
...updateOperations.$set,
};
if (options.section == null) {
updateOperations.$unset = {
section: 1,
};
query.section = {
$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);
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) {
cb.call({
add: (id: string, value: SettingValue, options: ISettingAddOptions = {}) => {
options.group = _id;
return this.add(id, value, options);
},
section: (section: string, cb: addSectionCallback) => cb.call({
add: (id: string, value: SettingValue, options: ISettingAddOptions = {}) => {
options.group = _id;
options.section = section;
return this.add(id, value, options);
},
}),
});
}
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: object): 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);
}
/*
* Update a setting by id
*/
init(): void {
this.initialLoad = true;
SettingsModel.find().observe({
added: (record: {_id: string; env: boolean; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
if (record.env === true) {
process.env[record._id] = String(record.value);
}
return this.load(record._id, record.value, this.initialLoad);
},
changed: (record: {_id: string; env: boolean; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
if (record.env === true) {
process.env[record._id] = String(record.value);
}
return this.load(record._id, record.value, this.initialLoad);
},
removed: (record: {_id: string; env: boolean}) => {
delete Meteor.settings[record._id];
if (record.env === true) {
delete process.env[record._id];
}
return this.load(record._id, undefined, this.initialLoad);
},
});
this.initialLoad = false;
this.afterInitialLoad.forEach((fn) => fn(Meteor.settings));
}
onAfterInitialLoad(fn: (settings: Meteor.Settings) => void): void {
this.afterInitialLoad.push(fn);
if (this.initialLoad === false) {
fn(Meteor.settings);
}
}
}
export const settings = new Settings();

@ -120,11 +120,11 @@ const log = (...args) => console.log(`CachedCollection ${ this.name } =>`, ...ar
export class CachedCollection extends EventEmitter {
constructor({
collection,
collection = new Mongo.Collection(null),
name,
methodName,
syncMethodName,
eventName,
methodName = `${ name }/get`,
syncMethodName = `${ name }/get`,
eventName = `${ name }-changed`,
eventType = 'onUser',
userRelated = true,
listenChangesForLoggedUsersOnly = false,
@ -134,13 +134,13 @@ export class CachedCollection extends EventEmitter {
onSyncData = (/* action, record */) => {},
}) {
super();
this.collection = collection || new Mongo.Collection(null);
this.collection = collection;
this.ready = new ReactiveVar(false);
this.name = name;
this.methodName = methodName || `${ name }/get`;
this.syncMethodName = syncMethodName || `${ name }/get`;
this.eventName = eventName || `${ name }-changed`;
this.methodName = methodName;
this.syncMethodName = syncMethodName;
this.eventName = eventName;
this.eventType = eventType;
this.useSync = useSync;
this.listenChangesForLoggedUsersOnly = listenChangesForLoggedUsersOnly;

12
client/main.d.ts vendored

@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/interface-name-prefix */
declare module 'meteor/reactive-dict' {
const ReactiveDict: ReactiveDictStatic;
interface ReactiveDictStatic {
new <T>(name: string, initialValue?: T): ReactiveDict<T>;
}
interface ReactiveDict<T> {
get(name: string): T;
set(name: string, newValue: T): void;
}
}

@ -19,13 +19,11 @@ onLicense('auditing', () => {
];
permissions.forEach((permission) => {
if (!Permissions.findOneById(permission._id)) {
Permissions.upsert(permission._id, { $set: permission });
}
Permissions.create(permission._id, permission.roles);
});
defaultRoles.forEach((role) =>
Roles.upsert({ _id: role.name }, { $setOnInsert: { scope: role.scope, description: role.description || '', protected: true } }),
Roles.createOrUpdate(role.name, role.scope, role.description),
);
});
});

@ -4,8 +4,8 @@ import { Permissions } from '../../../../app/models';
Meteor.startup(() => {
if (Permissions) {
Permissions.createOrUpdate('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
Permissions.createOrUpdate('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
Permissions.createOrUpdate('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
Permissions.create('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
Permissions.create('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
Permissions.create('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
}
});

@ -39,8 +39,8 @@ export const createPermissions = () => {
permissions.map((p) => Permissions.addRole(p, livechatMonitorRole));
Permissions.createOrUpdate('manage-livechat-units', [adminRole, livechatManagerRole]);
Permissions.createOrUpdate('manage-livechat-monitors', [adminRole, livechatManagerRole]);
Permissions.createOrUpdate('manage-livechat-tags', [adminRole, livechatManagerRole]);
Permissions.createOrUpdate('manage-livechat-priorities', [adminRole, livechatManagerRole]);
Permissions.create('manage-livechat-units', [adminRole, livechatManagerRole]);
Permissions.create('manage-livechat-monitors', [adminRole, livechatManagerRole]);
Permissions.create('manage-livechat-tags', [adminRole, livechatManagerRole]);
Permissions.create('manage-livechat-priorities', [adminRole, livechatManagerRole]);
};

@ -1,4 +1,8 @@
--require ts-node/register
--require babel-mocha-es6-compiler
--require babel-polyfill
--reporter spec
--ui bdd
--watch-extensions ts
--extension ts
app/**/*.tests.js app/**/*.tests.ts

76
package-lock.json generated

@ -6217,6 +6217,12 @@
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz",
"integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A=="
},
"@types/chai": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz",
"integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==",
"dev": true
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -6345,6 +6351,21 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/mocha": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz",
"integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==",
"dev": true
},
"@types/mock-require": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-2.0.0.tgz",
"integrity": "sha512-nOgjoE5bBiDeiA+z41i95makyHUSMWQMOPocP+J67Pqx/68HAXaeWN1NFtrAYYV6LrISIZZ8vKHm/a50k0f6Sg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/mongodb": {
"version": "3.5.8",
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.8.tgz",
@ -7227,6 +7248,12 @@
"readable-stream": "^2.0.6"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -13071,6 +13098,12 @@
}
}
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@ -20597,6 +20630,12 @@
"pify": "^3.0.0"
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"make-plural": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.1.0.tgz",
@ -29727,6 +29766,37 @@
"integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==",
"dev": true
},
"ts-node": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz",
"integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "3.1.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-support": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
}
}
},
"ts-pnp": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz",
@ -31319,6 +31389,12 @@
}
}
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
},
"zip-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.0.1.tgz",

@ -6,16 +6,6 @@
"name": "Rocket.Chat",
"url": "https://rocket.chat/"
},
"mocha": {
"tests": [
"app/**/*.tests.js"
],
"files": [
"app/**/*.mocks.js",
"app/**/*.js",
"!app/**/*.tests.js"
]
},
"keywords": [
"rocketchat",
"rocket",
@ -32,11 +22,11 @@
"stylelint": "stylelint \"app/**/*.css\" \"client/**/*.css\" \"app/**/*.less\" \"client/**/*.less\" \"ee/**/*.less\"",
"deploy": "npm run build && pm2 startOrRestart pm2.json",
"postinstall": "node .scripts/npm-postinstall.js",
"testunit-watch": "mocha --watch --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"",
"coverage": "nyc -r html mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"",
"coverage": "nyc -r html mocha --opts ./mocha.opts",
"test": "node .scripts/start.js",
"testui": "cypress run --project tests",
"testunit": "mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"",
"testunit": "mocha --opts ./mocha.opts",
"testunit-watch": "mocha --watch --opts ./mocha.opts",
"testapi": "mocha --opts ./mocha_api.opts",
"testapps": "mocha --opts ./mocha_apps.opts",
"testci": "npm run testapi && npm run testapps && npm run testui",
@ -73,7 +63,10 @@
"@storybook/addons": "^5.2.8",
"@storybook/react": "^5.2.8",
"@types/bcrypt": "^3.0.0",
"@types/chai": "^4.2.11",
"@types/meteor": "^1.4.37",
"@types/mocha": "^7.0.2",
"@types/mock-require": "^2.0.0",
"@types/mongodb": "^3.5.8",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
@ -116,6 +109,7 @@
"stylelint": "^9.9.0",
"stylelint-order": "^2.0.0",
"supertest": "^3.3.0",
"ts-node": "^8.8.2",
"typescript": "^3.7.3",
"webpack": "^4.29.3"
},

@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "esNext",
"module": "CommonJS",
"target": "es2018",
"lib": ["esnext", "dom"],
@ -29,7 +29,6 @@
},
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node"],
"esModuleInterop": true,
"preserveSymlinks": true,

Loading…
Cancel
Save