From eb36f8bed5d8e8b241765db2ba40b37729d17329 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Fri, 21 Dec 2018 19:14:54 -0200 Subject: [PATCH 01/14] Move rocketchat settings to specific package --- .meteor/packages | 3 +- .meteor/versions | 1 + packages/rocketchat-lib/lib/settings.js | 102 +------------------ packages/rocketchat-lib/package.js | 1 + packages/rocketchat-settings/client/index.js | 5 + packages/rocketchat-settings/lib/settings.js | 97 ++++++++++++++++++ packages/rocketchat-settings/package.js | 14 +++ packages/rocketchat-settings/server/index.js | 5 + 8 files changed, 127 insertions(+), 101 deletions(-) create mode 100644 packages/rocketchat-settings/client/index.js create mode 100644 packages/rocketchat-settings/lib/settings.js create mode 100644 packages/rocketchat-settings/package.js create mode 100644 packages/rocketchat-settings/server/index.js diff --git a/.meteor/packages b/.meteor/packages index b77a8aa4096c..52b54f3db3fa 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -200,4 +200,5 @@ rocketchat:bigbluebutton rocketchat:mailmessages juliancwirko:postcss littledata:synced-cron -rocketchat:utils \ No newline at end of file +rocketchat:utils +rocketchat:settings \ No newline at end of file diff --git a/.meteor/versions b/.meteor/versions index 447e1583da9c..c422d1b1304f 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -212,6 +212,7 @@ rocketchat:reactions@0.0.1 rocketchat:retention-policy@0.0.1 rocketchat:sandstorm@0.0.1 rocketchat:search@0.0.1 +rocketchat:settings@0.0.1 rocketchat:setup-wizard@0.0.1 rocketchat:slackbridge@0.0.1 rocketchat:slashcommands-archive@0.0.1 diff --git a/packages/rocketchat-lib/lib/settings.js b/packages/rocketchat-lib/lib/settings.js index f86ab11bcd25..7b90e489b732 100644 --- a/packages/rocketchat-lib/lib/settings.js +++ b/packages/rocketchat-lib/lib/settings.js @@ -1,101 +1,3 @@ -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; +import { settings } from 'meteor/rocketchat:settings'; -/* -* RocketChat.settings holds all packages settings -* @namespace RocketChat.settings -*/ -RocketChat.settings = { - callbacks: {}, - regexCallbacks: {}, - ts: new Date, - get(_id, callback) { - if (callback != null) { - RocketChat.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]); - } else { - 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) { - // async -> sync - // http://daemon.co.za/2012/04/simple-async-with-only-underscore/ - const save = function(setting) { - return function(callback) { - return Meteor.call('saveSetting', setting._id, setting.value, setting.editor, callback); - }; - }; - const actions = _.map(settings, (setting) => save(setting)); - return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))(); - }, - load(key, value, initialLoad) { - ['*', key].forEach((item) => { - if (RocketChat.settings.callbacks[item]) { - RocketChat.settings.callbacks[item].forEach((callback) => callback(key, value, initialLoad)); - } - }); - Object.keys(RocketChat.settings.regexCallbacks).forEach((cbKey) => { - const cbValue = RocketChat.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)) { - RocketChat.settings.regexCallbacks[name = k.source] = RocketChat.settings.regexCallbacks[name = k.source] || { - regex: k, - callbacks: [], - }; - RocketChat.settings.regexCallbacks[k.source].callbacks.push(callback); - } else { - RocketChat.settings.callbacks[k] = RocketChat.settings.callbacks[k] || []; - RocketChat.settings.callbacks[k].push(callback); - } - }); - }, -}; +RocketChat.settings = settings; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index d055661ffbbc..b81ad6206f9f 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -32,6 +32,7 @@ Package.onUse(function(api) { api.use('rocketchat:version'); api.use('rocketchat:logger'); api.use('rocketchat:mailer'); + api.use('rocketchat:settings'); api.use('mizzao:timesync'); api.use('rocketchat:custom-oauth'); api.use('konecty:multiple-instances-status'); diff --git a/packages/rocketchat-settings/client/index.js b/packages/rocketchat-settings/client/index.js new file mode 100644 index 000000000000..8f93e01fb4f4 --- /dev/null +++ b/packages/rocketchat-settings/client/index.js @@ -0,0 +1,5 @@ +import { settings } from '../lib/settings'; + +export { + settings, +}; diff --git a/packages/rocketchat-settings/lib/settings.js b/packages/rocketchat-settings/lib/settings.js new file mode 100644 index 000000000000..35b08414514f --- /dev/null +++ b/packages/rocketchat-settings/lib/settings.js @@ -0,0 +1,97 @@ +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]); + } else { + 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) { + // async -> sync + // http://daemon.co.za/2012/04/simple-async-with-only-underscore/ + const save = function(setting) { + return function(callback) { + return Meteor.call('saveSetting', setting._id, setting.value, setting.editor, callback); + }; + }; + const actions = _.map(settings, (setting) => save(setting)); + return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))(); + }, + 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); + } + }); + }, +}; diff --git a/packages/rocketchat-settings/package.js b/packages/rocketchat-settings/package.js new file mode 100644 index 000000000000..9d63fec60647 --- /dev/null +++ b/packages/rocketchat-settings/package.js @@ -0,0 +1,14 @@ +Package.describe({ + name: 'rocketchat:settings', + version: '0.0.1', + summary: '', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + ]); + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-settings/server/index.js b/packages/rocketchat-settings/server/index.js new file mode 100644 index 000000000000..8f93e01fb4f4 --- /dev/null +++ b/packages/rocketchat-settings/server/index.js @@ -0,0 +1,5 @@ +import { settings } from '../lib/settings'; + +export { + settings, +}; From 4428a97671f21cdf41ce8a3b2788cd5c58d564b5 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Mon, 24 Dec 2018 13:55:20 -0200 Subject: [PATCH 02/14] WIP: Move models from rocketchat-lib to a specific package (server) --- .meteor/packages | 3 +- .meteor/versions | 1 + packages/rocketchat-lib/package.js | 1 + .../rocketchat-lib/server/models/Avatars.js | 114 +-- .../server/models/ExportOperations.js | 79 +- .../rocketchat-lib/server/models/Messages.js | 834 +---------------- .../rocketchat-lib/server/models/Reports.js | 19 +- .../rocketchat-lib/server/models/Rooms.js | 806 +---------------- .../rocketchat-lib/server/models/Settings.js | 183 +--- .../server/models/Subscriptions.js | 850 +---------------- .../rocketchat-lib/server/models/Uploads.js | 111 +-- .../server/models/UserDataFiles.js | 41 +- .../rocketchat-lib/server/models/Users.js | 669 +------------- .../rocketchat-lib/server/models/_Base.js | 286 +----- packages/rocketchat-models/client/index.js | 0 packages/rocketchat-models/package.js | 17 + packages/rocketchat-models/server/index.js | 27 + .../server/models/Avatars.js | 116 +++ .../server/models/ExportOperations.js | 81 ++ .../server/models/Messages.js | 839 +++++++++++++++++ .../server/models/Reports.js | 21 + .../rocketchat-models/server/models/Rooms.js | 809 +++++++++++++++++ .../server/models/Settings.js | 184 ++++ .../server/models/Subscriptions.js | 853 ++++++++++++++++++ .../server/models/Uploads.js | 113 +++ .../server/models/UserDataFiles.js | 43 + .../rocketchat-models/server/models/Users.js | 672 ++++++++++++++ .../rocketchat-models/server/models/_Base.js | 283 ++++++ .../server/models/_BaseDb.js | 9 +- 29 files changed, 4088 insertions(+), 3976 deletions(-) create mode 100644 packages/rocketchat-models/client/index.js create mode 100755 packages/rocketchat-models/package.js create mode 100644 packages/rocketchat-models/server/index.js create mode 100644 packages/rocketchat-models/server/models/Avatars.js create mode 100644 packages/rocketchat-models/server/models/ExportOperations.js create mode 100644 packages/rocketchat-models/server/models/Messages.js create mode 100644 packages/rocketchat-models/server/models/Reports.js create mode 100644 packages/rocketchat-models/server/models/Rooms.js create mode 100644 packages/rocketchat-models/server/models/Settings.js create mode 100644 packages/rocketchat-models/server/models/Subscriptions.js create mode 100644 packages/rocketchat-models/server/models/Uploads.js create mode 100644 packages/rocketchat-models/server/models/UserDataFiles.js create mode 100644 packages/rocketchat-models/server/models/Users.js create mode 100644 packages/rocketchat-models/server/models/_Base.js rename packages/{rocketchat-lib => rocketchat-models}/server/models/_BaseDb.js (98%) diff --git a/.meteor/packages b/.meteor/packages index 52b54f3db3fa..98585f8b6d31 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -201,4 +201,5 @@ rocketchat:mailmessages juliancwirko:postcss littledata:synced-cron rocketchat:utils -rocketchat:settings \ No newline at end of file +rocketchat:settings +rocketchat:models \ No newline at end of file diff --git a/.meteor/versions b/.meteor/versions index c422d1b1304f..e759dba2d774 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -200,6 +200,7 @@ rocketchat:message-pin@0.0.1 rocketchat:message-snippet@0.0.1 rocketchat:message-star@0.0.1 rocketchat:migrations@0.0.1 +rocketchat:models@1.0.0 rocketchat:monitoring@2.30.2_3 rocketchat:nrr@1.0.0 rocketchat:oauth2-server@2.0.0 diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index b81ad6206f9f..c994b17fc1b3 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -25,6 +25,7 @@ Package.onUse(function(api) { api.use('service-configuration'); api.use('check'); api.use('rocketchat:utils'); + api.use('rocketchat:models'); api.use('rocketchat:accounts'); api.use('modules'); api.use('rocketchat:i18n'); diff --git a/packages/rocketchat-lib/server/models/Avatars.js b/packages/rocketchat-lib/server/models/Avatars.js index b056909215cb..455ca64a97d1 100644 --- a/packages/rocketchat-lib/server/models/Avatars.js +++ b/packages/rocketchat-lib/server/models/Avatars.js @@ -1,113 +1,3 @@ -import _ from 'underscore'; -import s from 'underscore.string'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; +import { Avatars } from 'meteor/rocketchat:models'; -RocketChat.models.Avatars = new class extends RocketChat.models._Base { - constructor() { - super('avatars'); - - this.model.before.insert((userId, doc) => { - doc.instanceId = InstanceStatus.id(); - }); - - this.tryEnsureIndex({ name: 1 }); - } - - insertAvatarFileInit(name, userId, store, file, extra) { - const fileData = { - _id: name, - name, - userId, - store, - complete: false, - uploading: true, - progress: 0, - extension: s.strRightBack(file.name, '.'), - uploadedAt: new Date(), - }; - - _.extend(fileData, file, extra); - - return this.insertOrUpsert(fileData); - } - - updateFileComplete(fileId, userId, file) { - if (!fileId) { - return; - } - - const filter = { - _id: fileId, - userId, - }; - - const update = { - $set: { - complete: true, - uploading: false, - progress: 1, - }, - }; - - update.$set = _.extend(file, update.$set); - - if (this.model.direct && this.model.direct.update) { - return this.model.direct.update(filter, update); - } else { - return this.update(filter, update); - } - } - - findOneByName(name) { - return this.findOne({ name }); - } - - updateFileNameById(fileId, name) { - const filter = { _id: fileId }; - const update = { - $set: { - name, - }, - }; - if (this.model.direct && this.model.direct.update) { - return this.model.direct.update(filter, update); - } else { - return this.update(filter, update); - } - } - - // @TODO deprecated - updateFileCompleteByNameAndUserId(name, userId, url) { - if (!name) { - return; - } - - const filter = { - name, - userId, - }; - - const update = { - $set: { - complete: true, - uploading: false, - progress: 1, - url, - }, - }; - - if (this.model.direct && this.model.direct.update) { - return this.model.direct.update(filter, update); - } else { - return this.update(filter, update); - } - } - - deleteFile(fileId) { - if (this.model.direct && this.model.direct.remove) { - return this.model.direct.remove({ _id: fileId }); - } else { - return this.remove({ _id: fileId }); - } - } -}; +RocketChat.models.Avatars = Avatars; diff --git a/packages/rocketchat-lib/server/models/ExportOperations.js b/packages/rocketchat-lib/server/models/ExportOperations.js index 095e8ec885b0..5f2375cc281c 100644 --- a/packages/rocketchat-lib/server/models/ExportOperations.js +++ b/packages/rocketchat-lib/server/models/ExportOperations.js @@ -1,78 +1,3 @@ -import _ from 'underscore'; +import { ExportOperations } from 'meteor/rocketchat:models'; -RocketChat.models.ExportOperations = new class ModelExportOperations extends RocketChat.models._Base { - constructor() { - super('export_operations'); - - this.tryEnsureIndex({ userId: 1 }); - this.tryEnsureIndex({ status: 1 }); - } - - // FIND - findById(id) { - const query = { _id: id }; - - return this.find(query); - } - - findLastOperationByUser(userId, fullExport = false, options = {}) { - const query = { - userId, - fullExport, - }; - - options.sort = { createdAt : -1 }; - return this.findOne(query, options); - } - - findPendingByUser(userId, options) { - const query = { - userId, - status: { - $nin: ['completed'], - }, - }; - - return this.find(query, options); - } - - findAllPending(options) { - const query = { - status: { $nin: ['completed'] }, - }; - - return this.find(query, options); - } - - // UPDATE - updateOperation(data) { - const update = { - $set: { - roomList: data.roomList, - status: data.status, - fileList: data.fileList, - generatedFile: data.generatedFile, - }, - }; - - return this.update(data._id, update); - } - - - // INSERT - create(data) { - const exportOperation = { - createdAt: new Date, - }; - - _.extend(exportOperation, data); - - return this.insert(exportOperation); - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -}; +RocketChat.models.ExportOperations = ExportOperations; diff --git a/packages/rocketchat-lib/server/models/Messages.js b/packages/rocketchat-lib/server/models/Messages.js index bc11062fa660..8a9de256ce0b 100644 --- a/packages/rocketchat-lib/server/models/Messages.js +++ b/packages/rocketchat-lib/server/models/Messages.js @@ -1,833 +1,3 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import _ from 'underscore'; +import { Messages } from 'meteor/rocketchat:models'; -RocketChat.models.Messages = new class extends RocketChat.models._Base { - constructor() { - super('message'); - - this.tryEnsureIndex({ rid: 1, ts: 1 }); - this.tryEnsureIndex({ ts: 1 }); - this.tryEnsureIndex({ 'u._id': 1 }); - this.tryEnsureIndex({ editedAt: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ 'editedBy._id': 1 }, { sparse: 1 }); - this.tryEnsureIndex({ rid: 1, t: 1, 'u._id': 1 }); - this.tryEnsureIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }); - this.tryEnsureIndex({ msg: 'text' }); - this.tryEnsureIndex({ 'file._id': 1 }, { sparse: 1 }); - this.tryEnsureIndex({ 'mentions.username': 1 }, { sparse: 1 }); - this.tryEnsureIndex({ pinned: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ snippeted: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ location: '2dsphere' }); - this.tryEnsureIndex({ slackBotId: 1, slackTs: 1 }, { sparse: 1 }); - } - - countVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $gte: afterTimestamp, - $lte: beforeTimestamp, - }, - }; - - return this.find(query, options).count(); - } - - // FIND - findByMention(username, options) { - const query = { 'mentions.username': username }; - - return this.find(query, options); - } - - findFilesByUserId(userId, options = {}) { - const query = { - 'u._id': userId, - 'file._id': { $exists: true }, - }; - return this.find(query, { fields: { 'file._id': 1 }, ...options }); - } - - findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ts, users = [], options = {}) { - const query = { - rid, - ts, - 'file._id': { $exists: true }, - }; - - if (excludePinned) { - query.pinned = { $ne: true }; - } - - if (users.length) { - query['u.username'] = { $in: users }; - } - - return this.find(query, { fields: { 'file._id': 1 }, ...options }); - } - findVisibleByMentionAndRoomId(username, rid, options) { - const query = { - _hidden: { $ne: true }, - 'mentions.username': username, - rid, - }; - - return this.find(query, options); - } - - findVisibleByRoomId(roomId, options) { - const query = { - _hidden: { - $ne: true, - }, - - rid: roomId, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdNotContainingTypes(roomId, types, options) { - const query = { - _hidden: { - $ne: true, - }, - - rid: roomId, - }; - - if (Match.test(types, [String]) && (types.length > 0)) { - query.t = - { $nin: types }; - } - - return this.find(query, options); - } - - findInvisibleByRoomId(roomId, options) { - const query = { - _hidden: true, - rid: roomId, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdAfterTimestamp(roomId, timestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $gt: timestamp, - }, - }; - - return this.find(query, options); - } - - findForUpdates(roomId, timestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - _updatedAt: { - $gt: timestamp, - }, - }; - return this.find(query, options); - } - - findVisibleByRoomIdBeforeTimestamp(roomId, timestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $lt: timestamp, - }, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdBeforeTimestampInclusive(roomId, timestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $lte: timestamp, - }, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdBetweenTimestamps(roomId, afterTimestamp, beforeTimestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $gt: afterTimestamp, - $lt: beforeTimestamp, - }, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $gte: afterTimestamp, - $lte: beforeTimestamp, - }, - }; - - return this.find(query, options); - } - - findVisibleByRoomIdBeforeTimestampNotContainingTypes(roomId, timestamp, types, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $lt: timestamp, - }, - }; - - if (Match.test(types, [String]) && (types.length > 0)) { - query.t = - { $nin: types }; - } - - return this.find(query, options); - } - - findVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, afterTimestamp, beforeTimestamp, types, options) { - const query = { - _hidden: { - $ne: true, - }, - rid: roomId, - ts: { - $gt: afterTimestamp, - $lt: beforeTimestamp, - }, - }; - - if (Match.test(types, [String]) && (types.length > 0)) { - query.t = - { $nin: types }; - } - - return this.find(query, options); - } - - findVisibleCreatedOrEditedAfterTimestamp(timestamp, options) { - const query = { - _hidden: { $ne: true }, - $or: [{ - ts: { - $gt: timestamp, - }, - }, - { - editedAt: { - $gt: timestamp, - }, - }, - ], - }; - - return this.find(query, options); - } - - findStarredByUserAtRoom(userId, roomId, options) { - const query = { - _hidden: { $ne: true }, - 'starred._id': userId, - rid: roomId, - }; - - return this.find(query, options); - } - - findPinnedByRoom(roomId, options) { - const query = { - t: { $ne: 'rm' }, - _hidden: { $ne: true }, - pinned: true, - rid: roomId, - }; - - return this.find(query, options); - } - - findSnippetedByRoom(roomId, options) { - const query = { - _hidden: { $ne: true }, - snippeted: true, - rid: roomId, - }; - - return this.find(query, options); - } - - getLastTimestamp(options) { - if (options == null) { options = {}; } - const query = { ts: { $exists: 1 } }; - options.sort = { ts: -1 }; - options.limit = 1; - const [message] = this.find(query, options).fetch(); - return message && message.ts; - } - - findByRoomIdAndMessageIds(rid, messageIds, options) { - const query = { - rid, - _id: { - $in: messageIds, - }, - }; - - return this.find(query, options); - } - - findOneBySlackBotIdAndSlackTs(slackBotId, slackTs) { - const query = { - slackBotId, - slackTs, - }; - - return this.findOne(query); - } - - findOneBySlackTs(slackTs) { - const query = { slackTs }; - - return this.findOne(query); - } - - findByRoomIdAndType(roomId, type, options) { - const query = { - rid: roomId, - t: type, - }; - - if (options == null) { options = {}; } - - return this.find(query, options); - } - - findByRoomId(roomId, options) { - const query = { - rid: roomId, - }; - - return this.find(query, options); - } - - getLastVisibleMessageSentWithNoTypeByRoomId(rid, messageId) { - const query = { - rid, - _hidden: { $ne: true }, - t: { $exists: false }, - }; - - if (messageId) { - query._id = { $ne: messageId }; - } - - const options = { - sort: { - ts: -1, - }, - }; - - return this.findOne(query, options); - } - - cloneAndSaveAsHistoryById(_id) { - const me = RocketChat.models.Users.findOneById(Meteor.userId()); - const record = this.findOneById(_id); - record._hidden = true; - record.parent = record._id; - record.editedAt = new Date; - record.editedBy = { - _id: Meteor.userId(), - username: me.username, - }; - delete record._id; - return this.insert(record); - } - - // UPDATE - setHiddenById(_id, hidden) { - if (hidden == null) { hidden = true; } - const query = { _id }; - - const update = { - $set: { - _hidden: hidden, - }, - }; - - return this.update(query, update); - } - - setAsDeletedByIdAndUser(_id, user) { - const query = { _id }; - - const update = { - $set: { - msg: '', - t: 'rm', - urls: [], - mentions: [], - attachments: [], - reactions: [], - editedAt: new Date(), - editedBy: { - _id: user._id, - username: user.username, - }, - }, - }; - - return this.update(query, update); - } - - setPinnedByIdAndUserId(_id, pinnedBy, pinned, pinnedAt) { - if (pinned == null) { pinned = true; } - if (pinnedAt == null) { pinnedAt = 0; } - const query = { _id }; - - const update = { - $set: { - pinned, - pinnedAt: pinnedAt || new Date, - pinnedBy, - }, - }; - - return this.update(query, update); - } - - setSnippetedByIdAndUserId(message, snippetName, snippetedBy, snippeted, snippetedAt) { - if (snippeted == null) { snippeted = true; } - if (snippetedAt == null) { snippetedAt = 0; } - const query = { _id: message._id }; - - const msg = `\`\`\`${ message.msg }\`\`\``; - - const update = { - $set: { - msg, - snippeted, - snippetedAt: snippetedAt || new Date, - snippetedBy, - snippetName, - }, - }; - - return this.update(query, update); - } - - setUrlsById(_id, urls) { - const query = { _id }; - - const update = { - $set: { - urls, - }, - }; - - return this.update(query, update); - } - - updateAllUsernamesByUserId(userId, username) { - const query = { 'u._id': userId }; - - const update = { - $set: { - 'u.username': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateUsernameOfEditByUserId(userId, username) { - const query = { 'editedBy._id': userId }; - - const update = { - $set: { - 'editedBy.username': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateUsernameAndMessageOfMentionByIdAndOldUsername(_id, oldUsername, newUsername, newMessage) { - const query = { - _id, - 'mentions.username': oldUsername, - }; - - const update = { - $set: { - 'mentions.$.username': newUsername, - msg: newMessage, - }, - }; - - return this.update(query, update); - } - - updateUserStarById(_id, userId, starred) { - let update; - const query = { _id }; - - if (starred) { - update = { - $addToSet: { - starred: { _id: userId }, - }, - }; - } else { - update = { - $pull: { - starred: { _id: Meteor.userId() }, - }, - }; - } - - return this.update(query, update); - } - - upgradeEtsToEditAt() { - const query = { ets: { $exists: 1 } }; - - const update = { - $rename: { - ets: 'editedAt', - }, - }; - - return this.update(query, update, { multi: true }); - } - - setMessageAttachments(_id, attachments) { - const query = { _id }; - - const update = { - $set: { - attachments, - }, - }; - - return this.update(query, update); - } - - setSlackBotIdAndSlackTs(_id, slackBotId, slackTs) { - const query = { _id }; - - const update = { - $set: { - slackBotId, - slackTs, - }, - }; - - return this.update(query, update); - } - - unlinkUserId(userId, newUserId, newUsername, newNameAlias) { - const query = { - 'u._id': userId, - }; - - const update = { - $set: { - alias: newNameAlias, - 'u._id': newUserId, - 'u.username' : newUsername, - 'u.name' : undefined, - }, - }; - - return this.update(query, update, { multi: true }); - } - - // INSERT - createWithTypeRoomIdMessageAndUser(type, roomId, message, user, extraData) { - const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { sysMes: 1 } }); - if ((room != null ? room.sysMes : undefined) === false) { - return; - } - const record = { - t: type, - rid: roomId, - ts: new Date, - msg: message, - u: { - _id: user._id, - username: user.username, - }, - groupable: false, - }; - - if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) { - record.unread = true; - } - - _.extend(record, extraData); - - record._id = this.insertOrUpsert(record); - RocketChat.models.Rooms.incMsgCountById(room._id, 1); - return record; - } - - createNavigationHistoryWithRoomIdMessageAndUser(roomId, message, user, extraData) { - const type = 'livechat_navigation_history'; - const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { sysMes: 1 } }); - if ((room != null ? room.sysMes : undefined) === false) { - return; - } - const record = { - t: type, - rid: roomId, - ts: new Date, - msg: message, - u: { - _id: user._id, - username: user.username, - }, - groupable: false, - }; - - if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) { - record.unread = true; - } - - _.extend(record, extraData); - - record._id = this.insertOrUpsert(record); - return record; - } - - createUserJoinWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('uj', roomId, message, user, extraData); - } - - createUserLeaveWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('ul', roomId, message, user, extraData); - } - - createUserRemovedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('ru', roomId, message, user, extraData); - } - - createUserAddedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('au', roomId, message, user, extraData); - } - - createCommandWithRoomIdAndUser(command, roomId, user, extraData) { - return this.createWithTypeRoomIdMessageAndUser('command', roomId, command, user, extraData); - } - - createUserMutedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('user-muted', roomId, message, user, extraData); - } - - createUserUnmutedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('user-unmuted', roomId, message, user, extraData); - } - - createNewModeratorWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('new-moderator', roomId, message, user, extraData); - } - - createModeratorRemovedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('moderator-removed', roomId, message, user, extraData); - } - - createNewOwnerWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('new-owner', roomId, message, user, extraData); - } - - createOwnerRemovedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('owner-removed', roomId, message, user, extraData); - } - - createNewLeaderWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('new-leader', roomId, message, user, extraData); - } - - createLeaderRemovedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('leader-removed', roomId, message, user, extraData); - } - - createSubscriptionRoleAddedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('subscription-role-added', roomId, message, user, extraData); - } - - createSubscriptionRoleRemovedWithRoomIdAndUser(roomId, user, extraData) { - const message = user.username; - return this.createWithTypeRoomIdMessageAndUser('subscription-role-removed', roomId, message, user, extraData); - } - - // REMOVE - removeById(_id) { - const query = { _id }; - - return this.remove(query); - } - - removeByRoomId(roomId) { - const query = { rid: roomId }; - - return this.remove(query); - } - - removeByIdPinnedTimestampAndUsers(rid, pinned, ts, users = []) { - const query = { - rid, - ts, - }; - - if (pinned) { - query.pinned = { $ne: true }; - } - - if (users.length) { - query['u.username'] = { $in: users }; - } - - return this.remove(query); - } - - removeByIdPinnedTimestampLimitAndUsers(rid, pinned, ts, limit, users = []) { - const query = { - rid, - ts, - }; - - if (pinned) { - query.pinned = { $ne: true }; - } - - if (users.length) { - query['u.username'] = { $in: users }; - } - - const messagesToDelete = RocketChat.models.Messages.find(query, { - fields: { - _id: 1, - }, - limit, - }).map(({ _id }) => _id); - - return this.remove({ - _id: { - $in: messagesToDelete, - }, - }); - } - - removeByUserId(userId) { - const query = { 'u._id': userId }; - - return this.remove(query); - } - - removeFilesByRoomId(roomId) { - this.find({ - rid: roomId, - 'file._id': { - $exists: true, - }, - }, { - fields: { - 'file._id': 1, - }, - }).fetch().forEach((document) => FileUpload.getStore('Uploads').deleteById(document.file._id)); - } - - getMessageByFileId(fileID) { - return this.findOne({ 'file._id': fileID }); - } - - setAsRead(rid, until) { - return this.update({ - rid, - unread: true, - ts: { $lt: until }, - }, { - $unset: { - unread: 1, - }, - }, { - multi: true, - }); - } - - setAsReadById(_id) { - return this.update({ - _id, - }, { - $unset: { - unread: 1, - }, - }); - } - - findUnreadMessagesByRoomAndDate(rid, after) { - const query = { - unread: true, - rid, - }; - - if (after) { - query.ts = { $gt: after }; - } - - return this.find(query, { - fields: { - _id: 1, - }, - }); - } -}; +RocketChat.models.Messages = Messages; diff --git a/packages/rocketchat-lib/server/models/Reports.js b/packages/rocketchat-lib/server/models/Reports.js index 923d0dccee3e..ec1aac6ee255 100644 --- a/packages/rocketchat-lib/server/models/Reports.js +++ b/packages/rocketchat-lib/server/models/Reports.js @@ -1,18 +1,3 @@ -import _ from 'underscore'; +import { Reports } from 'meteor/rocketchat:models'; -RocketChat.models.Reports = new class extends RocketChat.models._Base { - constructor() { - super('reports'); - } - createWithMessageDescriptionAndUserId(message, description, userId, extraData) { - const record = { - message, - description, - ts: new Date(), - userId, - }; - _.extend(record, extraData); - record._id = this.insert(record); - return record; - } -}; +RocketChat.models.Reports = Reports; diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js index 2105c43a1112..d664591ad794 100644 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ b/packages/rocketchat-lib/server/models/Rooms.js @@ -1,806 +1,4 @@ -import _ from 'underscore'; -import s from 'underscore.string'; +import { Rooms } from 'meteor/rocketchat:models'; -class ModelRooms extends RocketChat.models._Base { - constructor(...args) { - super(...args); +RocketChat.models.Rooms = Rooms; - this.tryEnsureIndex({ name: 1 }, { unique: 1, sparse: 1 }); - this.tryEnsureIndex({ default: 1 }); - this.tryEnsureIndex({ t: 1 }); - this.tryEnsureIndex({ 'u._id': 1 }); - } - - findOneByIdOrName(_idOrName, options) { - const query = { - $or: [{ - _id: _idOrName, - }, { - name: _idOrName, - }], - }; - - return this.findOne(query, options); - } - - findOneByImportId(_id, options) { - const query = { importIds: _id }; - - return this.findOne(query, options); - } - - findOneByName(name, options) { - const query = { name }; - - return this.findOne(query, options); - } - - findOneByNameAndNotId(name, rid) { - const query = { - _id: { $ne: rid }, - name, - }; - - return this.findOne(query); - } - - findOneByDisplayName(fname, options) { - const query = { fname }; - - return this.findOne(query, options); - } - - findOneByNameAndType(name, type, options) { - const query = { - name, - t: type, - }; - - return this.findOne(query, options); - } - - // FIND - - findById(roomId, options) { - return this.find({ _id: roomId }, options); - } - - findByIds(roomIds, options) { - return this.find({ _id: { $in: [].concat(roomIds) } }, options); - } - - findByType(type, options) { - const query = { t: type }; - - return this.find(query, options); - } - - findByTypeInIds(type, ids, options) { - const query = { - _id: { - $in: ids, - }, - t: type, - }; - - return this.find(query, options); - } - - findByTypes(types, options) { - const query = { - t: { - $in: types, - }, - }; - - return this.find(query, options); - } - - findByUserId(userId, options) { - const query = { 'u._id': userId }; - - return this.find(query, options); - } - - findBySubscriptionUserId(userId, options) { - const data = RocketChat.models.Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() - .map((item) => item.rid); - - const query = { - _id: { - $in: data, - }, - }; - - return this.find(query, options); - } - - findBySubscriptionTypeAndUserId(type, userId, options) { - const data = RocketChat.models.Subscriptions.findByUserIdAndType(userId, type, { fields: { rid: 1 } }).fetch() - .map((item) => item.rid); - - const query = { - t: type, - _id: { - $in: data, - }, - }; - - return this.find(query, options); - } - - findBySubscriptionUserIdUpdatedAfter(userId, _updatedAt, options) { - const ids = RocketChat.models.Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() - .map((item) => item.rid); - - const query = { - _id: { - $in: ids, - }, - _updatedAt: { - $gt: _updatedAt, - }, - }; - - return this.find(query, options); - } - - findByNameContaining(name, options) { - const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); - - const query = { - $or: [ - { name: nameRegex }, - { - t: 'd', - usernames: nameRegex, - }, - ], - }; - - return this.find(query, options); - } - - findByNameContainingAndTypes(name, types, options) { - const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); - - const query = { - t: { - $in: types, - }, - $or: [ - { name: nameRegex }, - { - t: 'd', - usernames: nameRegex, - }, - ], - }; - - return this.find(query, options); - } - - findByNameAndType(name, type, options) { - const query = { - t: type, - name, - }; - - // do not use cache - return this._db.find(query, options); - } - - findByNameAndTypeNotDefault(name, type, options) { - const query = { - t: type, - name, - default: { - $ne: true, - }, - }; - - // do not use cache - return this._db.find(query, options); - } - - findByNameAndTypesNotInIds(name, types, ids, options) { - const query = { - _id: { - $ne: ids, - }, - t: { - $in: types, - }, - name, - }; - - // do not use cache - return this._db.find(query, options); - } - - findChannelAndPrivateByNameStarting(name, options) { - const nameRegex = new RegExp(`^${ s.trim(s.escapeRegExp(name)) }`, 'i'); - - const query = { - t: { - $in: ['c', 'p'], - }, - name: nameRegex, - }; - - return this.find(query, options); - } - - findByDefaultAndTypes(defaultValue, types, options) { - const query = { - default: defaultValue, - t: { - $in: types, - }, - }; - - return this.find(query, options); - } - - findDirectRoomContainingUsername(username, options) { - const query = { - t: 'd', - usernames: username, - }; - - return this.find(query, options); - } - - findDirectRoomContainingAllUsernames(usernames, options) { - const query = { - t: 'd', - usernames: { $size: usernames.length, $all: usernames }, - }; - - return this.findOne(query, options); - } - - findByTypeAndName(type, name, options) { - const query = { - name, - t: type, - }; - - return this.find(query, options); - } - - findByTypeAndNameContaining(type, name, options) { - const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); - - const query = { - name: nameRegex, - t: type, - }; - - return this.find(query, options); - } - - findByTypeInIdsAndNameContaining(type, ids, name, options) { - const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); - - const query = { - _id: { - $in: ids, - }, - name: nameRegex, - t: type, - }; - - return this.find(query, options); - } - - findByTypeAndArchivationState(type, archivationstate, options) { - const query = { t: type }; - - if (archivationstate) { - query.archived = true; - } else { - query.archived = { $ne: true }; - } - - return this.find(query, options); - } - - // UPDATE - addImportIds(_id, importIds) { - importIds = [].concat(importIds); - const query = { _id }; - - const update = { - $addToSet: { - importIds: { - $each: importIds, - }, - }, - }; - - return this.update(query, update); - } - - archiveById(_id) { - const query = { _id }; - - const update = { - $set: { - archived: true, - }, - }; - - return this.update(query, update); - } - - unarchiveById(_id) { - const query = { _id }; - - const update = { - $set: { - archived: false, - }, - }; - - return this.update(query, update); - } - - setNameById(_id, name, fname) { - const query = { _id }; - - const update = { - $set: { - name, - fname, - }, - }; - - return this.update(query, update); - } - - setFnameById(_id, fname) { - const query = { _id }; - - const update = { - $set: { - fname, - }, - }; - - return this.update(query, update); - } - - incMsgCountById(_id, inc) { - if (inc == null) { inc = 1; } - const query = { _id }; - - const update = { - $inc: { - msgs: inc, - }, - }; - - return this.update(query, update); - } - - incMsgCountAndSetLastMessageById(_id, inc, lastMessageTimestamp, lastMessage) { - if (inc == null) { inc = 1; } - const query = { _id }; - - const update = { - $set: { - lm: lastMessageTimestamp, - }, - $inc: { - msgs: inc, - }, - }; - - if (lastMessage) { - update.$set.lastMessage = lastMessage; - } - - return this.update(query, update); - } - - incUsersCountById(_id, inc = 1) { - const query = { _id }; - - const update = { - $inc: { - usersCount: inc, - }, - }; - - return this.update(query, update); - } - - incUsersCountByIds(ids, inc = 1) { - const query = { - _id: { - $in: ids, - }, - }; - - const update = { - $inc: { - usersCount: inc, - }, - }; - - return this.update(query, update, { multi: true }); - } - - setLastMessageById(_id, lastMessage) { - const query = { _id }; - - const update = { - $set: { - lastMessage, - }, - }; - - return this.update(query, update); - } - - resetLastMessageById(_id, messageId) { - const query = { _id }; - const lastMessage = RocketChat.models.Messages.getLastVisibleMessageSentWithNoTypeByRoomId(_id, messageId); - - const update = lastMessage ? { - $set: { - lastMessage, - }, - } : { - $unset: { - lastMessage: 1, - }, - }; - - return this.update(query, update); - } - - replaceUsername(previousUsername, username) { - const query = { usernames: previousUsername }; - - const update = { - $set: { - 'usernames.$': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - replaceMutedUsername(previousUsername, username) { - const query = { muted: previousUsername }; - - const update = { - $set: { - 'muted.$': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - replaceUsernameOfUserByUserId(userId, username) { - const query = { 'u._id': userId }; - - const update = { - $set: { - 'u.username': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - setJoinCodeById(_id, joinCode) { - let update; - const query = { _id }; - - if ((joinCode != null ? joinCode.trim() : undefined) !== '') { - update = { - $set: { - joinCodeRequired: true, - joinCode, - }, - }; - } else { - update = { - $set: { - joinCodeRequired: false, - }, - $unset: { - joinCode: 1, - }, - }; - } - - return this.update(query, update); - } - - setUserById(_id, user) { - const query = { _id }; - - const update = { - $set: { - u: { - _id: user._id, - username: user.username, - }, - }, - }; - - return this.update(query, update); - } - - setTypeById(_id, type) { - const query = { _id }; - const update = { - $set: { - t: type, - }, - }; - if (type === 'p') { - update.$unset = { default: '' }; - } - - return this.update(query, update); - } - - setTopicById(_id, topic) { - const query = { _id }; - - const update = { - $set: { - topic, - }, - }; - - return this.update(query, update); - } - - setAnnouncementById(_id, announcement, announcementDetails) { - const query = { _id }; - - const update = { - $set: { - announcement, - announcementDetails, - }, - }; - - return this.update(query, update); - } - - setCustomFieldsById(_id, customFields) { - const query = { _id }; - - const update = { - $set: { - customFields, - }, - }; - - return this.update(query, update); - } - - muteUsernameByRoomId(_id, username) { - const query = { _id }; - - const update = { - $addToSet: { - muted: username, - }, - }; - - return this.update(query, update); - } - - unmuteUsernameByRoomId(_id, username) { - const query = { _id }; - - const update = { - $pull: { - muted: username, - }, - }; - - return this.update(query, update); - } - - saveDefaultById(_id, defaultValue) { - const query = { _id }; - - const update = { - $set: { - default: defaultValue === 'true', - }, - }; - - return this.update(query, update); - } - - saveRetentionEnabledById(_id, value) { - const query = { _id }; - - const update = {}; - - if (value == null) { - update.$unset = { 'retention.enabled': true }; - } else { - update.$set = { 'retention.enabled': !!value }; - } - - return this.update(query, update); - } - - saveRetentionMaxAgeById(_id, value) { - const query = { _id }; - - value = Number(value); - if (!value) { - value = 30; - } - - const update = { - $set: { - 'retention.maxAge': value, - }, - }; - - return this.update(query, update); - } - - saveRetentionExcludePinnedById(_id, value) { - const query = { _id }; - - const update = { - $set: { - 'retention.excludePinned': value === true, - }, - }; - - return this.update(query, update); - } - - saveRetentionFilesOnlyById(_id, value) { - const query = { _id }; - - const update = { - $set: { - 'retention.filesOnly': value === true, - }, - }; - - return this.update(query, update); - } - - saveRetentionOverrideGlobalById(_id, value) { - const query = { _id }; - - const update = { - $set: { - 'retention.overrideGlobal': value === true, - }, - }; - - return this.update(query, update); - } - - saveEncryptedById(_id, value) { - const query = { _id }; - - const update = { - $set: { - encrypted: value === true, - }, - }; - - return this.update(query, update); - } - - setTopicAndTagsById(_id, topic, tags) { - const setData = {}; - const unsetData = {}; - - if (topic != null) { - if (!_.isEmpty(s.trim(topic))) { - setData.topic = s.trim(topic); - } else { - unsetData.topic = 1; - } - } - - if (tags != null) { - if (!_.isEmpty(s.trim(tags))) { - setData.tags = s.trim(tags).split(',').map((tag) => s.trim(tag)); - } else { - unsetData.tags = 1; - } - } - - const update = {}; - - if (!_.isEmpty(setData)) { - update.$set = setData; - } - - if (!_.isEmpty(unsetData)) { - update.$unset = unsetData; - } - - if (_.isEmpty(update)) { - return; - } - - return this.update({ _id }, update); - } - - // INSERT - createWithTypeNameUserAndUsernames(type, name, fname, user, usernames, extraData) { - const room = { - name, - fname, - t: type, - usernames, - msgs: 0, - usersCount: 0, - u: { - _id: user._id, - username: user.username, - }, - }; - - _.extend(room, extraData); - - room._id = this.insert(room); - return room; - } - - createWithIdTypeAndName(_id, type, name, extraData) { - const room = { - _id, - ts: new Date(), - t: type, - name, - usernames: [], - msgs: 0, - usersCount: 0, - }; - - _.extend(room, extraData); - - this.insert(room); - return room; - } - - createWithFullRoomData(room) { - delete room._id; - - room._id = this.insert(room); - return room; - } - - - // REMOVE - removeById(_id) { - const query = { _id }; - - return this.remove(query); - } - - removeDirectRoomContainingUsername(username) { - const query = { - t: 'd', - usernames: username, - }; - - return this.remove(query); - } -} - -RocketChat.models.Rooms = new ModelRooms('room', true); diff --git a/packages/rocketchat-lib/server/models/Settings.js b/packages/rocketchat-lib/server/models/Settings.js index 3cd5af4fb8c9..6fc6d0da7df2 100644 --- a/packages/rocketchat-lib/server/models/Settings.js +++ b/packages/rocketchat-lib/server/models/Settings.js @@ -1,182 +1,3 @@ -class ModelSettings extends RocketChat.models._Base { - constructor(...args) { - super(...args); +import { Settings } from 'meteor/rocketchat:models'; - this.tryEnsureIndex({ blocked: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ hidden: 1 }, { sparse: 1 }); - } - - // FIND - findById(_id) { - const query = { _id }; - - return this.find(query); - } - - findOneNotHiddenById(_id) { - const query = { - _id, - hidden: { $ne: true }, - }; - - return this.findOne(query); - } - - findByIds(_id = []) { - _id = [].concat(_id); - - const query = { - _id: { - $in: _id, - }, - }; - - return this.find(query); - } - - findByRole(role, options) { - const query = { role }; - - return this.find(query, options); - } - - findPublic(options) { - const query = { public: true }; - - return this.find(query, options); - } - - findNotHiddenPublic(ids = []) { - const filter = { - hidden: { $ne: true }, - public: true, - }; - - if (ids.length > 0) { - filter._id = - { $in: ids }; - } - - return this.find(filter, { fields: { _id: 1, value: 1 } }); - } - - findNotHiddenPublicUpdatedAfter(updatedAt) { - const filter = { - hidden: { $ne: true }, - public: true, - _updatedAt: { - $gt: updatedAt, - }, - }; - - return this.find(filter, { fields: { _id: 1, value: 1 } }); - } - - findNotHiddenPrivate() { - return this.find({ - hidden: { $ne: true }, - public: { $ne: true }, - }); - } - - findNotHidden(options) { - return this.find({ hidden: { $ne: true } }, options); - } - - findNotHiddenUpdatedAfter(updatedAt) { - return this.find({ - hidden: { $ne: true }, - _updatedAt: { - $gt: updatedAt, - }, - }); - } - - findSetupWizardSettings() { - return this.find({ wizard: { $exists: true, $ne: null } }); - } - - // UPDATE - updateValueById(_id, value) { - const query = { - blocked: { $ne: true }, - value: { $ne: value }, - _id, - }; - - const update = { - $set: { - value, - }, - }; - - return this.update(query, update); - } - - updateValueAndEditorById(_id, value, editor) { - const query = { - blocked: { $ne: true }, - value: { $ne: value }, - _id, - }; - - const update = { - $set: { - value, - editor, - }, - }; - - return this.update(query, update); - } - - updateValueNotHiddenById(_id, value) { - const query = { - _id, - hidden: { $ne: true }, - blocked: { $ne: true }, - }; - - const update = { - $set: { - value, - }, - }; - - return this.update(query, update); - } - - updateOptionsById(_id, options) { - const query = { - blocked: { $ne: true }, - _id, - }; - - const update = { $set: options }; - - return this.update(query, update); - } - - // INSERT - createWithIdAndValue(_id, value) { - const record = { - _id, - value, - _createdAt: new Date, - }; - - return this.insert(record); - } - - // REMOVE - removeById(_id) { - const query = { - blocked: { $ne: true }, - _id, - }; - - return this.remove(query); - } -} - -RocketChat.models.Settings = new ModelSettings('settings', true); +RocketChat.models.Settings = Settings; diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 6b4728a7ead5..36931f09b5ca 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -1,850 +1,4 @@ -import { Match } from 'meteor/check'; +import { Subscriptions } from 'meteor/rocketchat:models'; -class ModelSubscriptions extends RocketChat.models._Base { - constructor(...args) { - super(...args); +RocketChat.models.Subscriptions = Subscriptions; - this.tryEnsureIndex({ rid: 1, 'u._id': 1 }, { unique: 1 }); - this.tryEnsureIndex({ rid: 1, 'u.username': 1 }); - this.tryEnsureIndex({ rid: 1, alert: 1, 'u._id': 1 }); - this.tryEnsureIndex({ rid: 1, roles: 1 }); - this.tryEnsureIndex({ 'u._id': 1, name: 1, t: 1 }); - this.tryEnsureIndex({ open: 1 }); - this.tryEnsureIndex({ alert: 1 }); - - this.tryEnsureIndex({ rid: 1, 'u._id': 1, open: 1 }); - - this.tryEnsureIndex({ ts: 1 }); - this.tryEnsureIndex({ ls: 1 }); - this.tryEnsureIndex({ audioNotifications: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ desktopNotifications: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ mobilePushNotifications: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ emailNotifications: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ autoTranslate: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ autoTranslateLanguage: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ 'userHighlights.0': 1 }, { sparse: 1 }); - } - - - // FIND ONE - findOneByRoomIdAndUserId(roomId, userId, options) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - return this.findOne(query, options); - } - - findOneByRoomIdAndUsername(roomId, username, options) { - const query = { - rid: roomId, - 'u.username': username, - }; - - return this.findOne(query, options); - } - - findOneByRoomNameAndUserId(roomName, userId) { - const query = { - name: roomName, - 'u._id': userId, - }; - - return this.findOne(query); - } - - // FIND - findByUserId(userId, options) { - const query = - { 'u._id': userId }; - - return this.find(query, options); - } - - findByUserIdAndType(userId, type, options) { - const query = { - 'u._id': userId, - t: type, - }; - - return this.find(query, options); - } - - findByUserIdAndTypes(userId, types, options) { - const query = { - 'u._id': userId, - t: { - $in: types, - }, - }; - - return this.find(query, options); - } - - findByUserIdUpdatedAfter(userId, updatedAt, options) { - const query = { - 'u._id': userId, - _updatedAt: { - $gt: updatedAt, - }, - }; - - return this.find(query, options); - } - - findByRoomIdAndRoles(roomId, roles, options) { - roles = [].concat(roles); - const query = { - rid: roomId, - roles: { $in: roles }, - }; - - return this.find(query, options); - } - - findByType(types, options) { - const query = { - t: { - $in: types, - }, - }; - - return this.find(query, options); - } - - findByTypeAndUserId(type, userId, options) { - const query = { - t: type, - 'u._id': userId, - }; - - return this.find(query, options); - } - - findByRoomId(roomId, options) { - const query = - { rid: roomId }; - - return this.find(query, options); - } - - findByRoomIdAndNotUserId(roomId, userId, options) { - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - }; - - return this.find(query, options); - } - - findByRoomWithUserHighlights(roomId, options) { - const query = { - rid: roomId, - 'userHighlights.0': { $exists: true }, - }; - - return this.find(query, options); - } - - getLastSeen(options) { - if (options == null) { - options = {}; - } - const query = { ls: { $exists: 1 } }; - options.sort = { ls: -1 }; - options.limit = 1; - const [subscription] = this.find(query, options).fetch(); - return subscription && subscription.ls; - } - - findByRoomIdAndUserIds(roomId, userIds, options) { - const query = { - rid: roomId, - 'u._id': { - $in: userIds, - }, - }; - - return this.find(query, options); - } - - findByRoomIdAndUserIdsOrAllMessages(roomId, userIds) { - const query = { - rid: roomId, - $or: [ - { 'u._id': { $in: userIds } }, - { emailNotifications: 'all' }, - ], - }; - - return this.find(query); - } - - findByRoomIdWhenUserIdExists(rid, options) { - const query = { rid, 'u._id': { $exists: 1 } }; - - return this.find(query, options); - } - - findByRoomIdWhenUsernameExists(rid, options) { - const query = { rid, 'u.username': { $exists: 1 } }; - - return this.find(query, options); - } - - findUnreadByUserId(userId) { - const query = { - 'u._id': userId, - unread: { - $gt: 0, - }, - }; - - return this.find(query, { fields: { unread: 1 } }); - } - - getMinimumLastSeenByRoomId(rid) { - return this.db.findOne({ - rid, - }, { - sort: { - ls: 1, - }, - fields: { - ls: 1, - }, - }); - } - - // UPDATE - archiveByRoomId(roomId) { - const query = - { rid: roomId }; - - const update = { - $set: { - alert: false, - open: false, - archived: true, - }, - }; - - return this.update(query, update, { multi: true }); - } - - unarchiveByRoomId(roomId) { - const query = - { rid: roomId }; - - const update = { - $set: { - alert: false, - open: true, - archived: false, - }, - }; - - return this.update(query, update, { multi: true }); - } - - hideByRoomIdAndUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - const update = { - $set: { - alert: false, - open: false, - }, - }; - - return this.update(query, update); - } - - openByRoomIdAndUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - const update = { - $set: { - open: true, - }, - }; - - return this.update(query, update); - } - - setAsReadByRoomIdAndUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - const update = { - $set: { - open: true, - alert: false, - unread: 0, - userMentions: 0, - groupMentions: 0, - ls: new Date, - }, - }; - - return this.update(query, update); - } - - setAsUnreadByRoomIdAndUserId(roomId, userId, firstMessageUnreadTimestamp) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - const update = { - $set: { - open: true, - alert: true, - ls: firstMessageUnreadTimestamp, - }, - }; - - return this.update(query, update); - } - - setCustomFieldsDirectMessagesByUserId(userId, fields) { - const query = { - 'u._id': userId, - t: 'd', - }; - const update = { $set: { customFields: fields } }; - const options = { multi: true }; - - return this.update(query, update, options); - } - - setFavoriteByRoomIdAndUserId(roomId, userId, favorite) { - if (favorite == null) { - favorite = true; - } - const query = { - rid: roomId, - 'u._id': userId, - }; - - const update = { - $set: { - f: favorite, - }, - }; - - return this.update(query, update); - } - - updateNameAndAlertByRoomId(roomId, name, fname) { - const query = - { rid: roomId }; - - const update = { - $set: { - name, - fname, - alert: true, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateDisplayNameByRoomId(roomId, fname) { - const query = - { rid: roomId }; - - const update = { - $set: { - fname, - }, - }; - - return this.update(query, update, { multi: true }); - } - - setUserUsernameByUserId(userId, username) { - const query = - { 'u._id': userId }; - - const update = { - $set: { - 'u.username': username, - }, - }; - - return this.update(query, update, { multi: true }); - } - - setNameForDirectRoomsWithOldName(oldName, name) { - const query = { - name: oldName, - t: 'd', - }; - - const update = { - $set: { - name, - }, - }; - - return this.update(query, update, { multi: true }); - } - - incUnreadForRoomIdExcludingUserId(roomId, userId, inc) { - if (inc == null) { - inc = 1; - } - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - }; - - const update = { - $set: { - alert: true, - open: true, - }, - $inc: { - unread: inc, - }, - }; - - return this.update(query, update, { multi: true }); - } - - incGroupMentionsAndUnreadForRoomIdExcludingUserId(roomId, userId, incGroup = 1, incUnread = 1) { - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - }; - - const update = { - $set: { - alert: true, - open: true, - }, - $inc: { - unread: incUnread, - groupMentions: incGroup, - }, - }; - - return this.update(query, update, { multi: true }); - } - - incUserMentionsAndUnreadForRoomIdAndUserIds(roomId, userIds, incUser = 1, incUnread = 1) { - const query = { - rid: roomId, - 'u._id': { - $in: userIds, - }, - }; - - const update = { - $set: { - alert: true, - open: true, - }, - $inc: { - unread: incUnread, - userMentions: incUser, - }, - }; - - return this.update(query, update, { multi: true }); - } - - ignoreUser({ _id, ignoredUser : ignored, ignore = true }) { - const query = { - _id, - }; - const update = { - }; - if (ignore) { - update.$addToSet = { ignored }; - } else { - update.$pull = { ignored }; - } - - return this.update(query, update); - } - - setAlertForRoomIdExcludingUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - alert: { $ne: true }, - }; - - const update = { - $set: { - alert: true, - }, - }; - return this.update(query, update, { multi: true }); - } - - setOpenForRoomIdExcludingUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - open: { $ne: true }, - }; - - const update = { - $set: { - open: true, - }, - }; - return this.update(query, update, { multi: true }); - } - - setBlockedByRoomId(rid, blocked, blocker) { - const query = { - rid, - 'u._id': blocked, - }; - - const update = { - $set: { - blocked: true, - }, - }; - - const query2 = { - rid, - 'u._id': blocker, - }; - - const update2 = { - $set: { - blocker: true, - }, - }; - - return this.update(query, update) && this.update(query2, update2); - } - - unsetBlockedByRoomId(rid, blocked, blocker) { - const query = { - rid, - 'u._id': blocked, - }; - - const update = { - $unset: { - blocked: 1, - }, - }; - - const query2 = { - rid, - 'u._id': blocker, - }; - - const update2 = { - $unset: { - blocker: 1, - }, - }; - - return this.update(query, update) && this.update(query2, update2); - } - - updateCustomFieldsByRoomId(rid, cfields) { - const query = { rid }; - const customFields = cfields || {}; - const update = { - $set: { - customFields, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateTypeByRoomId(roomId, type) { - const query = - { rid: roomId }; - - const update = { - $set: { - t: type, - }, - }; - - return this.update(query, update, { multi: true }); - } - - addRoleById(_id, role) { - const query = - { _id }; - - const update = { - $addToSet: { - roles: role, - }, - }; - - return this.update(query, update); - } - - removeRoleById(_id, role) { - const query = - { _id }; - - const update = { - $pull: { - roles: role, - }, - }; - - return this.update(query, update); - } - - setArchivedByUsername(username, archived) { - const query = { - t: 'd', - name: username, - }; - - const update = { - $set: { - archived, - }, - }; - - return this.update(query, update, { multi: true }); - } - - clearDesktopNotificationUserPreferences(userId) { - const query = { - 'u._id': userId, - desktopPrefOrigin: 'user', - }; - - const update = { - $unset: { - desktopNotifications: 1, - desktopPrefOrigin: 1, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateDesktopNotificationUserPreferences(userId, desktopNotifications) { - const query = { - 'u._id': userId, - desktopPrefOrigin: { - $ne: 'subscription', - }, - }; - - const update = { - $set: { - desktopNotifications, - desktopPrefOrigin: 'user', - }, - }; - - return this.update(query, update, { multi: true }); - } - - clearMobileNotificationUserPreferences(userId) { - const query = { - 'u._id': userId, - mobilePrefOrigin: 'user', - }; - - const update = { - $unset: { - mobilePushNotifications: 1, - mobilePrefOrigin: 1, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateMobileNotificationUserPreferences(userId, mobilePushNotifications) { - const query = { - 'u._id': userId, - mobilePrefOrigin: { - $ne: 'subscription', - }, - }; - - const update = { - $set: { - mobilePushNotifications, - mobilePrefOrigin: 'user', - }, - }; - - return this.update(query, update, { multi: true }); - } - - clearEmailNotificationUserPreferences(userId) { - const query = { - 'u._id': userId, - emailPrefOrigin: 'user', - }; - - const update = { - $unset: { - emailNotifications: 1, - emailPrefOrigin: 1, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateEmailNotificationUserPreferences(userId, emailNotifications) { - const query = { - 'u._id': userId, - emailPrefOrigin: { - $ne: 'subscription', - }, - }; - - const update = { - $set: { - emailNotifications, - emailPrefOrigin: 'user', - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateUserHighlights(userId, userHighlights) { - const query = { - 'u._id': userId, - }; - - const update = { - $set: { - userHighlights, - }, - }; - - return this.update(query, update, { multi: true }); - } - - updateDirectFNameByName(name, fname) { - const query = { - t: 'd', - name, - }; - - const update = { - $set: { - fname, - }, - }; - - return this.update(query, update, { multi: true }); - } - - // INSERT - createWithRoomAndUser(room, user, extraData) { - const subscription = { - open: false, - alert: false, - unread: 0, - userMentions: 0, - groupMentions: 0, - ts: room.ts, - rid: room._id, - name: room.name, - fname: room.fname, - customFields: room.customFields, - t: room.t, - u: { - _id: user._id, - username: user.username, - name: user.name, - }, - ...RocketChat.getDefaultSubscriptionPref(user), - ...extraData, - }; - - const result = this.insert(subscription); - - RocketChat.models.Rooms.incUsersCountById(room._id); - - return result; - } - - - // REMOVE - removeByUserId(userId) { - const query = { - 'u._id': userId, - }; - - const roomIds = this.findByUserId(userId).map((s) => s.rid); - - const result = this.remove(query); - - if (Match.test(result, Number) && result > 0) { - RocketChat.models.Rooms.incUsersCountByIds(roomIds, -1); - } - - return result; - } - - removeByRoomId(roomId) { - const query = { - rid: roomId, - }; - - const result = this.remove(query); - - if (Match.test(result, Number) && result > 0) { - RocketChat.models.Rooms.incUsersCountById(roomId, - result); - } - - return result; - } - - removeByRoomIdAndUserId(roomId, userId) { - const query = { - rid: roomId, - 'u._id': userId, - }; - - const result = this.remove(query); - - if (Match.test(result, Number) && result > 0) { - RocketChat.models.Rooms.incUsersCountById(roomId, - result); - } - - return result; - } -} - -RocketChat.models.Subscriptions = new ModelSubscriptions('subscription', true); diff --git a/packages/rocketchat-lib/server/models/Uploads.js b/packages/rocketchat-lib/server/models/Uploads.js index e25823640caf..7dc92cf6f67b 100644 --- a/packages/rocketchat-lib/server/models/Uploads.js +++ b/packages/rocketchat-lib/server/models/Uploads.js @@ -1,110 +1,3 @@ -import _ from 'underscore'; -import s from 'underscore.string'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; +import { Uploads } from 'meteor/rocketchat:models'; -RocketChat.models.Uploads = new class extends RocketChat.models._Base { - constructor() { - super('uploads'); - - this.model.before.insert((userId, doc) => { - doc.instanceId = InstanceStatus.id(); - }); - - this.tryEnsureIndex({ rid: 1 }); - this.tryEnsureIndex({ uploadedAt: 1 }); - } - - findNotHiddenFilesOfRoom(roomId, searchText, limit) { - const fileQuery = { - rid: roomId, - complete: true, - uploading: false, - _hidden: { - $ne: true, - }, - }; - - if (searchText) { - fileQuery.name = { $regex: new RegExp(RegExp.escape(searchText), 'i') }; - } - - const fileOptions = { - limit, - sort: { - uploadedAt: -1, - }, - fields: { - _id: 1, - userId: 1, - rid: 1, - name: 1, - description: 1, - type: 1, - url: 1, - uploadedAt: 1, - }, - }; - - return this.find(fileQuery, fileOptions); - } - - insertFileInit(userId, store, file, extra) { - const fileData = { - userId, - store, - complete: false, - uploading: true, - progress: 0, - extension: s.strRightBack(file.name, '.'), - uploadedAt: new Date(), - }; - - _.extend(fileData, file, extra); - - if (this.model.direct && this.model.direct.insert != null) { - file = this.model.direct.insert(fileData); - } else { - file = this.insert(fileData); - } - - return file; - } - - updateFileComplete(fileId, userId, file) { - let result; - if (!fileId) { - return; - } - - const filter = { - _id: fileId, - userId, - }; - - const update = { - $set: { - complete: true, - uploading: false, - progress: 1, - }, - }; - - update.$set = _.extend(file, update.$set); - - if (this.model.direct && this.model.direct.update != null) { - result = this.model.direct.update(filter, update); - } else { - result = this.update(filter, update); - } - - return result; - } - - deleteFile(fileId) { - if (this.model.direct && this.model.direct.remove != null) { - return this.model.direct.remove({ _id: fileId }); - } else { - return this.remove({ _id: fileId }); - } - } -}; +RocketChat.models.Uploads = Uploads; diff --git a/packages/rocketchat-lib/server/models/UserDataFiles.js b/packages/rocketchat-lib/server/models/UserDataFiles.js index 8ed425dcf178..b3eff0ac6125 100644 --- a/packages/rocketchat-lib/server/models/UserDataFiles.js +++ b/packages/rocketchat-lib/server/models/UserDataFiles.js @@ -1,40 +1,3 @@ -import _ from 'underscore'; +import { UserDataFiles } from 'meteor/rocketchat:models'; -RocketChat.models.UserDataFiles = new class ModelUserDataFiles extends RocketChat.models._Base { - constructor() { - super('user_data_files'); - - this.tryEnsureIndex({ userId: 1 }); - } - - // FIND - findById(id) { - const query = { _id: id }; - return this.find(query); - } - - findLastFileByUser(userId, options = {}) { - const query = { - userId, - }; - - options.sort = { _updatedAt : -1 }; - return this.findOne(query, options); - } - - // INSERT - create(data) { - const userDataFile = { - createdAt: new Date, - }; - - _.extend(userDataFile, data); - - return this.insert(userDataFile); - } - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -}; +RocketChat.models.UserDataFiles = UserDataFiles; diff --git a/packages/rocketchat-lib/server/models/Users.js b/packages/rocketchat-lib/server/models/Users.js index bc9ac2f61927..8918c19aa575 100644 --- a/packages/rocketchat-lib/server/models/Users.js +++ b/packages/rocketchat-lib/server/models/Users.js @@ -1,669 +1,4 @@ -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; -import _ from 'underscore'; -import s from 'underscore.string'; +import { Users } from 'meteor/rocketchat:models'; -class ModelUsers extends RocketChat.models._Base { - constructor(...args) { - super(...args); +RocketChat.models.Users = Users; - this.tryEnsureIndex({ roles: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ name: 1 }); - this.tryEnsureIndex({ lastLogin: 1 }); - this.tryEnsureIndex({ status: 1 }); - this.tryEnsureIndex({ active: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 }); - this.tryEnsureIndex({ type: 1 }); - } - - findOneByImportId(_id, options) { - return this.findOne({ importIds: _id }, options); - } - - findOneByUsername(username, options) { - if (typeof username === 'string') { - username = new RegExp(`^${ username }$`, 'i'); - } - - const query = { username }; - - return this.findOne(query, options); - } - - findOneByEmailAddress(emailAddress, options) { - const query = { 'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i') }; - - return this.findOne(query, options); - } - - findOneAdmin(admin, options) { - const query = { admin }; - - return this.findOne(query, options); - } - - findOneByIdAndLoginToken(_id, token, options) { - const query = { - _id, - 'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token), - }; - - return this.findOne(query, options); - } - - findOneById(userId, options) { - const query = { _id: userId }; - - return this.findOne(query, options); - } - - // FIND - findById(userId) { - const query = { _id: userId }; - - return this.find(query); - } - - findByIds(users, options) { - const query = { _id: { $in: users } }; - return this.find(query, options); - } - - findUsersNotOffline(options) { - const query = { - username: { - $exists: 1, - }, - status: { - $in: ['online', 'away', 'busy'], - }, - }; - - return this.find(query, options); - } - - findByRoomId(rid, options) { - const data = RocketChat.models.Subscriptions.findByRoomId(rid).fetch().map((item) => item.u._id); - const query = { - _id: { - $in: data, - }, - }; - - return this.find(query, options); - } - - findByUsername(username, options) { - const query = { username }; - - return this.find(query, options); - } - - findActiveByUsernameOrNameRegexWithExceptions(searchTerm, exceptions, options) { - if (exceptions == null) { exceptions = []; } - if (options == null) { options = {}; } - if (!_.isArray(exceptions)) { - exceptions = [exceptions]; - } - - const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i'); - const query = { - $or: [{ - username: termRegex, - }, { - name: termRegex, - }], - active: true, - type: { - $in: ['user', 'bot'], - }, - $and: [{ - username: { - $exists: true, - }, - }, { - username: { - $nin: exceptions, - }, - }], - }; - - return this.find(query, options); - } - - findByActiveUsersExcept(searchTerm, exceptions, options) { - if (exceptions == null) { exceptions = []; } - if (options == null) { options = {}; } - if (!_.isArray(exceptions)) { - exceptions = [exceptions]; - } - - const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i'); - - const orStmt = _.reduce(RocketChat.settings.get('Accounts_SearchFields').trim().split(','), function(acc, el) { - acc.push({ [el.trim()]: termRegex }); - return acc; - }, []); - const query = { - $and: [ - { - active: true, - $or: orStmt, - }, - { - username: { $exists: true, $nin: exceptions }, - }, - ], - }; - - // do not use cache - return this._db.find(query, options); - } - - findUsersByNameOrUsername(nameOrUsername, options) { - const query = { - username: { - $exists: 1, - }, - - $or: [ - { name: nameOrUsername }, - { username: nameOrUsername }, - ], - - type: { - $in: ['user'], - }, - }; - - return this.find(query, options); - } - - findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress, options) { - const query = { - $or: [ - { name: usernameNameOrEmailAddress }, - { username: usernameNameOrEmailAddress }, - { 'emails.address': usernameNameOrEmailAddress }, - ], - type: { - $in: ['user', 'bot'], - }, - }; - - return this.find(query, options); - } - - findLDAPUsers(options) { - const query = { ldap: true }; - - return this.find(query, options); - } - - findCrowdUsers(options) { - const query = { crowd: true }; - - return this.find(query, options); - } - - getLastLogin(options) { - if (options == null) { options = {}; } - const query = { lastLogin: { $exists: 1 } }; - options.sort = { lastLogin: -1 }; - options.limit = 1; - const [user] = this.find(query, options).fetch(); - return user && user.lastLogin; - } - - findUsersByUsernames(usernames, options) { - const query = { - username: { - $in: usernames, - }, - }; - - return this.find(query, options); - } - - findUsersByIds(ids, options) { - const query = { - _id: { - $in: ids, - }, - }; - return this.find(query, options); - } - - findUsersWithUsernameByIds(ids, options) { - const query = { - _id: { - $in: ids, - }, - username: { - $exists: 1, - }, - }; - - return this.find(query, options); - } - - findUsersWithUsernameByIdsNotOffline(ids, options) { - const query = { - _id: { - $in: ids, - }, - username: { - $exists: 1, - }, - status: { - $in: ['online', 'away', 'busy'], - }, - }; - - return this.find(query, options); - } - - getOldest(fields = { _id: 1 }) { - const query = { - _id: { - $ne: 'rocket.cat', - }, - }; - - const options = { - fields, - sort: { - createdAt: 1, - }, - }; - - return this.findOne(query, options); - } - - // UPDATE - addImportIds(_id, importIds) { - importIds = [].concat(importIds); - - const query = { _id }; - - const update = { - $addToSet: { - importIds: { - $each: importIds, - }, - }, - }; - - return this.update(query, update); - } - - updateLastLoginById(_id) { - const update = { - $set: { - lastLogin: new Date, - }, - }; - - return this.update(_id, update); - } - - setServiceId(_id, serviceName, serviceId) { - const update = - { $set: {} }; - - const serviceIdKey = `services.${ serviceName }.id`; - update.$set[serviceIdKey] = serviceId; - - return this.update(_id, update); - } - - setUsername(_id, username) { - const update = - { $set: { username } }; - - return this.update(_id, update); - } - - setEmail(_id, email) { - const update = { - $set: { - emails: [{ - address: email, - verified: false, - }, - ], - }, - }; - - return this.update(_id, update); - } - - setEmailVerified(_id, email) { - const query = { - _id, - emails: { - $elemMatch: { - address: email, - verified: false, - }, - }, - }; - - const update = { - $set: { - 'emails.$.verified': true, - }, - }; - - return this.update(query, update); - } - - setName(_id, name) { - const update = { - $set: { - name, - }, - }; - - return this.update(_id, update); - } - - setCustomFields(_id, fields) { - const values = {}; - Object.keys(fields).forEach((key) => { - values[`customFields.${ key }`] = fields[key]; - }); - - const update = { $set: values }; - - return this.update(_id, update); - } - - setAvatarOrigin(_id, origin) { - const update = { - $set: { - avatarOrigin: origin, - }, - }; - - return this.update(_id, update); - } - - unsetAvatarOrigin(_id) { - const update = { - $unset: { - avatarOrigin: 1, - }, - }; - - return this.update(_id, update); - } - - setUserActive(_id, active) { - if (active == null) { active = true; } - const update = { - $set: { - active, - }, - }; - - return this.update(_id, update); - } - - setAllUsersActive(active) { - const update = { - $set: { - active, - }, - }; - - return this.update({}, update, { multi: true }); - } - - unsetLoginTokens(_id) { - const update = { - $set: { - 'services.resume.loginTokens' : [], - }, - }; - - return this.update(_id, update); - } - - unsetRequirePasswordChange(_id) { - const update = { - $unset: { - requirePasswordChange : true, - requirePasswordChangeReason : true, - }, - }; - - return this.update(_id, update); - } - - resetPasswordAndSetRequirePasswordChange(_id, requirePasswordChange, requirePasswordChangeReason) { - const update = { - $unset: { - 'services.password': 1, - }, - $set: { - requirePasswordChange, - requirePasswordChangeReason, - }, - }; - - return this.update(_id, update); - } - - setLanguage(_id, language) { - const update = { - $set: { - language, - }, - }; - - return this.update(_id, update); - } - - setProfile(_id, profile) { - const update = { - $set: { - 'settings.profile': profile, - }, - }; - - return this.update(_id, update); - } - - clearSettings(_id) { - const update = { - $set: { - settings: {}, - }, - }; - - return this.update(_id, update); - } - - setPreferences(_id, preferences) { - const settings = Object.assign( - {}, - ...Object.keys(preferences).map((key) => ({ [`settings.preferences.${ key }`]: preferences[key] })) - ); - - const update = { - $set: settings, - }; - if (parseInt(preferences.clockMode) === 0) { - delete update.$set['settings.preferences.clockMode']; - update.$unset = { 'settings.preferences.clockMode': 1 }; - } - - return this.update(_id, update); - } - - setUtcOffset(_id, utcOffset) { - const query = { - _id, - utcOffset: { - $ne: utcOffset, - }, - }; - - const update = { - $set: { - utcOffset, - }, - }; - - return this.update(query, update); - } - - saveUserById(_id, data) { - const setData = {}; - const unsetData = {}; - - if (data.name != null) { - if (!_.isEmpty(s.trim(data.name))) { - setData.name = s.trim(data.name); - } else { - unsetData.name = 1; - } - } - - if (data.email != null) { - if (!_.isEmpty(s.trim(data.email))) { - setData.emails = [{ address: s.trim(data.email) }]; - } else { - unsetData.emails = 1; - } - } - - if (data.phone != null) { - if (!_.isEmpty(s.trim(data.phone))) { - setData.phone = [{ phoneNumber: s.trim(data.phone) }]; - } else { - unsetData.phone = 1; - } - } - - const update = {}; - - if (!_.isEmpty(setData)) { - update.$set = setData; - } - - if (!_.isEmpty(unsetData)) { - update.$unset = unsetData; - } - - if (_.isEmpty(update)) { - return true; - } - - return this.update({ _id }, update); - } - - setReason(_id, reason) { - const update = { - $set: { - reason, - }, - }; - - return this.update(_id, update); - } - - unsetReason(_id) { - const update = { - $unset: { - reason: true, - }, - }; - - return this.update(_id, update); - } - - addBannerById(_id, banner) { - const update = { - $set: { - [`banners.${ banner.id }`]: banner, - }, - }; - - return this.update({ _id }, update); - } - - removeBannerById(_id, banner) { - const update = { - $unset: { - [`banners.${ banner.id }`]: true, - }, - }; - - return this.update({ _id }, update); - } - - removeResumeService(_id) { - const update = { - $unset: { - 'services.resume': '', - }, - }; - - return this.update({ _id }, update); - } - - // INSERT - create(data) { - const user = { - createdAt: new Date, - avatarOrigin: 'none', - }; - - _.extend(user, data); - - return this.insert(user); - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } - - /* -Find users to send a message by email if: -- he is not online -- has a verified email -- has not disabled email notifications -- `active` is equal to true (false means they were deactivated and can't login) -*/ - getUsersToSendOfflineEmail(usersIds) { - const query = { - _id: { - $in: usersIds, - }, - active: true, - status: 'offline', - statusConnection: { - $ne: 'online', - }, - 'emails.verified': true, - }; - - const options = { - fields: { - name: 1, - username: 1, - emails: 1, - 'settings.preferences.emailNotificationMode': 1, - language: 1, - }, - }; - - return this.find(query, options); - } -} - -RocketChat.models.Users = new ModelUsers(Meteor.users, true); diff --git a/packages/rocketchat-lib/server/models/_Base.js b/packages/rocketchat-lib/server/models/_Base.js index d09b91760b07..bfda6cf65cce 100644 --- a/packages/rocketchat-lib/server/models/_Base.js +++ b/packages/rocketchat-lib/server/models/_Base.js @@ -1,285 +1,3 @@ -import { check } from 'meteor/check'; -import ModelsBaseDb from './_BaseDb'; -import objectPath from 'object-path'; -import _ from 'underscore'; +import { Base } from 'meteor/rocketchat:models'; -class ModelsBase { - constructor(nameOrModel) { - this._db = new ModelsBaseDb(nameOrModel, this); - this.model = this._db.model; - this.collectionName = this._db.collectionName; - this.name = this._db.name; - - this.on = this._db.on.bind(this._db); - this.emit = this._db.emit.bind(this._db); - - this.db = this; - } - - get origin() { - return '_db'; - } - - arrayToCursor(data) { - return { - fetch() { - return data; - }, - count() { - return data.length; - }, - forEach(fn) { - return data.forEach(fn); - }, - }; - } - - setUpdatedAt(...args/* record, checkQuery, query*/) { - return this._db.setUpdatedAt(...args); - } - - find(...args) { - try { - return this[this.origin].find(...args); - } catch (e) { - console.error('Exception on find', e, ...args); - } - } - - findOne(...args) { - try { - return this[this.origin].findOne(...args); - } catch (e) { - console.error('Exception on find', e, ...args); - } - } - - findOneById(...args) { - try { - return this[this.origin].findOneById(...args); - } catch (e) { - console.error('Exception on find', e, ...args); - } - } - - findOneByIds(ids, options, ...args) { - check(ids, [String]); - - try { - return this[this.origin].findOneByIds(ids, options); - } catch (e) { - console.error('Exception on find', e, [ids, options, ...args]); - } - } - - insert(...args/* record*/) { - return this._db.insert(...args); - } - - update(...args/* query, update, options*/) { - return this._db.update(...args); - } - - upsert(...args/* query, update*/) { - return this._db.upsert(...args); - } - - remove(...args/* query*/) { - return this._db.remove(...args); - } - - insertOrUpsert(...args) { - return this._db.insertOrUpsert(...args); - } - - allow(...args) { - return this._db.allow(...args); - } - - deny(...args) { - return this._db.deny(...args); - } - - ensureIndex(...args) { - return this._db.ensureIndex(...args); - } - - dropIndex(...args) { - return this._db.dropIndex(...args); - } - - tryEnsureIndex(...args) { - return this._db.tryEnsureIndex(...args); - } - - tryDropIndex(...args) { - return this._db.tryDropIndex(...args); - } - - trashFind(...args/* query, options*/) { - return this._db.trashFind(...args); - } - - trashFindOneById(...args/* _id, options*/) { - return this._db.trashFindOneById(...args); - } - - trashFindDeletedAfter(...args/* deletedAt, query, options*/) { - return this._db.trashFindDeletedAfter(...args); - } - - trashFindDeleted(...args) { - return this._db.trashFindDeleted(...args); - } - - processQueryOptionsOnResult(result, options = {}) { - if (result === undefined || result === null) { - return undefined; - } - - if (Array.isArray(result)) { - if (options.sort) { - result = result.sort((a, b) => { - let r = 0; - for (const field in options.sort) { - if (options.sort.hasOwnProperty(field)) { - const direction = options.sort[field]; - let valueA; - let valueB; - if (field.indexOf('.') > -1) { - valueA = objectPath.get(a, field); - valueB = objectPath.get(b, field); - } else { - valueA = a[field]; - valueB = b[field]; - } - if (valueA > valueB) { - r = direction; - break; - } - if (valueA < valueB) { - r = -direction; - break; - } - } - } - return r; - }); - } - - if (typeof options.skip === 'number') { - result.splice(0, options.skip); - } - - if (typeof options.limit === 'number' && options.limit !== 0) { - result.splice(options.limit); - } - } - - if (!options.fields) { - options.fields = {}; - } - - const fieldsToRemove = []; - const fieldsToGet = []; - - for (const field in options.fields) { - if (options.fields.hasOwnProperty(field)) { - if (options.fields[field] === 0) { - fieldsToRemove.push(field); - } else if (options.fields[field] === 1) { - fieldsToGet.push(field); - } - } - } - - if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) { - console.warn('Can\'t mix remove and get fields'); - fieldsToRemove.splice(0, fieldsToRemove.length); - } - - if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) { - fieldsToGet.push('_id'); - } - - const pickFields = (obj, fields) => { - const picked = {}; - fields.forEach((field) => { - if (field.indexOf('.') !== -1) { - objectPath.set(picked, field, objectPath.get(obj, field)); - } else { - picked[field] = obj[field]; - } - }); - return picked; - }; - - if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) { - if (Array.isArray(result)) { - result = result.map((record) => { - if (fieldsToRemove.length > 0) { - return _.omit(record, ...fieldsToRemove); - } - - if (fieldsToGet.length > 0) { - return pickFields(record, fieldsToGet); - } - - return null; - }); - } else { - if (fieldsToRemove.length > 0) { - return _.omit(result, ...fieldsToRemove); - } - - if (fieldsToGet.length > 0) { - return pickFields(result, fieldsToGet); - } - } - } - - return result; - } - - // dinamicTrashFindAfter(method, deletedAt, ...args) { - // const scope = { - // find: (query={}) => { - // return this.trashFindDeletedAfter(deletedAt, query, { fields: {_id: 1, _deletedAt: 1} }); - // } - // }; - - // scope.model = { - // find: scope.find - // }; - - // return this[method].apply(scope, args); - // } - - // dinamicFindAfter(method, updatedAt, ...args) { - // const scope = { - // find: (query={}, options) => { - // query._updatedAt = { - // $gt: updatedAt - // }; - - // return this.find(query, options); - // } - // }; - - // scope.model = { - // find: scope.find - // }; - - // return this[method].apply(scope, args); - // } - - // dinamicFindChangesAfter(method, updatedAt, ...args) { - // return { - // update: this.dinamicFindAfter(method, updatedAt, ...args).fetch(), - // remove: this.dinamicTrashFindAfter(method, updatedAt, ...args).fetch() - // }; - // } - -} - -RocketChat.models._Base = ModelsBase; +RocketChat.models._Base = Base; diff --git a/packages/rocketchat-models/client/index.js b/packages/rocketchat-models/client/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/rocketchat-models/package.js b/packages/rocketchat-models/package.js new file mode 100755 index 000000000000..780626a8c855 --- /dev/null +++ b/packages/rocketchat-models/package.js @@ -0,0 +1,17 @@ +Package.describe({ + name: 'rocketchat:models', + summary: 'RocketChat Models', + version: '1.0.0', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:settings', + 'rocketchat:utils', + 'konecty:multiple-instances-status', + ]); + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-models/server/index.js b/packages/rocketchat-models/server/index.js new file mode 100644 index 000000000000..372205cdf8fa --- /dev/null +++ b/packages/rocketchat-models/server/index.js @@ -0,0 +1,27 @@ +import { Base } from './models/_Base'; +import { BaseDb } from './models/_BaseDb'; +import Avatars from './models/Avatars'; +import ExportOperations from './models/ExportOperations'; +import Messages from './models/Messages'; +import Reports from './models/Reports'; +import Rooms from './models/Rooms'; +import Settings from './models/Settings'; +import Subscriptions from './models/Subscriptions'; +import Uploads from './models/Uploads'; +import UserDataFiles from './models/UserDataFiles'; +import Users from './models/Users'; + +export { + Base, + BaseDb, + Avatars, + ExportOperations, + Messages, + Reports, + Rooms, + Settings, + Subscriptions, + Uploads, + UserDataFiles, + Users, +}; diff --git a/packages/rocketchat-models/server/models/Avatars.js b/packages/rocketchat-models/server/models/Avatars.js new file mode 100644 index 000000000000..1859f3d29a6d --- /dev/null +++ b/packages/rocketchat-models/server/models/Avatars.js @@ -0,0 +1,116 @@ +import _ from 'underscore'; +import s from 'underscore.string'; +import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; +import { Base } from './_Base'; + +export class Avatars extends Base { + constructor() { + super('avatars'); + + this.model.before.insert((userId, doc) => { + doc.instanceId = InstanceStatus.id(); + }); + + this.tryEnsureIndex({ name: 1 }); + } + + insertAvatarFileInit(name, userId, store, file, extra) { + const fileData = { + _id: name, + name, + userId, + store, + complete: false, + uploading: true, + progress: 0, + extension: s.strRightBack(file.name, '.'), + uploadedAt: new Date(), + }; + + _.extend(fileData, file, extra); + + return this.insertOrUpsert(fileData); + } + + updateFileComplete(fileId, userId, file) { + if (!fileId) { + return; + } + + const filter = { + _id: fileId, + userId, + }; + + const update = { + $set: { + complete: true, + uploading: false, + progress: 1, + }, + }; + + update.$set = _.extend(file, update.$set); + + if (this.model.direct && this.model.direct.update) { + return this.model.direct.update(filter, update); + } else { + return this.update(filter, update); + } + } + + findOneByName(name) { + return this.findOne({ name }); + } + + updateFileNameById(fileId, name) { + const filter = { _id: fileId }; + const update = { + $set: { + name, + }, + }; + if (this.model.direct && this.model.direct.update) { + return this.model.direct.update(filter, update); + } else { + return this.update(filter, update); + } + } + + // @TODO deprecated + updateFileCompleteByNameAndUserId(name, userId, url) { + if (!name) { + return; + } + + const filter = { + name, + userId, + }; + + const update = { + $set: { + complete: true, + uploading: false, + progress: 1, + url, + }, + }; + + if (this.model.direct && this.model.direct.update) { + return this.model.direct.update(filter, update); + } else { + return this.update(filter, update); + } + } + + deleteFile(fileId) { + if (this.model.direct && this.model.direct.remove) { + return this.model.direct.remove({ _id: fileId }); + } else { + return this.remove({ _id: fileId }); + } + } +} + +export default new Avatars(); diff --git a/packages/rocketchat-models/server/models/ExportOperations.js b/packages/rocketchat-models/server/models/ExportOperations.js new file mode 100644 index 000000000000..5a0d1dc60f6a --- /dev/null +++ b/packages/rocketchat-models/server/models/ExportOperations.js @@ -0,0 +1,81 @@ +import { Base } from './_Base'; +import _ from 'underscore'; + +export class ExportOperations extends Base { + constructor() { + super('export_operations'); + + this.tryEnsureIndex({ userId: 1 }); + this.tryEnsureIndex({ status: 1 }); + } + + // FIND + findById(id) { + const query = { _id: id }; + + return this.find(query); + } + + findLastOperationByUser(userId, fullExport = false, options = {}) { + const query = { + userId, + fullExport, + }; + + options.sort = { createdAt : -1 }; + return this.findOne(query, options); + } + + findPendingByUser(userId, options) { + const query = { + userId, + status: { + $nin: ['completed'], + }, + }; + + return this.find(query, options); + } + + findAllPending(options) { + const query = { + status: { $nin: ['completed'] }, + }; + + return this.find(query, options); + } + + // UPDATE + updateOperation(data) { + const update = { + $set: { + roomList: data.roomList, + status: data.status, + fileList: data.fileList, + generatedFile: data.generatedFile, + }, + }; + + return this.update(data._id, update); + } + + + // INSERT + create(data) { + const exportOperation = { + createdAt: new Date, + }; + + _.extend(exportOperation, data); + + return this.insert(exportOperation); + } + + + // REMOVE + removeById(_id) { + return this.remove(_id); + } +} + +export default new ExportOperations(); diff --git a/packages/rocketchat-models/server/models/Messages.js b/packages/rocketchat-models/server/models/Messages.js new file mode 100644 index 000000000000..f7993db20d48 --- /dev/null +++ b/packages/rocketchat-models/server/models/Messages.js @@ -0,0 +1,839 @@ +import { Meteor } from 'meteor/meteor'; +import { Match } from 'meteor/check'; +import { settings } from 'meteor/rocketchat:settings'; +import { Base } from './_Base'; +import Rooms from './Rooms'; +import Users from './Users'; +import _ from 'underscore'; + +export class Messages extends Base { + constructor() { + super('message'); + + this.tryEnsureIndex({ rid: 1, ts: 1 }); + this.tryEnsureIndex({ ts: 1 }); + this.tryEnsureIndex({ 'u._id': 1 }); + this.tryEnsureIndex({ editedAt: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ 'editedBy._id': 1 }, { sparse: 1 }); + this.tryEnsureIndex({ rid: 1, t: 1, 'u._id': 1 }); + this.tryEnsureIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }); + this.tryEnsureIndex({ msg: 'text' }); + this.tryEnsureIndex({ 'file._id': 1 }, { sparse: 1 }); + this.tryEnsureIndex({ 'mentions.username': 1 }, { sparse: 1 }); + this.tryEnsureIndex({ pinned: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ snippeted: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ location: '2dsphere' }); + this.tryEnsureIndex({ slackBotId: 1, slackTs: 1 }, { sparse: 1 }); + } + + countVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $gte: afterTimestamp, + $lte: beforeTimestamp, + }, + }; + + return this.find(query, options).count(); + } + + // FIND + findByMention(username, options) { + const query = { 'mentions.username': username }; + + return this.find(query, options); + } + + findFilesByUserId(userId, options = {}) { + const query = { + 'u._id': userId, + 'file._id': { $exists: true }, + }; + return this.find(query, { fields: { 'file._id': 1 }, ...options }); + } + + findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ts, users = [], options = {}) { + const query = { + rid, + ts, + 'file._id': { $exists: true }, + }; + + if (excludePinned) { + query.pinned = { $ne: true }; + } + + if (users.length) { + query['u.username'] = { $in: users }; + } + + return this.find(query, { fields: { 'file._id': 1 }, ...options }); + } + findVisibleByMentionAndRoomId(username, rid, options) { + const query = { + _hidden: { $ne: true }, + 'mentions.username': username, + rid, + }; + + return this.find(query, options); + } + + findVisibleByRoomId(roomId, options) { + const query = { + _hidden: { + $ne: true, + }, + + rid: roomId, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdNotContainingTypes(roomId, types, options) { + const query = { + _hidden: { + $ne: true, + }, + + rid: roomId, + }; + + if (Match.test(types, [String]) && (types.length > 0)) { + query.t = + { $nin: types }; + } + + return this.find(query, options); + } + + findInvisibleByRoomId(roomId, options) { + const query = { + _hidden: true, + rid: roomId, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdAfterTimestamp(roomId, timestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $gt: timestamp, + }, + }; + + return this.find(query, options); + } + + findForUpdates(roomId, timestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + _updatedAt: { + $gt: timestamp, + }, + }; + return this.find(query, options); + } + + findVisibleByRoomIdBeforeTimestamp(roomId, timestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $lt: timestamp, + }, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdBeforeTimestampInclusive(roomId, timestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $lte: timestamp, + }, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdBetweenTimestamps(roomId, afterTimestamp, beforeTimestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $gt: afterTimestamp, + $lt: beforeTimestamp, + }, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $gte: afterTimestamp, + $lte: beforeTimestamp, + }, + }; + + return this.find(query, options); + } + + findVisibleByRoomIdBeforeTimestampNotContainingTypes(roomId, timestamp, types, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $lt: timestamp, + }, + }; + + if (Match.test(types, [String]) && (types.length > 0)) { + query.t = + { $nin: types }; + } + + return this.find(query, options); + } + + findVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, afterTimestamp, beforeTimestamp, types, options) { + const query = { + _hidden: { + $ne: true, + }, + rid: roomId, + ts: { + $gt: afterTimestamp, + $lt: beforeTimestamp, + }, + }; + + if (Match.test(types, [String]) && (types.length > 0)) { + query.t = + { $nin: types }; + } + + return this.find(query, options); + } + + findVisibleCreatedOrEditedAfterTimestamp(timestamp, options) { + const query = { + _hidden: { $ne: true }, + $or: [{ + ts: { + $gt: timestamp, + }, + }, + { + editedAt: { + $gt: timestamp, + }, + }, + ], + }; + + return this.find(query, options); + } + + findStarredByUserAtRoom(userId, roomId, options) { + const query = { + _hidden: { $ne: true }, + 'starred._id': userId, + rid: roomId, + }; + + return this.find(query, options); + } + + findPinnedByRoom(roomId, options) { + const query = { + t: { $ne: 'rm' }, + _hidden: { $ne: true }, + pinned: true, + rid: roomId, + }; + + return this.find(query, options); + } + + findSnippetedByRoom(roomId, options) { + const query = { + _hidden: { $ne: true }, + snippeted: true, + rid: roomId, + }; + + return this.find(query, options); + } + + getLastTimestamp(options) { + if (options == null) { options = {}; } + const query = { ts: { $exists: 1 } }; + options.sort = { ts: -1 }; + options.limit = 1; + const [message] = this.find(query, options).fetch(); + return message && message.ts; + } + + findByRoomIdAndMessageIds(rid, messageIds, options) { + const query = { + rid, + _id: { + $in: messageIds, + }, + }; + + return this.find(query, options); + } + + findOneBySlackBotIdAndSlackTs(slackBotId, slackTs) { + const query = { + slackBotId, + slackTs, + }; + + return this.findOne(query); + } + + findOneBySlackTs(slackTs) { + const query = { slackTs }; + + return this.findOne(query); + } + + findByRoomIdAndType(roomId, type, options) { + const query = { + rid: roomId, + t: type, + }; + + if (options == null) { options = {}; } + + return this.find(query, options); + } + + findByRoomId(roomId, options) { + const query = { + rid: roomId, + }; + + return this.find(query, options); + } + + getLastVisibleMessageSentWithNoTypeByRoomId(rid, messageId) { + const query = { + rid, + _hidden: { $ne: true }, + t: { $exists: false }, + }; + + if (messageId) { + query._id = { $ne: messageId }; + } + + const options = { + sort: { + ts: -1, + }, + }; + + return this.findOne(query, options); + } + + cloneAndSaveAsHistoryById(_id) { + const me = Users.findOneById(Meteor.userId()); + const record = this.findOneById(_id); + record._hidden = true; + record.parent = record._id; + record.editedAt = new Date; + record.editedBy = { + _id: Meteor.userId(), + username: me.username, + }; + delete record._id; + return this.insert(record); + } + + // UPDATE + setHiddenById(_id, hidden) { + if (hidden == null) { hidden = true; } + const query = { _id }; + + const update = { + $set: { + _hidden: hidden, + }, + }; + + return this.update(query, update); + } + + setAsDeletedByIdAndUser(_id, user) { + const query = { _id }; + + const update = { + $set: { + msg: '', + t: 'rm', + urls: [], + mentions: [], + attachments: [], + reactions: [], + editedAt: new Date(), + editedBy: { + _id: user._id, + username: user.username, + }, + }, + }; + + return this.update(query, update); + } + + setPinnedByIdAndUserId(_id, pinnedBy, pinned, pinnedAt) { + if (pinned == null) { pinned = true; } + if (pinnedAt == null) { pinnedAt = 0; } + const query = { _id }; + + const update = { + $set: { + pinned, + pinnedAt: pinnedAt || new Date, + pinnedBy, + }, + }; + + return this.update(query, update); + } + + setSnippetedByIdAndUserId(message, snippetName, snippetedBy, snippeted, snippetedAt) { + if (snippeted == null) { snippeted = true; } + if (snippetedAt == null) { snippetedAt = 0; } + const query = { _id: message._id }; + + const msg = `\`\`\`${ message.msg }\`\`\``; + + const update = { + $set: { + msg, + snippeted, + snippetedAt: snippetedAt || new Date, + snippetedBy, + snippetName, + }, + }; + + return this.update(query, update); + } + + setUrlsById(_id, urls) { + const query = { _id }; + + const update = { + $set: { + urls, + }, + }; + + return this.update(query, update); + } + + updateAllUsernamesByUserId(userId, username) { + const query = { 'u._id': userId }; + + const update = { + $set: { + 'u.username': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateUsernameOfEditByUserId(userId, username) { + const query = { 'editedBy._id': userId }; + + const update = { + $set: { + 'editedBy.username': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateUsernameAndMessageOfMentionByIdAndOldUsername(_id, oldUsername, newUsername, newMessage) { + const query = { + _id, + 'mentions.username': oldUsername, + }; + + const update = { + $set: { + 'mentions.$.username': newUsername, + msg: newMessage, + }, + }; + + return this.update(query, update); + } + + updateUserStarById(_id, userId, starred) { + let update; + const query = { _id }; + + if (starred) { + update = { + $addToSet: { + starred: { _id: userId }, + }, + }; + } else { + update = { + $pull: { + starred: { _id: Meteor.userId() }, + }, + }; + } + + return this.update(query, update); + } + + upgradeEtsToEditAt() { + const query = { ets: { $exists: 1 } }; + + const update = { + $rename: { + ets: 'editedAt', + }, + }; + + return this.update(query, update, { multi: true }); + } + + setMessageAttachments(_id, attachments) { + const query = { _id }; + + const update = { + $set: { + attachments, + }, + }; + + return this.update(query, update); + } + + setSlackBotIdAndSlackTs(_id, slackBotId, slackTs) { + const query = { _id }; + + const update = { + $set: { + slackBotId, + slackTs, + }, + }; + + return this.update(query, update); + } + + unlinkUserId(userId, newUserId, newUsername, newNameAlias) { + const query = { + 'u._id': userId, + }; + + const update = { + $set: { + alias: newNameAlias, + 'u._id': newUserId, + 'u.username' : newUsername, + 'u.name' : undefined, + }, + }; + + return this.update(query, update, { multi: true }); + } + + // INSERT + createWithTypeRoomIdMessageAndUser(type, roomId, message, user, extraData) { + const room = Rooms.findOneById(roomId, { fields: { sysMes: 1 } }); + if ((room != null ? room.sysMes : undefined) === false) { + return; + } + const record = { + t: type, + rid: roomId, + ts: new Date, + msg: message, + u: { + _id: user._id, + username: user.username, + }, + groupable: false, + }; + + if (settings.get('Message_Read_Receipt_Enabled')) { + record.unread = true; + } + + _.extend(record, extraData); + + record._id = this.insertOrUpsert(record); + Rooms.incMsgCountById(room._id, 1); + return record; + } + + createNavigationHistoryWithRoomIdMessageAndUser(roomId, message, user, extraData) { + const type = 'livechat_navigation_history'; + const room = Rooms.findOneById(roomId, { fields: { sysMes: 1 } }); + if ((room != null ? room.sysMes : undefined) === false) { + return; + } + const record = { + t: type, + rid: roomId, + ts: new Date, + msg: message, + u: { + _id: user._id, + username: user.username, + }, + groupable: false, + }; + + if (settings.get('Message_Read_Receipt_Enabled')) { + record.unread = true; + } + + _.extend(record, extraData); + + record._id = this.insertOrUpsert(record); + return record; + } + + createUserJoinWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('uj', roomId, message, user, extraData); + } + + createUserLeaveWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('ul', roomId, message, user, extraData); + } + + createUserRemovedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('ru', roomId, message, user, extraData); + } + + createUserAddedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('au', roomId, message, user, extraData); + } + + createCommandWithRoomIdAndUser(command, roomId, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('command', roomId, command, user, extraData); + } + + createUserMutedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('user-muted', roomId, message, user, extraData); + } + + createUserUnmutedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('user-unmuted', roomId, message, user, extraData); + } + + createNewModeratorWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('new-moderator', roomId, message, user, extraData); + } + + createModeratorRemovedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('moderator-removed', roomId, message, user, extraData); + } + + createNewOwnerWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('new-owner', roomId, message, user, extraData); + } + + createOwnerRemovedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('owner-removed', roomId, message, user, extraData); + } + + createNewLeaderWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('new-leader', roomId, message, user, extraData); + } + + createLeaderRemovedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('leader-removed', roomId, message, user, extraData); + } + + createSubscriptionRoleAddedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('subscription-role-added', roomId, message, user, extraData); + } + + createSubscriptionRoleRemovedWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('subscription-role-removed', roomId, message, user, extraData); + } + + // REMOVE + removeById(_id) { + const query = { _id }; + + return this.remove(query); + } + + removeByRoomId(roomId) { + const query = { rid: roomId }; + + return this.remove(query); + } + + removeByIdPinnedTimestampAndUsers(rid, pinned, ts, users = []) { + const query = { + rid, + ts, + }; + + if (pinned) { + query.pinned = { $ne: true }; + } + + if (users.length) { + query['u.username'] = { $in: users }; + } + + return this.remove(query); + } + + removeByIdPinnedTimestampLimitAndUsers(rid, pinned, ts, limit, users = []) { + const query = { + rid, + ts, + }; + + if (pinned) { + query.pinned = { $ne: true }; + } + + if (users.length) { + query['u.username'] = { $in: users }; + } + + const messagesToDelete = this.find(query, { + fields: { + _id: 1, + }, + limit, + }).map(({ _id }) => _id); + + return this.remove({ + _id: { + $in: messagesToDelete, + }, + }); + } + + removeByUserId(userId) { + const query = { 'u._id': userId }; + + return this.remove(query); + } + + removeFilesByRoomId(roomId) { + this.find({ + rid: roomId, + 'file._id': { + $exists: true, + }, + }, { + fields: { + 'file._id': 1, + }, + }).fetch().forEach((document) => FileUpload.getStore('Uploads').deleteById(document.file._id)); + } + + getMessageByFileId(fileID) { + return this.findOne({ 'file._id': fileID }); + } + + setAsRead(rid, until) { + return this.update({ + rid, + unread: true, + ts: { $lt: until }, + }, { + $unset: { + unread: 1, + }, + }, { + multi: true, + }); + } + + setAsReadById(_id) { + return this.update({ + _id, + }, { + $unset: { + unread: 1, + }, + }); + } + + findUnreadMessagesByRoomAndDate(rid, after) { + const query = { + unread: true, + rid, + }; + + if (after) { + query.ts = { $gt: after }; + } + + return this.find(query, { + fields: { + _id: 1, + }, + }); + } +} + +export default new Messages(); diff --git a/packages/rocketchat-models/server/models/Reports.js b/packages/rocketchat-models/server/models/Reports.js new file mode 100644 index 000000000000..09fd9dc6da98 --- /dev/null +++ b/packages/rocketchat-models/server/models/Reports.js @@ -0,0 +1,21 @@ +import { Base } from './_Base'; +import _ from 'underscore'; + +export class Reports extends Base { + constructor() { + super('reports'); + } + createWithMessageDescriptionAndUserId(message, description, userId, extraData) { + const record = { + message, + description, + ts: new Date(), + userId, + }; + _.extend(record, extraData); + record._id = this.insert(record); + return record; + } +} + +export default new Reports(); diff --git a/packages/rocketchat-models/server/models/Rooms.js b/packages/rocketchat-models/server/models/Rooms.js new file mode 100644 index 000000000000..3b0a31c908a5 --- /dev/null +++ b/packages/rocketchat-models/server/models/Rooms.js @@ -0,0 +1,809 @@ +import { Base } from './_Base'; +import Messages from './Messages'; +import Subscriptions from './Subscriptions'; +import _ from 'underscore'; +import s from 'underscore.string'; + +export class Rooms extends Base { + constructor(...args) { + super(...args); + + this.tryEnsureIndex({ name: 1 }, { unique: 1, sparse: 1 }); + this.tryEnsureIndex({ default: 1 }); + this.tryEnsureIndex({ t: 1 }); + this.tryEnsureIndex({ 'u._id': 1 }); + } + + findOneByIdOrName(_idOrName, options) { + const query = { + $or: [{ + _id: _idOrName, + }, { + name: _idOrName, + }], + }; + + return this.findOne(query, options); + } + + findOneByImportId(_id, options) { + const query = { importIds: _id }; + + return this.findOne(query, options); + } + + findOneByName(name, options) { + const query = { name }; + + return this.findOne(query, options); + } + + findOneByNameAndNotId(name, rid) { + const query = { + _id: { $ne: rid }, + name, + }; + + return this.findOne(query); + } + + findOneByDisplayName(fname, options) { + const query = { fname }; + + return this.findOne(query, options); + } + + findOneByNameAndType(name, type, options) { + const query = { + name, + t: type, + }; + + return this.findOne(query, options); + } + + // FIND + + findById(roomId, options) { + return this.find({ _id: roomId }, options); + } + + findByIds(roomIds, options) { + return this.find({ _id: { $in: [].concat(roomIds) } }, options); + } + + findByType(type, options) { + const query = { t: type }; + + return this.find(query, options); + } + + findByTypeInIds(type, ids, options) { + const query = { + _id: { + $in: ids, + }, + t: type, + }; + + return this.find(query, options); + } + + findByTypes(types, options) { + const query = { + t: { + $in: types, + }, + }; + + return this.find(query, options); + } + + findByUserId(userId, options) { + const query = { 'u._id': userId }; + + return this.find(query, options); + } + + findBySubscriptionUserId(userId, options) { + const data = Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() + .map((item) => item.rid); + + const query = { + _id: { + $in: data, + }, + }; + + return this.find(query, options); + } + + findBySubscriptionTypeAndUserId(type, userId, options) { + const data = Subscriptions.findByUserIdAndType(userId, type, { fields: { rid: 1 } }).fetch() + .map((item) => item.rid); + + const query = { + t: type, + _id: { + $in: data, + }, + }; + + return this.find(query, options); + } + + findBySubscriptionUserIdUpdatedAfter(userId, _updatedAt, options) { + const ids = Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() + .map((item) => item.rid); + + const query = { + _id: { + $in: ids, + }, + _updatedAt: { + $gt: _updatedAt, + }, + }; + + return this.find(query, options); + } + + findByNameContaining(name, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); + + const query = { + $or: [ + { name: nameRegex }, + { + t: 'd', + usernames: nameRegex, + }, + ], + }; + + return this.find(query, options); + } + + findByNameContainingAndTypes(name, types, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); + + const query = { + t: { + $in: types, + }, + $or: [ + { name: nameRegex }, + { + t: 'd', + usernames: nameRegex, + }, + ], + }; + + return this.find(query, options); + } + + findByNameAndType(name, type, options) { + const query = { + t: type, + name, + }; + + // do not use cache + return this._db.find(query, options); + } + + findByNameAndTypeNotDefault(name, type, options) { + const query = { + t: type, + name, + default: { + $ne: true, + }, + }; + + // do not use cache + return this._db.find(query, options); + } + + findByNameAndTypesNotInIds(name, types, ids, options) { + const query = { + _id: { + $ne: ids, + }, + t: { + $in: types, + }, + name, + }; + + // do not use cache + return this._db.find(query, options); + } + + findChannelAndPrivateByNameStarting(name, options) { + const nameRegex = new RegExp(`^${ s.trim(s.escapeRegExp(name)) }`, 'i'); + + const query = { + t: { + $in: ['c', 'p'], + }, + name: nameRegex, + }; + + return this.find(query, options); + } + + findByDefaultAndTypes(defaultValue, types, options) { + const query = { + default: defaultValue, + t: { + $in: types, + }, + }; + + return this.find(query, options); + } + + findDirectRoomContainingUsername(username, options) { + const query = { + t: 'd', + usernames: username, + }; + + return this.find(query, options); + } + + findDirectRoomContainingAllUsernames(usernames, options) { + const query = { + t: 'd', + usernames: { $size: usernames.length, $all: usernames }, + }; + + return this.findOne(query, options); + } + + findByTypeAndName(type, name, options) { + const query = { + name, + t: type, + }; + + return this.find(query, options); + } + + findByTypeAndNameContaining(type, name, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); + + const query = { + name: nameRegex, + t: type, + }; + + return this.find(query, options); + } + + findByTypeInIdsAndNameContaining(type, ids, name, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); + + const query = { + _id: { + $in: ids, + }, + name: nameRegex, + t: type, + }; + + return this.find(query, options); + } + + findByTypeAndArchivationState(type, archivationstate, options) { + const query = { t: type }; + + if (archivationstate) { + query.archived = true; + } else { + query.archived = { $ne: true }; + } + + return this.find(query, options); + } + + // UPDATE + addImportIds(_id, importIds) { + importIds = [].concat(importIds); + const query = { _id }; + + const update = { + $addToSet: { + importIds: { + $each: importIds, + }, + }, + }; + + return this.update(query, update); + } + + archiveById(_id) { + const query = { _id }; + + const update = { + $set: { + archived: true, + }, + }; + + return this.update(query, update); + } + + unarchiveById(_id) { + const query = { _id }; + + const update = { + $set: { + archived: false, + }, + }; + + return this.update(query, update); + } + + setNameById(_id, name, fname) { + const query = { _id }; + + const update = { + $set: { + name, + fname, + }, + }; + + return this.update(query, update); + } + + setFnameById(_id, fname) { + const query = { _id }; + + const update = { + $set: { + fname, + }, + }; + + return this.update(query, update); + } + + incMsgCountById(_id, inc) { + if (inc == null) { inc = 1; } + const query = { _id }; + + const update = { + $inc: { + msgs: inc, + }, + }; + + return this.update(query, update); + } + + incMsgCountAndSetLastMessageById(_id, inc, lastMessageTimestamp, lastMessage) { + if (inc == null) { inc = 1; } + const query = { _id }; + + const update = { + $set: { + lm: lastMessageTimestamp, + }, + $inc: { + msgs: inc, + }, + }; + + if (lastMessage) { + update.$set.lastMessage = lastMessage; + } + + return this.update(query, update); + } + + incUsersCountById(_id, inc = 1) { + const query = { _id }; + + const update = { + $inc: { + usersCount: inc, + }, + }; + + return this.update(query, update); + } + + incUsersCountByIds(ids, inc = 1) { + const query = { + _id: { + $in: ids, + }, + }; + + const update = { + $inc: { + usersCount: inc, + }, + }; + + return this.update(query, update, { multi: true }); + } + + setLastMessageById(_id, lastMessage) { + const query = { _id }; + + const update = { + $set: { + lastMessage, + }, + }; + + return this.update(query, update); + } + + resetLastMessageById(_id, messageId) { + const query = { _id }; + const lastMessage = Messages.getLastVisibleMessageSentWithNoTypeByRoomId(_id, messageId); + + const update = lastMessage ? { + $set: { + lastMessage, + }, + } : { + $unset: { + lastMessage: 1, + }, + }; + + return this.update(query, update); + } + + replaceUsername(previousUsername, username) { + const query = { usernames: previousUsername }; + + const update = { + $set: { + 'usernames.$': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + replaceMutedUsername(previousUsername, username) { + const query = { muted: previousUsername }; + + const update = { + $set: { + 'muted.$': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + replaceUsernameOfUserByUserId(userId, username) { + const query = { 'u._id': userId }; + + const update = { + $set: { + 'u.username': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + setJoinCodeById(_id, joinCode) { + let update; + const query = { _id }; + + if ((joinCode != null ? joinCode.trim() : undefined) !== '') { + update = { + $set: { + joinCodeRequired: true, + joinCode, + }, + }; + } else { + update = { + $set: { + joinCodeRequired: false, + }, + $unset: { + joinCode: 1, + }, + }; + } + + return this.update(query, update); + } + + setUserById(_id, user) { + const query = { _id }; + + const update = { + $set: { + u: { + _id: user._id, + username: user.username, + }, + }, + }; + + return this.update(query, update); + } + + setTypeById(_id, type) { + const query = { _id }; + const update = { + $set: { + t: type, + }, + }; + if (type === 'p') { + update.$unset = { default: '' }; + } + + return this.update(query, update); + } + + setTopicById(_id, topic) { + const query = { _id }; + + const update = { + $set: { + topic, + }, + }; + + return this.update(query, update); + } + + setAnnouncementById(_id, announcement, announcementDetails) { + const query = { _id }; + + const update = { + $set: { + announcement, + announcementDetails, + }, + }; + + return this.update(query, update); + } + + setCustomFieldsById(_id, customFields) { + const query = { _id }; + + const update = { + $set: { + customFields, + }, + }; + + return this.update(query, update); + } + + muteUsernameByRoomId(_id, username) { + const query = { _id }; + + const update = { + $addToSet: { + muted: username, + }, + }; + + return this.update(query, update); + } + + unmuteUsernameByRoomId(_id, username) { + const query = { _id }; + + const update = { + $pull: { + muted: username, + }, + }; + + return this.update(query, update); + } + + saveDefaultById(_id, defaultValue) { + const query = { _id }; + + const update = { + $set: { + default: defaultValue === 'true', + }, + }; + + return this.update(query, update); + } + + saveRetentionEnabledById(_id, value) { + const query = { _id }; + + const update = {}; + + if (value == null) { + update.$unset = { 'retention.enabled': true }; + } else { + update.$set = { 'retention.enabled': !!value }; + } + + return this.update(query, update); + } + + saveRetentionMaxAgeById(_id, value) { + const query = { _id }; + + value = Number(value); + if (!value) { + value = 30; + } + + const update = { + $set: { + 'retention.maxAge': value, + }, + }; + + return this.update(query, update); + } + + saveRetentionExcludePinnedById(_id, value) { + const query = { _id }; + + const update = { + $set: { + 'retention.excludePinned': value === true, + }, + }; + + return this.update(query, update); + } + + saveRetentionFilesOnlyById(_id, value) { + const query = { _id }; + + const update = { + $set: { + 'retention.filesOnly': value === true, + }, + }; + + return this.update(query, update); + } + + saveRetentionOverrideGlobalById(_id, value) { + const query = { _id }; + + const update = { + $set: { + 'retention.overrideGlobal': value === true, + }, + }; + + return this.update(query, update); + } + + saveEncryptedById(_id, value) { + const query = { _id }; + + const update = { + $set: { + encrypted: value === true, + }, + }; + + return this.update(query, update); + } + + setTopicAndTagsById(_id, topic, tags) { + const setData = {}; + const unsetData = {}; + + if (topic != null) { + if (!_.isEmpty(s.trim(topic))) { + setData.topic = s.trim(topic); + } else { + unsetData.topic = 1; + } + } + + if (tags != null) { + if (!_.isEmpty(s.trim(tags))) { + setData.tags = s.trim(tags).split(',').map((tag) => s.trim(tag)); + } else { + unsetData.tags = 1; + } + } + + const update = {}; + + if (!_.isEmpty(setData)) { + update.$set = setData; + } + + if (!_.isEmpty(unsetData)) { + update.$unset = unsetData; + } + + if (_.isEmpty(update)) { + return; + } + + return this.update({ _id }, update); + } + + // INSERT + createWithTypeNameUserAndUsernames(type, name, fname, user, usernames, extraData) { + const room = { + name, + fname, + t: type, + usernames, + msgs: 0, + usersCount: 0, + u: { + _id: user._id, + username: user.username, + }, + }; + + _.extend(room, extraData); + + room._id = this.insert(room); + return room; + } + + createWithIdTypeAndName(_id, type, name, extraData) { + const room = { + _id, + ts: new Date(), + t: type, + name, + usernames: [], + msgs: 0, + usersCount: 0, + }; + + _.extend(room, extraData); + + this.insert(room); + return room; + } + + createWithFullRoomData(room) { + delete room._id; + + room._id = this.insert(room); + return room; + } + + + // REMOVE + removeById(_id) { + const query = { _id }; + + return this.remove(query); + } + + removeDirectRoomContainingUsername(username) { + const query = { + t: 'd', + usernames: username, + }; + + return this.remove(query); + } +} + +export default new Rooms('rooms', true); diff --git a/packages/rocketchat-models/server/models/Settings.js b/packages/rocketchat-models/server/models/Settings.js new file mode 100644 index 000000000000..b692b26292e7 --- /dev/null +++ b/packages/rocketchat-models/server/models/Settings.js @@ -0,0 +1,184 @@ +import { Base } from './_Base'; + +export class Settings extends Base { + constructor(...args) { + super(...args); + + this.tryEnsureIndex({ blocked: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ hidden: 1 }, { sparse: 1 }); + } + + // FIND + findById(_id) { + const query = { _id }; + + return this.find(query); + } + + findOneNotHiddenById(_id) { + const query = { + _id, + hidden: { $ne: true }, + }; + + return this.findOne(query); + } + + findByIds(_id = []) { + _id = [].concat(_id); + + const query = { + _id: { + $in: _id, + }, + }; + + return this.find(query); + } + + findByRole(role, options) { + const query = { role }; + + return this.find(query, options); + } + + findPublic(options) { + const query = { public: true }; + + return this.find(query, options); + } + + findNotHiddenPublic(ids = []) { + const filter = { + hidden: { $ne: true }, + public: true, + }; + + if (ids.length > 0) { + filter._id = + { $in: ids }; + } + + return this.find(filter, { fields: { _id: 1, value: 1 } }); + } + + findNotHiddenPublicUpdatedAfter(updatedAt) { + const filter = { + hidden: { $ne: true }, + public: true, + _updatedAt: { + $gt: updatedAt, + }, + }; + + return this.find(filter, { fields: { _id: 1, value: 1 } }); + } + + findNotHiddenPrivate() { + return this.find({ + hidden: { $ne: true }, + public: { $ne: true }, + }); + } + + findNotHidden(options) { + return this.find({ hidden: { $ne: true } }, options); + } + + findNotHiddenUpdatedAfter(updatedAt) { + return this.find({ + hidden: { $ne: true }, + _updatedAt: { + $gt: updatedAt, + }, + }); + } + + findSetupWizardSettings() { + return this.find({ wizard: { $exists: true, $ne: null } }); + } + + // UPDATE + updateValueById(_id, value) { + const query = { + blocked: { $ne: true }, + value: { $ne: value }, + _id, + }; + + const update = { + $set: { + value, + }, + }; + + return this.update(query, update); + } + + updateValueAndEditorById(_id, value, editor) { + const query = { + blocked: { $ne: true }, + value: { $ne: value }, + _id, + }; + + const update = { + $set: { + value, + editor, + }, + }; + + return this.update(query, update); + } + + updateValueNotHiddenById(_id, value) { + const query = { + _id, + hidden: { $ne: true }, + blocked: { $ne: true }, + }; + + const update = { + $set: { + value, + }, + }; + + return this.update(query, update); + } + + updateOptionsById(_id, options) { + const query = { + blocked: { $ne: true }, + _id, + }; + + const update = { $set: options }; + + return this.update(query, update); + } + + // INSERT + createWithIdAndValue(_id, value) { + const record = { + _id, + value, + _createdAt: new Date, + }; + + return this.insert(record); + } + + // REMOVE + removeById(_id) { + const query = { + blocked: { $ne: true }, + _id, + }; + + return this.remove(query); + } +} + +export default new Settings('settings', true); diff --git a/packages/rocketchat-models/server/models/Subscriptions.js b/packages/rocketchat-models/server/models/Subscriptions.js new file mode 100644 index 000000000000..6e332eea851f --- /dev/null +++ b/packages/rocketchat-models/server/models/Subscriptions.js @@ -0,0 +1,853 @@ +import { Base } from './_Base'; +import { Match } from 'meteor/check'; +import Rooms from './Rooms'; +import { getDefaultSubscriptionPref } from 'meteor/rocketchat:utils'; + +export class Subscriptions extends Base { + constructor(...args) { + super(...args); + + this.tryEnsureIndex({ rid: 1, 'u._id': 1 }, { unique: 1 }); + this.tryEnsureIndex({ rid: 1, 'u.username': 1 }); + this.tryEnsureIndex({ rid: 1, alert: 1, 'u._id': 1 }); + this.tryEnsureIndex({ rid: 1, roles: 1 }); + this.tryEnsureIndex({ 'u._id': 1, name: 1, t: 1 }); + this.tryEnsureIndex({ open: 1 }); + this.tryEnsureIndex({ alert: 1 }); + + this.tryEnsureIndex({ rid: 1, 'u._id': 1, open: 1 }); + + this.tryEnsureIndex({ ts: 1 }); + this.tryEnsureIndex({ ls: 1 }); + this.tryEnsureIndex({ audioNotifications: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ desktopNotifications: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ mobilePushNotifications: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ emailNotifications: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ autoTranslate: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ autoTranslateLanguage: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ 'userHighlights.0': 1 }, { sparse: 1 }); + } + + + // FIND ONE + findOneByRoomIdAndUserId(roomId, userId, options) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + return this.findOne(query, options); + } + + findOneByRoomIdAndUsername(roomId, username, options) { + const query = { + rid: roomId, + 'u.username': username, + }; + + return this.findOne(query, options); + } + + findOneByRoomNameAndUserId(roomName, userId) { + const query = { + name: roomName, + 'u._id': userId, + }; + + return this.findOne(query); + } + + // FIND + findByUserId(userId, options) { + const query = + { 'u._id': userId }; + + return this.find(query, options); + } + + findByUserIdAndType(userId, type, options) { + const query = { + 'u._id': userId, + t: type, + }; + + return this.find(query, options); + } + + findByUserIdAndTypes(userId, types, options) { + const query = { + 'u._id': userId, + t: { + $in: types, + }, + }; + + return this.find(query, options); + } + + findByUserIdUpdatedAfter(userId, updatedAt, options) { + const query = { + 'u._id': userId, + _updatedAt: { + $gt: updatedAt, + }, + }; + + return this.find(query, options); + } + + findByRoomIdAndRoles(roomId, roles, options) { + roles = [].concat(roles); + const query = { + rid: roomId, + roles: { $in: roles }, + }; + + return this.find(query, options); + } + + findByType(types, options) { + const query = { + t: { + $in: types, + }, + }; + + return this.find(query, options); + } + + findByTypeAndUserId(type, userId, options) { + const query = { + t: type, + 'u._id': userId, + }; + + return this.find(query, options); + } + + findByRoomId(roomId, options) { + const query = + { rid: roomId }; + + return this.find(query, options); + } + + findByRoomIdAndNotUserId(roomId, userId, options) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + }; + + return this.find(query, options); + } + + findByRoomWithUserHighlights(roomId, options) { + const query = { + rid: roomId, + 'userHighlights.0': { $exists: true }, + }; + + return this.find(query, options); + } + + getLastSeen(options) { + if (options == null) { + options = {}; + } + const query = { ls: { $exists: 1 } }; + options.sort = { ls: -1 }; + options.limit = 1; + const [subscription] = this.find(query, options).fetch(); + return subscription && subscription.ls; + } + + findByRoomIdAndUserIds(roomId, userIds, options) { + const query = { + rid: roomId, + 'u._id': { + $in: userIds, + }, + }; + + return this.find(query, options); + } + + findByRoomIdAndUserIdsOrAllMessages(roomId, userIds) { + const query = { + rid: roomId, + $or: [ + { 'u._id': { $in: userIds } }, + { emailNotifications: 'all' }, + ], + }; + + return this.find(query); + } + + findByRoomIdWhenUserIdExists(rid, options) { + const query = { rid, 'u._id': { $exists: 1 } }; + + return this.find(query, options); + } + + findByRoomIdWhenUsernameExists(rid, options) { + const query = { rid, 'u.username': { $exists: 1 } }; + + return this.find(query, options); + } + + findUnreadByUserId(userId) { + const query = { + 'u._id': userId, + unread: { + $gt: 0, + }, + }; + + return this.find(query, { fields: { unread: 1 } }); + } + + getMinimumLastSeenByRoomId(rid) { + return this.db.findOne({ + rid, + }, { + sort: { + ls: 1, + }, + fields: { + ls: 1, + }, + }); + } + + // UPDATE + archiveByRoomId(roomId) { + const query = + { rid: roomId }; + + const update = { + $set: { + alert: false, + open: false, + archived: true, + }, + }; + + return this.update(query, update, { multi: true }); + } + + unarchiveByRoomId(roomId) { + const query = + { rid: roomId }; + + const update = { + $set: { + alert: false, + open: true, + archived: false, + }, + }; + + return this.update(query, update, { multi: true }); + } + + hideByRoomIdAndUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + const update = { + $set: { + alert: false, + open: false, + }, + }; + + return this.update(query, update); + } + + openByRoomIdAndUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + const update = { + $set: { + open: true, + }, + }; + + return this.update(query, update); + } + + setAsReadByRoomIdAndUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + const update = { + $set: { + open: true, + alert: false, + unread: 0, + userMentions: 0, + groupMentions: 0, + ls: new Date, + }, + }; + + return this.update(query, update); + } + + setAsUnreadByRoomIdAndUserId(roomId, userId, firstMessageUnreadTimestamp) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + const update = { + $set: { + open: true, + alert: true, + ls: firstMessageUnreadTimestamp, + }, + }; + + return this.update(query, update); + } + + setCustomFieldsDirectMessagesByUserId(userId, fields) { + const query = { + 'u._id': userId, + t: 'd', + }; + const update = { $set: { customFields: fields } }; + const options = { multi: true }; + + return this.update(query, update, options); + } + + setFavoriteByRoomIdAndUserId(roomId, userId, favorite) { + if (favorite == null) { + favorite = true; + } + const query = { + rid: roomId, + 'u._id': userId, + }; + + const update = { + $set: { + f: favorite, + }, + }; + + return this.update(query, update); + } + + updateNameAndAlertByRoomId(roomId, name, fname) { + const query = + { rid: roomId }; + + const update = { + $set: { + name, + fname, + alert: true, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateDisplayNameByRoomId(roomId, fname) { + const query = + { rid: roomId }; + + const update = { + $set: { + fname, + }, + }; + + return this.update(query, update, { multi: true }); + } + + setUserUsernameByUserId(userId, username) { + const query = + { 'u._id': userId }; + + const update = { + $set: { + 'u.username': username, + }, + }; + + return this.update(query, update, { multi: true }); + } + + setNameForDirectRoomsWithOldName(oldName, name) { + const query = { + name: oldName, + t: 'd', + }; + + const update = { + $set: { + name, + }, + }; + + return this.update(query, update, { multi: true }); + } + + incUnreadForRoomIdExcludingUserId(roomId, userId, inc) { + if (inc == null) { + inc = 1; + } + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + }; + + const update = { + $set: { + alert: true, + open: true, + }, + $inc: { + unread: inc, + }, + }; + + return this.update(query, update, { multi: true }); + } + + incGroupMentionsAndUnreadForRoomIdExcludingUserId(roomId, userId, incGroup = 1, incUnread = 1) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + }; + + const update = { + $set: { + alert: true, + open: true, + }, + $inc: { + unread: incUnread, + groupMentions: incGroup, + }, + }; + + return this.update(query, update, { multi: true }); + } + + incUserMentionsAndUnreadForRoomIdAndUserIds(roomId, userIds, incUser = 1, incUnread = 1) { + const query = { + rid: roomId, + 'u._id': { + $in: userIds, + }, + }; + + const update = { + $set: { + alert: true, + open: true, + }, + $inc: { + unread: incUnread, + userMentions: incUser, + }, + }; + + return this.update(query, update, { multi: true }); + } + + ignoreUser({ _id, ignoredUser : ignored, ignore = true }) { + const query = { + _id, + }; + const update = { + }; + if (ignore) { + update.$addToSet = { ignored }; + } else { + update.$pull = { ignored }; + } + + return this.update(query, update); + } + + setAlertForRoomIdExcludingUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + alert: { $ne: true }, + }; + + const update = { + $set: { + alert: true, + }, + }; + return this.update(query, update, { multi: true }); + } + + setOpenForRoomIdExcludingUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + open: { $ne: true }, + }; + + const update = { + $set: { + open: true, + }, + }; + return this.update(query, update, { multi: true }); + } + + setBlockedByRoomId(rid, blocked, blocker) { + const query = { + rid, + 'u._id': blocked, + }; + + const update = { + $set: { + blocked: true, + }, + }; + + const query2 = { + rid, + 'u._id': blocker, + }; + + const update2 = { + $set: { + blocker: true, + }, + }; + + return this.update(query, update) && this.update(query2, update2); + } + + unsetBlockedByRoomId(rid, blocked, blocker) { + const query = { + rid, + 'u._id': blocked, + }; + + const update = { + $unset: { + blocked: 1, + }, + }; + + const query2 = { + rid, + 'u._id': blocker, + }; + + const update2 = { + $unset: { + blocker: 1, + }, + }; + + return this.update(query, update) && this.update(query2, update2); + } + + updateCustomFieldsByRoomId(rid, cfields) { + const query = { rid }; + const customFields = cfields || {}; + const update = { + $set: { + customFields, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateTypeByRoomId(roomId, type) { + const query = + { rid: roomId }; + + const update = { + $set: { + t: type, + }, + }; + + return this.update(query, update, { multi: true }); + } + + addRoleById(_id, role) { + const query = + { _id }; + + const update = { + $addToSet: { + roles: role, + }, + }; + + return this.update(query, update); + } + + removeRoleById(_id, role) { + const query = + { _id }; + + const update = { + $pull: { + roles: role, + }, + }; + + return this.update(query, update); + } + + setArchivedByUsername(username, archived) { + const query = { + t: 'd', + name: username, + }; + + const update = { + $set: { + archived, + }, + }; + + return this.update(query, update, { multi: true }); + } + + clearDesktopNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + desktopPrefOrigin: 'user', + }; + + const update = { + $unset: { + desktopNotifications: 1, + desktopPrefOrigin: 1, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateDesktopNotificationUserPreferences(userId, desktopNotifications) { + const query = { + 'u._id': userId, + desktopPrefOrigin: { + $ne: 'subscription', + }, + }; + + const update = { + $set: { + desktopNotifications, + desktopPrefOrigin: 'user', + }, + }; + + return this.update(query, update, { multi: true }); + } + + clearMobileNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + mobilePrefOrigin: 'user', + }; + + const update = { + $unset: { + mobilePushNotifications: 1, + mobilePrefOrigin: 1, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateMobileNotificationUserPreferences(userId, mobilePushNotifications) { + const query = { + 'u._id': userId, + mobilePrefOrigin: { + $ne: 'subscription', + }, + }; + + const update = { + $set: { + mobilePushNotifications, + mobilePrefOrigin: 'user', + }, + }; + + return this.update(query, update, { multi: true }); + } + + clearEmailNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + emailPrefOrigin: 'user', + }; + + const update = { + $unset: { + emailNotifications: 1, + emailPrefOrigin: 1, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateEmailNotificationUserPreferences(userId, emailNotifications) { + const query = { + 'u._id': userId, + emailPrefOrigin: { + $ne: 'subscription', + }, + }; + + const update = { + $set: { + emailNotifications, + emailPrefOrigin: 'user', + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateUserHighlights(userId, userHighlights) { + const query = { + 'u._id': userId, + }; + + const update = { + $set: { + userHighlights, + }, + }; + + return this.update(query, update, { multi: true }); + } + + updateDirectFNameByName(name, fname) { + const query = { + t: 'd', + name, + }; + + const update = { + $set: { + fname, + }, + }; + + return this.update(query, update, { multi: true }); + } + + // INSERT + createWithRoomAndUser(room, user, extraData) { + const subscription = { + open: false, + alert: false, + unread: 0, + userMentions: 0, + groupMentions: 0, + ts: room.ts, + rid: room._id, + name: room.name, + fname: room.fname, + customFields: room.customFields, + t: room.t, + u: { + _id: user._id, + username: user.username, + name: user.name, + }, + ...getDefaultSubscriptionPref(user), + ...extraData, + }; + + const result = this.insert(subscription); + + Rooms.incUsersCountById(room._id); + + return result; + } + + + // REMOVE + removeByUserId(userId) { + const query = { + 'u._id': userId, + }; + + const roomIds = this.findByUserId(userId).map((s) => s.rid); + + const result = this.remove(query); + + if (Match.test(result, Number) && result > 0) { + Rooms.incUsersCountByIds(roomIds, -1); + } + + return result; + } + + removeByRoomId(roomId) { + const query = { + rid: roomId, + }; + + const result = this.remove(query); + + if (Match.test(result, Number) && result > 0) { + Rooms.incUsersCountById(roomId, - result); + } + + return result; + } + + removeByRoomIdAndUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + const result = this.remove(query); + + if (Match.test(result, Number) && result > 0) { + Rooms.incUsersCountById(roomId, - result); + } + + return result; + } +} + +export default new Subscriptions('subscription', true); diff --git a/packages/rocketchat-models/server/models/Uploads.js b/packages/rocketchat-models/server/models/Uploads.js new file mode 100644 index 000000000000..12f45b373b83 --- /dev/null +++ b/packages/rocketchat-models/server/models/Uploads.js @@ -0,0 +1,113 @@ +import _ from 'underscore'; +import s from 'underscore.string'; +import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; +import { Base } from './_Base'; + +export class Uploads extends Base { + constructor() { + super('uploads'); + + this.model.before.insert((userId, doc) => { + doc.instanceId = InstanceStatus.id(); + }); + + this.tryEnsureIndex({ rid: 1 }); + this.tryEnsureIndex({ uploadedAt: 1 }); + } + + findNotHiddenFilesOfRoom(roomId, searchText, limit) { + const fileQuery = { + rid: roomId, + complete: true, + uploading: false, + _hidden: { + $ne: true, + }, + }; + + if (searchText) { + fileQuery.name = { $regex: new RegExp(RegExp.escape(searchText), 'i') }; + } + + const fileOptions = { + limit, + sort: { + uploadedAt: -1, + }, + fields: { + _id: 1, + userId: 1, + rid: 1, + name: 1, + description: 1, + type: 1, + url: 1, + uploadedAt: 1, + }, + }; + + return this.find(fileQuery, fileOptions); + } + + insertFileInit(userId, store, file, extra) { + const fileData = { + userId, + store, + complete: false, + uploading: true, + progress: 0, + extension: s.strRightBack(file.name, '.'), + uploadedAt: new Date(), + }; + + _.extend(fileData, file, extra); + + if (this.model.direct && this.model.direct.insert != null) { + file = this.model.direct.insert(fileData); + } else { + file = this.insert(fileData); + } + + return file; + } + + updateFileComplete(fileId, userId, file) { + let result; + if (!fileId) { + return; + } + + const filter = { + _id: fileId, + userId, + }; + + const update = { + $set: { + complete: true, + uploading: false, + progress: 1, + }, + }; + + update.$set = _.extend(file, update.$set); + + if (this.model.direct && this.model.direct.update != null) { + result = this.model.direct.update(filter, update); + } else { + result = this.update(filter, update); + } + + return result; + } + + deleteFile(fileId) { + if (this.model.direct && this.model.direct.remove != null) { + return this.model.direct.remove({ _id: fileId }); + } else { + return this.remove({ _id: fileId }); + } + } +} + +export default new Uploads(); diff --git a/packages/rocketchat-models/server/models/UserDataFiles.js b/packages/rocketchat-models/server/models/UserDataFiles.js new file mode 100644 index 000000000000..7fe3a2b49504 --- /dev/null +++ b/packages/rocketchat-models/server/models/UserDataFiles.js @@ -0,0 +1,43 @@ +import { Base } from './_Base'; +import _ from 'underscore'; + +export class UserDataFiles extends Base { + constructor() { + super('user_data_files'); + + this.tryEnsureIndex({ userId: 1 }); + } + + // FIND + findById(id) { + const query = { _id: id }; + return this.find(query); + } + + findLastFileByUser(userId, options = {}) { + const query = { + userId, + }; + + options.sort = { _updatedAt : -1 }; + return this.findOne(query, options); + } + + // INSERT + create(data) { + const userDataFile = { + createdAt: new Date, + }; + + _.extend(userDataFile, data); + + return this.insert(userDataFile); + } + + // REMOVE + removeById(_id) { + return this.remove(_id); + } +} + +export default new UserDataFiles(); diff --git a/packages/rocketchat-models/server/models/Users.js b/packages/rocketchat-models/server/models/Users.js new file mode 100644 index 000000000000..786da5b96aa3 --- /dev/null +++ b/packages/rocketchat-models/server/models/Users.js @@ -0,0 +1,672 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; +import { settings } from 'meteor/rocketchat:settings'; +import { Base } from './_Base'; +import Subscriptions from './Subscriptions'; +import _ from 'underscore'; +import s from 'underscore.string'; + +export class Users extends Base { + constructor(...args) { + super(...args); + + this.tryEnsureIndex({ roles: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ name: 1 }); + this.tryEnsureIndex({ lastLogin: 1 }); + this.tryEnsureIndex({ status: 1 }); + this.tryEnsureIndex({ active: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 }); + this.tryEnsureIndex({ type: 1 }); + } + + findOneByImportId(_id, options) { + return this.findOne({ importIds: _id }, options); + } + + findOneByUsername(username, options) { + if (typeof username === 'string') { + username = new RegExp(`^${ username }$`, 'i'); + } + + const query = { username }; + + return this.findOne(query, options); + } + + findOneByEmailAddress(emailAddress, options) { + const query = { 'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i') }; + + return this.findOne(query, options); + } + + findOneAdmin(admin, options) { + const query = { admin }; + + return this.findOne(query, options); + } + + findOneByIdAndLoginToken(_id, token, options) { + const query = { + _id, + 'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token), + }; + + return this.findOne(query, options); + } + + findOneById(userId, options) { + const query = { _id: userId }; + + return this.findOne(query, options); + } + + // FIND + findById(userId) { + const query = { _id: userId }; + + return this.find(query); + } + + findByIds(users, options) { + const query = { _id: { $in: users } }; + return this.find(query, options); + } + + findUsersNotOffline(options) { + const query = { + username: { + $exists: 1, + }, + status: { + $in: ['online', 'away', 'busy'], + }, + }; + + return this.find(query, options); + } + + findByRoomId(rid, options) { + const data = Subscriptions.findByRoomId(rid).fetch().map((item) => item.u._id); + const query = { + _id: { + $in: data, + }, + }; + + return this.find(query, options); + } + + findByUsername(username, options) { + const query = { username }; + + return this.find(query, options); + } + + findActiveByUsernameOrNameRegexWithExceptions(searchTerm, exceptions, options) { + if (exceptions == null) { exceptions = []; } + if (options == null) { options = {}; } + if (!_.isArray(exceptions)) { + exceptions = [exceptions]; + } + + const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i'); + const query = { + $or: [{ + username: termRegex, + }, { + name: termRegex, + }], + active: true, + type: { + $in: ['user', 'bot'], + }, + $and: [{ + username: { + $exists: true, + }, + }, { + username: { + $nin: exceptions, + }, + }], + }; + + return this.find(query, options); + } + + findByActiveUsersExcept(searchTerm, exceptions, options) { + if (exceptions == null) { exceptions = []; } + if (options == null) { options = {}; } + if (!_.isArray(exceptions)) { + exceptions = [exceptions]; + } + + const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i'); + + const orStmt = _.reduce(settings.get('Accounts_SearchFields').trim().split(','), function(acc, el) { + acc.push({ [el.trim()]: termRegex }); + return acc; + }, []); + const query = { + $and: [ + { + active: true, + $or: orStmt, + }, + { + username: { $exists: true, $nin: exceptions }, + }, + ], + }; + + // do not use cache + return this._db.find(query, options); + } + + findUsersByNameOrUsername(nameOrUsername, options) { + const query = { + username: { + $exists: 1, + }, + + $or: [ + { name: nameOrUsername }, + { username: nameOrUsername }, + ], + + type: { + $in: ['user'], + }, + }; + + return this.find(query, options); + } + + findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress, options) { + const query = { + $or: [ + { name: usernameNameOrEmailAddress }, + { username: usernameNameOrEmailAddress }, + { 'emails.address': usernameNameOrEmailAddress }, + ], + type: { + $in: ['user', 'bot'], + }, + }; + + return this.find(query, options); + } + + findLDAPUsers(options) { + const query = { ldap: true }; + + return this.find(query, options); + } + + findCrowdUsers(options) { + const query = { crowd: true }; + + return this.find(query, options); + } + + getLastLogin(options) { + if (options == null) { options = {}; } + const query = { lastLogin: { $exists: 1 } }; + options.sort = { lastLogin: -1 }; + options.limit = 1; + const [user] = this.find(query, options).fetch(); + return user && user.lastLogin; + } + + findUsersByUsernames(usernames, options) { + const query = { + username: { + $in: usernames, + }, + }; + + return this.find(query, options); + } + + findUsersByIds(ids, options) { + const query = { + _id: { + $in: ids, + }, + }; + return this.find(query, options); + } + + findUsersWithUsernameByIds(ids, options) { + const query = { + _id: { + $in: ids, + }, + username: { + $exists: 1, + }, + }; + + return this.find(query, options); + } + + findUsersWithUsernameByIdsNotOffline(ids, options) { + const query = { + _id: { + $in: ids, + }, + username: { + $exists: 1, + }, + status: { + $in: ['online', 'away', 'busy'], + }, + }; + + return this.find(query, options); + } + + getOldest(fields = { _id: 1 }) { + const query = { + _id: { + $ne: 'rocket.cat', + }, + }; + + const options = { + fields, + sort: { + createdAt: 1, + }, + }; + + return this.findOne(query, options); + } + + // UPDATE + addImportIds(_id, importIds) { + importIds = [].concat(importIds); + + const query = { _id }; + + const update = { + $addToSet: { + importIds: { + $each: importIds, + }, + }, + }; + + return this.update(query, update); + } + + updateLastLoginById(_id) { + const update = { + $set: { + lastLogin: new Date, + }, + }; + + return this.update(_id, update); + } + + setServiceId(_id, serviceName, serviceId) { + const update = + { $set: {} }; + + const serviceIdKey = `services.${ serviceName }.id`; + update.$set[serviceIdKey] = serviceId; + + return this.update(_id, update); + } + + setUsername(_id, username) { + const update = + { $set: { username } }; + + return this.update(_id, update); + } + + setEmail(_id, email) { + const update = { + $set: { + emails: [{ + address: email, + verified: false, + }, + ], + }, + }; + + return this.update(_id, update); + } + + setEmailVerified(_id, email) { + const query = { + _id, + emails: { + $elemMatch: { + address: email, + verified: false, + }, + }, + }; + + const update = { + $set: { + 'emails.$.verified': true, + }, + }; + + return this.update(query, update); + } + + setName(_id, name) { + const update = { + $set: { + name, + }, + }; + + return this.update(_id, update); + } + + setCustomFields(_id, fields) { + const values = {}; + Object.keys(fields).forEach((key) => { + values[`customFields.${ key }`] = fields[key]; + }); + + const update = { $set: values }; + + return this.update(_id, update); + } + + setAvatarOrigin(_id, origin) { + const update = { + $set: { + avatarOrigin: origin, + }, + }; + + return this.update(_id, update); + } + + unsetAvatarOrigin(_id) { + const update = { + $unset: { + avatarOrigin: 1, + }, + }; + + return this.update(_id, update); + } + + setUserActive(_id, active) { + if (active == null) { active = true; } + const update = { + $set: { + active, + }, + }; + + return this.update(_id, update); + } + + setAllUsersActive(active) { + const update = { + $set: { + active, + }, + }; + + return this.update({}, update, { multi: true }); + } + + unsetLoginTokens(_id) { + const update = { + $set: { + 'services.resume.loginTokens' : [], + }, + }; + + return this.update(_id, update); + } + + unsetRequirePasswordChange(_id) { + const update = { + $unset: { + requirePasswordChange : true, + requirePasswordChangeReason : true, + }, + }; + + return this.update(_id, update); + } + + resetPasswordAndSetRequirePasswordChange(_id, requirePasswordChange, requirePasswordChangeReason) { + const update = { + $unset: { + 'services.password': 1, + }, + $set: { + requirePasswordChange, + requirePasswordChangeReason, + }, + }; + + return this.update(_id, update); + } + + setLanguage(_id, language) { + const update = { + $set: { + language, + }, + }; + + return this.update(_id, update); + } + + setProfile(_id, profile) { + const update = { + $set: { + 'settings.profile': profile, + }, + }; + + return this.update(_id, update); + } + + clearSettings(_id) { + const update = { + $set: { + settings: {}, + }, + }; + + return this.update(_id, update); + } + + setPreferences(_id, preferences) { + const settings = Object.assign( + {}, + ...Object.keys(preferences).map((key) => ({ [`settings.preferences.${ key }`]: preferences[key] })) + ); + + const update = { + $set: settings, + }; + if (parseInt(preferences.clockMode) === 0) { + delete update.$set['settings.preferences.clockMode']; + update.$unset = { 'settings.preferences.clockMode': 1 }; + } + + return this.update(_id, update); + } + + setUtcOffset(_id, utcOffset) { + const query = { + _id, + utcOffset: { + $ne: utcOffset, + }, + }; + + const update = { + $set: { + utcOffset, + }, + }; + + return this.update(query, update); + } + + saveUserById(_id, data) { + const setData = {}; + const unsetData = {}; + + if (data.name != null) { + if (!_.isEmpty(s.trim(data.name))) { + setData.name = s.trim(data.name); + } else { + unsetData.name = 1; + } + } + + if (data.email != null) { + if (!_.isEmpty(s.trim(data.email))) { + setData.emails = [{ address: s.trim(data.email) }]; + } else { + unsetData.emails = 1; + } + } + + if (data.phone != null) { + if (!_.isEmpty(s.trim(data.phone))) { + setData.phone = [{ phoneNumber: s.trim(data.phone) }]; + } else { + unsetData.phone = 1; + } + } + + const update = {}; + + if (!_.isEmpty(setData)) { + update.$set = setData; + } + + if (!_.isEmpty(unsetData)) { + update.$unset = unsetData; + } + + if (_.isEmpty(update)) { + return true; + } + + return this.update({ _id }, update); + } + + setReason(_id, reason) { + const update = { + $set: { + reason, + }, + }; + + return this.update(_id, update); + } + + unsetReason(_id) { + const update = { + $unset: { + reason: true, + }, + }; + + return this.update(_id, update); + } + + addBannerById(_id, banner) { + const update = { + $set: { + [`banners.${ banner.id }`]: banner, + }, + }; + + return this.update({ _id }, update); + } + + removeBannerById(_id, banner) { + const update = { + $unset: { + [`banners.${ banner.id }`]: true, + }, + }; + + return this.update({ _id }, update); + } + + removeResumeService(_id) { + const update = { + $unset: { + 'services.resume': '', + }, + }; + + return this.update({ _id }, update); + } + + // INSERT + create(data) { + const user = { + createdAt: new Date, + avatarOrigin: 'none', + }; + + _.extend(user, data); + + return this.insert(user); + } + + + // REMOVE + removeById(_id) { + return this.remove(_id); + } + + /* +Find users to send a message by email if: +- he is not online +- has a verified email +- has not disabled email notifications +- `active` is equal to true (false means they were deactivated and can't login) +*/ + getUsersToSendOfflineEmail(usersIds) { + const query = { + _id: { + $in: usersIds, + }, + active: true, + status: 'offline', + statusConnection: { + $ne: 'online', + }, + 'emails.verified': true, + }; + + const options = { + fields: { + name: 1, + username: 1, + emails: 1, + 'settings.preferences.emailNotificationMode': 1, + language: 1, + }, + }; + + return this.find(query, options); + } +} + +export default new Users(Meteor.users, true); diff --git a/packages/rocketchat-models/server/models/_Base.js b/packages/rocketchat-models/server/models/_Base.js new file mode 100644 index 000000000000..5180e2ebe25e --- /dev/null +++ b/packages/rocketchat-models/server/models/_Base.js @@ -0,0 +1,283 @@ +import { check } from 'meteor/check'; +import { BaseDb } from './_BaseDb'; +import objectPath from 'object-path'; +import _ from 'underscore'; + +export class Base { + constructor(nameOrModel) { + this._db = new BaseDb(nameOrModel, this); + this.model = this._db.model; + this.collectionName = this._db.collectionName; + this.name = this._db.name; + + this.on = this._db.on.bind(this._db); + this.emit = this._db.emit.bind(this._db); + + this.db = this; + } + + get origin() { + return '_db'; + } + + arrayToCursor(data) { + return { + fetch() { + return data; + }, + count() { + return data.length; + }, + forEach(fn) { + return data.forEach(fn); + }, + }; + } + + setUpdatedAt(...args/* record, checkQuery, query*/) { + return this._db.setUpdatedAt(...args); + } + + find(...args) { + try { + return this[this.origin].find(...args); + } catch (e) { + console.error('Exception on find', e, ...args); + } + } + + findOne(...args) { + try { + return this[this.origin].findOne(...args); + } catch (e) { + console.error('Exception on find', e, ...args); + } + } + + findOneById(...args) { + try { + return this[this.origin].findOneById(...args); + } catch (e) { + console.error('Exception on find', e, ...args); + } + } + + findOneByIds(ids, options, ...args) { + check(ids, [String]); + + try { + return this[this.origin].findOneByIds(ids, options); + } catch (e) { + console.error('Exception on find', e, [ids, options, ...args]); + } + } + + insert(...args/* record*/) { + return this._db.insert(...args); + } + + update(...args/* query, update, options*/) { + return this._db.update(...args); + } + + upsert(...args/* query, update*/) { + return this._db.upsert(...args); + } + + remove(...args/* query*/) { + return this._db.remove(...args); + } + + insertOrUpsert(...args) { + return this._db.insertOrUpsert(...args); + } + + allow(...args) { + return this._db.allow(...args); + } + + deny(...args) { + return this._db.deny(...args); + } + + ensureIndex(...args) { + return this._db.ensureIndex(...args); + } + + dropIndex(...args) { + return this._db.dropIndex(...args); + } + + tryEnsureIndex(...args) { + return this._db.tryEnsureIndex(...args); + } + + tryDropIndex(...args) { + return this._db.tryDropIndex(...args); + } + + trashFind(...args/* query, options*/) { + return this._db.trashFind(...args); + } + + trashFindOneById(...args/* _id, options*/) { + return this._db.trashFindOneById(...args); + } + + trashFindDeletedAfter(...args/* deletedAt, query, options*/) { + return this._db.trashFindDeletedAfter(...args); + } + + trashFindDeleted(...args) { + return this._db.trashFindDeleted(...args); + } + + processQueryOptionsOnResult(result, options = {}) { + if (result === undefined || result === null) { + return undefined; + } + + if (Array.isArray(result)) { + if (options.sort) { + result = result.sort((a, b) => { + let r = 0; + for (const field in options.sort) { + if (options.sort.hasOwnProperty(field)) { + const direction = options.sort[field]; + let valueA; + let valueB; + if (field.indexOf('.') > -1) { + valueA = objectPath.get(a, field); + valueB = objectPath.get(b, field); + } else { + valueA = a[field]; + valueB = b[field]; + } + if (valueA > valueB) { + r = direction; + break; + } + if (valueA < valueB) { + r = -direction; + break; + } + } + } + return r; + }); + } + + if (typeof options.skip === 'number') { + result.splice(0, options.skip); + } + + if (typeof options.limit === 'number' && options.limit !== 0) { + result.splice(options.limit); + } + } + + if (!options.fields) { + options.fields = {}; + } + + const fieldsToRemove = []; + const fieldsToGet = []; + + for (const field in options.fields) { + if (options.fields.hasOwnProperty(field)) { + if (options.fields[field] === 0) { + fieldsToRemove.push(field); + } else if (options.fields[field] === 1) { + fieldsToGet.push(field); + } + } + } + + if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) { + console.warn('Can\'t mix remove and get fields'); + fieldsToRemove.splice(0, fieldsToRemove.length); + } + + if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) { + fieldsToGet.push('_id'); + } + + const pickFields = (obj, fields) => { + const picked = {}; + fields.forEach((field) => { + if (field.indexOf('.') !== -1) { + objectPath.set(picked, field, objectPath.get(obj, field)); + } else { + picked[field] = obj[field]; + } + }); + return picked; + }; + + if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) { + if (Array.isArray(result)) { + result = result.map((record) => { + if (fieldsToRemove.length > 0) { + return _.omit(record, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(record, fieldsToGet); + } + + return null; + }); + } else { + if (fieldsToRemove.length > 0) { + return _.omit(result, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(result, fieldsToGet); + } + } + } + + return result; + } + + // dinamicTrashFindAfter(method, deletedAt, ...args) { + // const scope = { + // find: (query={}) => { + // return this.trashFindDeletedAfter(deletedAt, query, { fields: {_id: 1, _deletedAt: 1} }); + // } + // }; + + // scope.model = { + // find: scope.find + // }; + + // return this[method].apply(scope, args); + // } + + // dinamicFindAfter(method, updatedAt, ...args) { + // const scope = { + // find: (query={}, options) => { + // query._updatedAt = { + // $gt: updatedAt + // }; + + // return this.find(query, options); + // } + // }; + + // scope.model = { + // find: scope.find + // }; + + // return this[method].apply(scope, args); + // } + + // dinamicFindChangesAfter(method, updatedAt, ...args) { + // return { + // update: this.dinamicFindAfter(method, updatedAt, ...args).fetch(), + // remove: this.dinamicTrashFindAfter(method, updatedAt, ...args).fetch() + // }; + // } + +} diff --git a/packages/rocketchat-lib/server/models/_BaseDb.js b/packages/rocketchat-models/server/models/_BaseDb.js similarity index 98% rename from packages/rocketchat-lib/server/models/_BaseDb.js rename to packages/rocketchat-models/server/models/_BaseDb.js index 3c97bf1a5749..5955f5c775d9 100644 --- a/packages/rocketchat-lib/server/models/_BaseDb.js +++ b/packages/rocketchat-models/server/models/_BaseDb.js @@ -1,9 +1,10 @@ import { Match } from 'meteor/check'; import { Mongo, MongoInternals } from 'meteor/mongo'; +import { settings } from 'meteor/rocketchat:settings'; import _ from 'underscore'; +import { EventEmitter } from 'events'; const baseName = 'rocketchat_'; -import { EventEmitter } from 'events'; const trash = new Mongo.Collection(`${ baseName }_trash`); try { @@ -15,11 +16,11 @@ try { const isOplogAvailable = MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle && !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle.onOplogEntry; let isOplogEnabled = isOplogAvailable; -RocketChat.settings.get('Force_Disable_OpLog_For_Cache', (key, value) => { +settings.get('Force_Disable_OpLog_For_Cache', (key, value) => { isOplogEnabled = isOplogAvailable && value === false; }); -class ModelsBaseDb extends EventEmitter { +export class BaseDb extends EventEmitter { constructor(model, baseModel) { super(); @@ -386,5 +387,3 @@ class ModelsBaseDb extends EventEmitter { return trash.find(query, options); } } - -export default ModelsBaseDb; From 157edc0789b00cb4bc37c1e240fa573d57eeb528 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Mon, 24 Dec 2018 13:56:24 -0200 Subject: [PATCH 03/14] Move function from rocketchat:lib to rocketchat:utils to use it in rocketchat:models --- .../lib/getDefaultSubscriptionPref.js | 32 ++----------------- packages/rocketchat-utils/client/index.js | 2 ++ .../lib/getDefaultSubscriptionPref.js | 31 ++++++++++++++++++ packages/rocketchat-utils/server/index.js | 2 ++ 4 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 packages/rocketchat-utils/lib/getDefaultSubscriptionPref.js diff --git a/packages/rocketchat-lib/lib/getDefaultSubscriptionPref.js b/packages/rocketchat-lib/lib/getDefaultSubscriptionPref.js index 7bf89190e40a..651cbb6435e2 100644 --- a/packages/rocketchat-lib/lib/getDefaultSubscriptionPref.js +++ b/packages/rocketchat-lib/lib/getDefaultSubscriptionPref.js @@ -1,31 +1,3 @@ -RocketChat.getDefaultSubscriptionPref = function _getDefaultSubscriptionPref(userPref) { - const subscription = {}; +import { getDefaultSubscriptionPref } from 'meteor/rocketchat:utils'; - const { - desktopNotifications, - mobileNotifications, - emailNotificationMode, - highlights, - } = (userPref.settings && userPref.settings.preferences) || {}; - - if (Array.isArray(highlights) && highlights.length) { - subscription.userHighlights = highlights; - } - - if (desktopNotifications && desktopNotifications !== 'default') { - subscription.desktopNotifications = desktopNotifications; - subscription.desktopPrefOrigin = 'user'; - } - - if (mobileNotifications && mobileNotifications !== 'default') { - subscription.mobilePushNotifications = mobileNotifications; - subscription.mobilePrefOrigin = 'user'; - } - - if (emailNotificationMode && emailNotificationMode !== 'default') { - subscription.emailNotifications = emailNotificationMode; - subscription.emailPrefOrigin = 'user'; - } - - return subscription; -}; +RocketChat.getDefaultSubscriptionPref = getDefaultSubscriptionPref; diff --git a/packages/rocketchat-utils/client/index.js b/packages/rocketchat-utils/client/index.js index a11bb5877925..b743c400456c 100644 --- a/packages/rocketchat-utils/client/index.js +++ b/packages/rocketchat-utils/client/index.js @@ -1,9 +1,11 @@ import { t, isRtl } from '../lib/tapi18n'; import { isChrome, isFirefox } from './lib/browsers'; +import { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref'; export { t, isRtl, isChrome, isFirefox, + getDefaultSubscriptionPref, }; diff --git a/packages/rocketchat-utils/lib/getDefaultSubscriptionPref.js b/packages/rocketchat-utils/lib/getDefaultSubscriptionPref.js new file mode 100644 index 000000000000..294a7d50a734 --- /dev/null +++ b/packages/rocketchat-utils/lib/getDefaultSubscriptionPref.js @@ -0,0 +1,31 @@ +export const getDefaultSubscriptionPref = (userPref) => { + const subscription = {}; + + const { + desktopNotifications, + mobileNotifications, + emailNotificationMode, + highlights, + } = (userPref.settings && userPref.settings.preferences) || {}; + + if (Array.isArray(highlights) && highlights.length) { + subscription.userHighlights = highlights; + } + + if (desktopNotifications && desktopNotifications !== 'default') { + subscription.desktopNotifications = desktopNotifications; + subscription.desktopPrefOrigin = 'user'; + } + + if (mobileNotifications && mobileNotifications !== 'default') { + subscription.mobilePushNotifications = mobileNotifications; + subscription.mobilePrefOrigin = 'user'; + } + + if (emailNotificationMode && emailNotificationMode !== 'default') { + subscription.emailNotifications = emailNotificationMode; + subscription.emailPrefOrigin = 'user'; + } + + return subscription; +}; diff --git a/packages/rocketchat-utils/server/index.js b/packages/rocketchat-utils/server/index.js index 49c8d6407fbc..1fc8d8d2b0cc 100644 --- a/packages/rocketchat-utils/server/index.js +++ b/packages/rocketchat-utils/server/index.js @@ -1,6 +1,8 @@ import { t, isRtl } from '../lib/tapi18n'; +import { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref'; export { t, isRtl, + getDefaultSubscriptionPref, }; From 24cfb4f0e32af0e02786778446e64bad7c9560c7 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Wed, 26 Dec 2018 10:28:51 -0200 Subject: [PATCH 04/14] Move client models from rocketchat:lib to rocketchat:models --- .../rocketchat-lib/client/models/Avatars.js | 9 +-- .../rocketchat-lib/client/models/Uploads.js | 8 +-- .../client/models/UserDataFiles.js | 9 +-- .../rocketchat-lib/client/models/_Base.js | 56 +------------------ packages/rocketchat-models/client/index.js | 11 ++++ .../client/models/Avatars.js | 10 ++++ .../client/models/Uploads.js | 10 ++++ .../client/models/UserDataFiles.js | 10 ++++ .../rocketchat-models/client/models/_Base.js | 55 ++++++++++++++++++ 9 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 packages/rocketchat-models/client/models/Avatars.js create mode 100644 packages/rocketchat-models/client/models/Uploads.js create mode 100644 packages/rocketchat-models/client/models/UserDataFiles.js create mode 100644 packages/rocketchat-models/client/models/_Base.js diff --git a/packages/rocketchat-lib/client/models/Avatars.js b/packages/rocketchat-lib/client/models/Avatars.js index bd8804e746c8..455ca64a97d1 100644 --- a/packages/rocketchat-lib/client/models/Avatars.js +++ b/packages/rocketchat-lib/client/models/Avatars.js @@ -1,6 +1,3 @@ -RocketChat.models.Avatars = new class extends RocketChat.models._Base { - constructor() { - super(); - this._initModel('avatars'); - } -}; +import { Avatars } from 'meteor/rocketchat:models'; + +RocketChat.models.Avatars = Avatars; diff --git a/packages/rocketchat-lib/client/models/Uploads.js b/packages/rocketchat-lib/client/models/Uploads.js index eefe630c708c..7dc92cf6f67b 100644 --- a/packages/rocketchat-lib/client/models/Uploads.js +++ b/packages/rocketchat-lib/client/models/Uploads.js @@ -1,7 +1,3 @@ +import { Uploads } from 'meteor/rocketchat:models'; -RocketChat.models.Uploads = new class extends RocketChat.models._Base { - constructor() { - super(); - this._initModel('uploads'); - } -}; +RocketChat.models.Uploads = Uploads; diff --git a/packages/rocketchat-lib/client/models/UserDataFiles.js b/packages/rocketchat-lib/client/models/UserDataFiles.js index 80e7e22e162e..b3eff0ac6125 100644 --- a/packages/rocketchat-lib/client/models/UserDataFiles.js +++ b/packages/rocketchat-lib/client/models/UserDataFiles.js @@ -1,6 +1,3 @@ -RocketChat.models.UserDataFiles = new class extends RocketChat.models._Base { - constructor() { - super(); - this._initModel('userDataFiles'); - } -}; +import { UserDataFiles } from 'meteor/rocketchat:models'; + +RocketChat.models.UserDataFiles = UserDataFiles; diff --git a/packages/rocketchat-lib/client/models/_Base.js b/packages/rocketchat-lib/client/models/_Base.js index 5a283e2ebcbc..bfda6cf65cce 100644 --- a/packages/rocketchat-lib/client/models/_Base.js +++ b/packages/rocketchat-lib/client/models/_Base.js @@ -1,55 +1,3 @@ -import { check } from 'meteor/check'; -import { Mongo } from 'meteor/mongo'; +import { Base } from 'meteor/rocketchat:models'; -RocketChat.models._Base = class { - - _baseName() { - return 'rocketchat_'; - } - - _initModel(name) { - check(name, String); - return this.model = new Mongo.Collection(this._baseName() + name); - } - - find(...args) { - return this.model.find.apply(this.model, args); - } - - findOne(...args) { - return this.model.findOne.apply(this.model, args); - } - - insert(...args) { - return this.model.insert.apply(this.model, args); - } - - update(...args) { - return this.model.update.apply(this.model, args); - } - - upsert(...args) { - return this.model.upsert.apply(this.model, args); - } - - remove(...args) { - return this.model.remove.apply(this.model, args); - } - - allow(...args) { - return this.model.allow.apply(this.model, args); - } - - deny(...args) { - return this.model.deny.apply(this.model, args); - } - - ensureIndex() {} - - dropIndex() {} - - tryEnsureIndex() {} - - tryDropIndex() {} - -}; +RocketChat.models._Base = Base; diff --git a/packages/rocketchat-models/client/index.js b/packages/rocketchat-models/client/index.js index e69de29bb2d1..a41ae40a2d38 100644 --- a/packages/rocketchat-models/client/index.js +++ b/packages/rocketchat-models/client/index.js @@ -0,0 +1,11 @@ +import { Base } from './models/_Base'; +import Avatars from './models/Avatars'; +import Uploads from './models/Uploads'; +import UserDataFiles from './models/UserDataFiles'; + +export { + Base, + Avatars, + Uploads, + UserDataFiles, +}; diff --git a/packages/rocketchat-models/client/models/Avatars.js b/packages/rocketchat-models/client/models/Avatars.js new file mode 100644 index 000000000000..103df0c8196a --- /dev/null +++ b/packages/rocketchat-models/client/models/Avatars.js @@ -0,0 +1,10 @@ +import { Base } from './_Base'; + +export class Avatars extends Base { + constructor() { + super(); + this._initModel('avatars'); + } +}; + +export default new Avatars(); diff --git a/packages/rocketchat-models/client/models/Uploads.js b/packages/rocketchat-models/client/models/Uploads.js new file mode 100644 index 000000000000..7e3a4f443036 --- /dev/null +++ b/packages/rocketchat-models/client/models/Uploads.js @@ -0,0 +1,10 @@ +import { Base } from './_Base'; + +export class Uploads extends Base { + constructor() { + super(); + this._initModel('uploads'); + } +}; + +export default new Uploads(); diff --git a/packages/rocketchat-models/client/models/UserDataFiles.js b/packages/rocketchat-models/client/models/UserDataFiles.js new file mode 100644 index 000000000000..511a2420fcc0 --- /dev/null +++ b/packages/rocketchat-models/client/models/UserDataFiles.js @@ -0,0 +1,10 @@ +import { Base } from './_Base'; + +export class UserDataFiles extends Base { + constructor() { + super(); + this._initModel('userDataFiles'); + } +}; + +export default new UserDataFiles(); diff --git a/packages/rocketchat-models/client/models/_Base.js b/packages/rocketchat-models/client/models/_Base.js new file mode 100644 index 000000000000..1dc0d3e3bb8d --- /dev/null +++ b/packages/rocketchat-models/client/models/_Base.js @@ -0,0 +1,55 @@ +import { check } from 'meteor/check'; +import { Mongo } from 'meteor/mongo'; + +export class Base { + + _baseName() { + return 'rocketchat_'; + } + + _initModel(name) { + check(name, String); + return this.model = new Mongo.Collection(this._baseName() + name); + } + + find(...args) { + return this.model.find.apply(this.model, args); + } + + findOne(...args) { + return this.model.findOne.apply(this.model, args); + } + + insert(...args) { + return this.model.insert.apply(this.model, args); + } + + update(...args) { + return this.model.update.apply(this.model, args); + } + + upsert(...args) { + return this.model.upsert.apply(this.model, args); + } + + remove(...args) { + return this.model.remove.apply(this.model, args); + } + + allow(...args) { + return this.model.allow.apply(this.model, args); + } + + deny(...args) { + return this.model.deny.apply(this.model, args); + } + + ensureIndex() {} + + dropIndex() {} + + tryEnsureIndex() {} + + tryDropIndex() {} + +}; From 0683f574d6584b604bac118473376f922403ce8a Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Wed, 26 Dec 2018 10:37:12 -0200 Subject: [PATCH 05/14] Fix lint --- packages/rocketchat-models/client/models/Avatars.js | 2 +- packages/rocketchat-models/client/models/Uploads.js | 2 +- packages/rocketchat-models/client/models/UserDataFiles.js | 2 +- packages/rocketchat-models/client/models/_Base.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rocketchat-models/client/models/Avatars.js b/packages/rocketchat-models/client/models/Avatars.js index 103df0c8196a..73a35965754a 100644 --- a/packages/rocketchat-models/client/models/Avatars.js +++ b/packages/rocketchat-models/client/models/Avatars.js @@ -5,6 +5,6 @@ export class Avatars extends Base { super(); this._initModel('avatars'); } -}; +} export default new Avatars(); diff --git a/packages/rocketchat-models/client/models/Uploads.js b/packages/rocketchat-models/client/models/Uploads.js index 7e3a4f443036..2c3ee0285d98 100644 --- a/packages/rocketchat-models/client/models/Uploads.js +++ b/packages/rocketchat-models/client/models/Uploads.js @@ -5,6 +5,6 @@ export class Uploads extends Base { super(); this._initModel('uploads'); } -}; +} export default new Uploads(); diff --git a/packages/rocketchat-models/client/models/UserDataFiles.js b/packages/rocketchat-models/client/models/UserDataFiles.js index 511a2420fcc0..93c1b1d44720 100644 --- a/packages/rocketchat-models/client/models/UserDataFiles.js +++ b/packages/rocketchat-models/client/models/UserDataFiles.js @@ -5,6 +5,6 @@ export class UserDataFiles extends Base { super(); this._initModel('userDataFiles'); } -}; +} export default new UserDataFiles(); diff --git a/packages/rocketchat-models/client/models/_Base.js b/packages/rocketchat-models/client/models/_Base.js index 1dc0d3e3bb8d..c8f9f0eafb82 100644 --- a/packages/rocketchat-models/client/models/_Base.js +++ b/packages/rocketchat-models/client/models/_Base.js @@ -52,4 +52,4 @@ export class Base { tryDropIndex() {} -}; +} From b26e192cb20fbb7ef7cc882dd331cdb61d5adfe4 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 11:34:57 -0200 Subject: [PATCH 06/14] Move rocketchat.info from lib to utils --- .scripts/set-version.js | 2 +- packages/rocketchat-lib/lib/info.js | 3 +++ packages/rocketchat-lib/package.js | 3 --- packages/rocketchat-utils/client/index.js | 2 ++ packages/rocketchat-utils/package.js | 1 + packages/{rocketchat-lib => rocketchat-utils}/rocketchat.info | 0 packages/rocketchat-utils/server/index.js | 2 ++ packages/rocketchat-version/plugin/compile-version.js | 3 +-- 8 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 packages/rocketchat-lib/lib/info.js rename packages/{rocketchat-lib => rocketchat-utils}/rocketchat.info (100%) diff --git a/.scripts/set-version.js b/.scripts/set-version.js index 0016f95e8f5a..27852ab15050 100644 --- a/.scripts/set-version.js +++ b/.scripts/set-version.js @@ -26,7 +26,7 @@ const files = [ './.circleci/update-releases.sh', './.docker/Dockerfile', './.docker/Dockerfile.rhel', - './packages/rocketchat-lib/rocketchat.info', + './packages/rocketchat-utils/rocketchat.info', ]; const readFile = (file) => new Promise((resolve, reject) => { fs.readFile(file, 'utf8', (error, result) => { diff --git a/packages/rocketchat-lib/lib/info.js b/packages/rocketchat-lib/lib/info.js new file mode 100644 index 000000000000..a47217dd6d90 --- /dev/null +++ b/packages/rocketchat-lib/lib/info.js @@ -0,0 +1,3 @@ +import { Info } from 'meteor/rocketchat:utils'; + +RocketChat.Info = Info; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index c994b17fc1b3..0337e192a31d 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -255,9 +255,6 @@ Package.onUse(function(api) { api.addFiles('startup/defaultRoomTypes.js'); api.addFiles('startup/index.js', 'server'); - // VERSION - api.addFiles('rocketchat.info'); - // EXPORT api.export('RocketChat'); api.export('handleError', 'client'); diff --git a/packages/rocketchat-utils/client/index.js b/packages/rocketchat-utils/client/index.js index b743c400456c..cb49ba819992 100644 --- a/packages/rocketchat-utils/client/index.js +++ b/packages/rocketchat-utils/client/index.js @@ -1,6 +1,7 @@ import { t, isRtl } from '../lib/tapi18n'; import { isChrome, isFirefox } from './lib/browsers'; import { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref'; +import { Info } from '../rocketchat.info'; export { t, @@ -8,4 +9,5 @@ export { isChrome, isFirefox, getDefaultSubscriptionPref, + Info, }; diff --git a/packages/rocketchat-utils/package.js b/packages/rocketchat-utils/package.js index 6655c606eda4..05fa30074847 100644 --- a/packages/rocketchat-utils/package.js +++ b/packages/rocketchat-utils/package.js @@ -8,6 +8,7 @@ Package.onUse(function(api) { api.use([ 'ecmascript', 'tap:i18n', + 'rocketchat:version', ]); api.mainModule('client/index.js', 'client'); api.mainModule('server/index.js', 'server'); diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-utils/rocketchat.info similarity index 100% rename from packages/rocketchat-lib/rocketchat.info rename to packages/rocketchat-utils/rocketchat.info diff --git a/packages/rocketchat-utils/server/index.js b/packages/rocketchat-utils/server/index.js index 1fc8d8d2b0cc..906b3434c665 100644 --- a/packages/rocketchat-utils/server/index.js +++ b/packages/rocketchat-utils/server/index.js @@ -1,8 +1,10 @@ import { t, isRtl } from '../lib/tapi18n'; import { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref'; +import { Info } from '../rocketchat.info'; export { t, isRtl, getDefaultSubscriptionPref, + Info, }; diff --git a/packages/rocketchat-version/plugin/compile-version.js b/packages/rocketchat-version/plugin/compile-version.js index a96fd6fcac8e..8e3a9b6cd5d9 100644 --- a/packages/rocketchat-version/plugin/compile-version.js +++ b/packages/rocketchat-version/plugin/compile-version.js @@ -48,7 +48,6 @@ class VersionCompiler { if (err == null && output.commit != null) { output.commit.tag = result.replace('\n', ''); } - exec('git rev-parse --abbrev-ref HEAD', function(err, result) { if (err == null && output.commit != null) { output.commit.branch = result.replace('\n', ''); @@ -57,7 +56,7 @@ class VersionCompiler { const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')); output.marketplaceApiVersion = pkg.dependencies['@rocket.chat/apps-engine'].replace(/[^0-9.]/g, ''); - output = `RocketChat.Info = ${ JSON.stringify(output, null, 4) };`; + output = `exports.Info = ${ JSON.stringify(output, null, 4) };`; file.addJavaScript({ data: output, path: `${ file.getPathInPackage() }.js`, From 8eeac90ecfc66f974809201e2da6d99cba4cbb93 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 11:36:18 -0200 Subject: [PATCH 07/14] Remove directly dependency between lib and migrations --- packages/rocketchat-lib/package.js | 2 ++ packages/rocketchat-lib/server/lib/migrations.js | 3 +++ packages/rocketchat-migrations/package.js | 1 - packages/rocketchat-migrations/server/index.js | 4 +++- packages/rocketchat-migrations/server/migrations.js | 5 +---- 5 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 packages/rocketchat-lib/server/lib/migrations.js diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 0337e192a31d..fade206644d5 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -26,6 +26,7 @@ Package.onUse(function(api) { api.use('check'); api.use('rocketchat:utils'); api.use('rocketchat:models'); + api.use('rocketchat:migrations'); api.use('rocketchat:accounts'); api.use('modules'); api.use('rocketchat:i18n'); @@ -133,6 +134,7 @@ Package.onUse(function(api) { api.addFiles('server/lib/sendNotificationsOnMessage.js', 'server'); api.addFiles('server/lib/validateEmailDomain.js', 'server'); api.addFiles('server/lib/passwordPolicy.js', 'server'); + api.addFiles('server/lib/migrations.js', 'server'); // SERVER MODELS api.addFiles('server/models/_Base.js', 'server'); diff --git a/packages/rocketchat-lib/server/lib/migrations.js b/packages/rocketchat-lib/server/lib/migrations.js new file mode 100644 index 000000000000..5fa1a6f6f70d --- /dev/null +++ b/packages/rocketchat-lib/server/lib/migrations.js @@ -0,0 +1,3 @@ +import { Migrations } from 'meteor/rocketchat:migrations'; + +RocketChat.Migrations = Migrations; diff --git a/packages/rocketchat-migrations/package.js b/packages/rocketchat-migrations/package.js index 7caa5b6acd46..311865258ed1 100644 --- a/packages/rocketchat-migrations/package.js +++ b/packages/rocketchat-migrations/package.js @@ -8,7 +8,6 @@ Package.describe({ Package.onUse(function(api) { api.use([ 'ecmascript', - 'rocketchat:lib', 'rocketchat:version', 'logging', 'check', diff --git a/packages/rocketchat-migrations/server/index.js b/packages/rocketchat-migrations/server/index.js index 9f9866dccd95..bd8a290dd2b3 100644 --- a/packages/rocketchat-migrations/server/index.js +++ b/packages/rocketchat-migrations/server/index.js @@ -1 +1,3 @@ -import './migrations'; +import { Migrations } from './migrations'; + +export { Migrations }; diff --git a/packages/rocketchat-migrations/server/migrations.js b/packages/rocketchat-migrations/server/migrations.js index 085de7faf8a8..8ee7902fe2fd 100644 --- a/packages/rocketchat-migrations/server/migrations.js +++ b/packages/rocketchat-migrations/server/migrations.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Mongo } from 'meteor/mongo'; -import { RocketChat } from 'meteor/rocketchat:lib'; import { Log } from 'meteor/logging'; import _ from 'underscore'; import s from 'underscore.string'; @@ -45,7 +44,7 @@ const DefaultMigration = { }, }; -const Migrations = this.Migrations = { +export const Migrations = { _list: [DefaultMigration], options: { // false disables logging @@ -411,5 +410,3 @@ Migrations._reset = function() { }]; this._collection.remove({}); }; - -RocketChat.Migrations = Migrations; From 7080105e98dcad62fa3ac83413d4276d8a9632af Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 11:37:35 -0200 Subject: [PATCH 08/14] Move statistics Model to rocketchat:models --- packages/rocketchat-models/server/index.js | 2 ++ .../server/models/Statistics.js | 28 +++++++++++++++++++ packages/rocketchat-statistics/package.js | 1 + .../server/models/Statistics.js | 26 ++--------------- 4 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 packages/rocketchat-models/server/models/Statistics.js diff --git a/packages/rocketchat-models/server/index.js b/packages/rocketchat-models/server/index.js index 372205cdf8fa..bb2ea816e180 100644 --- a/packages/rocketchat-models/server/index.js +++ b/packages/rocketchat-models/server/index.js @@ -10,6 +10,7 @@ import Subscriptions from './models/Subscriptions'; import Uploads from './models/Uploads'; import UserDataFiles from './models/UserDataFiles'; import Users from './models/Users'; +import Statistics from './models/Statistics'; export { Base, @@ -24,4 +25,5 @@ export { Uploads, UserDataFiles, Users, + Statistics, }; diff --git a/packages/rocketchat-models/server/models/Statistics.js b/packages/rocketchat-models/server/models/Statistics.js new file mode 100644 index 000000000000..ca95cd5642e6 --- /dev/null +++ b/packages/rocketchat-models/server/models/Statistics.js @@ -0,0 +1,28 @@ +import { Base } from './_Base'; + +export class Statistics extends Base { + constructor() { + super('statistics'); + + this.tryEnsureIndex({ createdAt: 1 }); + } + + // FIND ONE + findOneById(_id, options) { + const query = { _id }; + return this.findOne(query, options); + } + + findLast() { + const options = { + sort: { + createdAt: -1, + }, + limit: 1, + }; + const records = this.find({}, options).fetch(); + return records && records[0]; + } +} + +export default new Statistics(); diff --git a/packages/rocketchat-statistics/package.js b/packages/rocketchat-statistics/package.js index 0ab1fd373172..f379ce963648 100644 --- a/packages/rocketchat-statistics/package.js +++ b/packages/rocketchat-statistics/package.js @@ -10,6 +10,7 @@ Package.onUse(function(api) { 'mongo', 'ecmascript', 'rocketchat:lib', + 'rocketchat:models', 'konecty:multiple-instances-status', ]); api.mainModule('client/index.js', 'client'); diff --git a/packages/rocketchat-statistics/server/models/Statistics.js b/packages/rocketchat-statistics/server/models/Statistics.js index cb52ecef6740..d628e60380d1 100644 --- a/packages/rocketchat-statistics/server/models/Statistics.js +++ b/packages/rocketchat-statistics/server/models/Statistics.js @@ -1,26 +1,4 @@ import { RocketChat } from 'meteor/rocketchat:lib'; +import { Statistics } from 'meteor/rocketchat:models'; -RocketChat.models.Statistics = new class extends RocketChat.models._Base { - constructor() { - super('statistics'); - - this.tryEnsureIndex({ createdAt: 1 }); - } - - // FIND ONE - findOneById(_id, options) { - const query = { _id }; - return this.findOne(query, options); - } - - findLast() { - const options = { - sort: { - createdAt: -1, - }, - limit: 1, - }; - const records = this.find({}, options).fetch(); - return records && records[0]; - } -}; +RocketChat.models.Statistics = Statistics; From 3d2395c6e4061976383d0e918cbd60c0a51c49ff Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 11:38:42 -0200 Subject: [PATCH 09/14] Create rocketchat:metrics to be able to depacking rocketchat callbacks --- .meteor/packages | 3 +- .meteor/versions | 1 + packages/rocketchat-lib/package.js | 2 + packages/rocketchat-lib/server/lib/metrics.js | 182 +---------------- .../server/startup/statsTracker.js | 46 +---- packages/rocketchat-metrics/package.js | 16 ++ packages/rocketchat-metrics/server/index.js | 7 + .../rocketchat-metrics/server/lib/metrics.js | 187 ++++++++++++++++++ .../server/lib/statsTracker.js | 48 +++++ 9 files changed, 267 insertions(+), 225 deletions(-) create mode 100644 packages/rocketchat-metrics/package.js create mode 100644 packages/rocketchat-metrics/server/index.js create mode 100644 packages/rocketchat-metrics/server/lib/metrics.js create mode 100644 packages/rocketchat-metrics/server/lib/statsTracker.js diff --git a/.meteor/packages b/.meteor/packages index 98585f8b6d31..9d37a9746896 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -202,4 +202,5 @@ juliancwirko:postcss littledata:synced-cron rocketchat:utils rocketchat:settings -rocketchat:models \ No newline at end of file +rocketchat:models +rocketchat:metrics \ No newline at end of file diff --git a/.meteor/versions b/.meteor/versions index e759dba2d774..872ff32b8816 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -191,6 +191,7 @@ rocketchat:mailer@0.0.1 rocketchat:mailmessages@0.0.1 rocketchat:mapview@0.0.1 rocketchat:markdown@0.0.2 +rocketchat:metrics@0.0.1 rocketchat:mentions@0.0.1 rocketchat:mentions-flextab@0.0.1 rocketchat:message-action@0.0.1 diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index fade206644d5..354cfa33b8fa 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -27,6 +27,7 @@ Package.onUse(function(api) { api.use('rocketchat:utils'); api.use('rocketchat:models'); api.use('rocketchat:migrations'); + api.use('rocketchat:metrics'); api.use('rocketchat:accounts'); api.use('modules'); api.use('rocketchat:i18n'); @@ -82,6 +83,7 @@ Package.onUse(function(api) { api.addFiles('lib/messageBox.js'); api.addFiles('lib/MessageTypes.js'); api.addFiles('lib/templateVarHandler.js'); + api.addFiles('lib/info.js'); api.addFiles('lib/getUserNotificationPreference.js'); api.addFiles('lib/getUserPreference.js'); diff --git a/packages/rocketchat-lib/server/lib/metrics.js b/packages/rocketchat-lib/server/lib/metrics.js index 4608c7176054..79d0541c8349 100644 --- a/packages/rocketchat-lib/server/lib/metrics.js +++ b/packages/rocketchat-lib/server/lib/metrics.js @@ -1,184 +1,6 @@ -import { Meteor } from 'meteor/meteor'; import client from 'prom-client'; -import connect from 'connect'; -import http from 'http'; -import _ from 'underscore'; +import { metrics } from 'meteor/rocketchat:metrics'; RocketChat.promclient = client; -client.collectDefaultMetrics(); -RocketChat.metrics = {}; - -// one sample metrics only - a counter - -RocketChat.metrics.meteorMethods = new client.Summary({ - name: 'rocketchat_meteor_methods', - help: 'summary of meteor methods count and time', - labelNames: ['method', 'has_connection', 'has_user'], -}); - -RocketChat.metrics.rocketchatCallbacks = new client.Summary({ - name: 'rocketchat_callbacks', - help: 'summary of rocketchat callbacks count and time', - labelNames: ['hook', 'callback'], -}); - -RocketChat.metrics.rocketchatHooks = new client.Summary({ - name: 'rocketchat_hooks', - help: 'summary of rocketchat hooks count and time', - labelNames: ['hook', 'callbacks_length'], -}); - -RocketChat.metrics.rocketchatRestApi = new client.Summary({ - name: 'rocketchat_rest_api', - help: 'summary of rocketchat rest api count and time', - labelNames: ['method', 'entrypoint', 'user_agent', 'status', 'version'], -}); - -RocketChat.metrics.meteorSubscriptions = new client.Summary({ - name: 'rocketchat_meteor_subscriptions', - help: 'summary of meteor subscriptions count and time', - labelNames: ['subscription'], -}); - -RocketChat.metrics.messagesSent = new client.Counter({ name: 'rocketchat_message_sent', help: 'cumulated number of messages sent' }); -RocketChat.metrics.notificationsSent = new client.Counter({ name: 'rocketchat_notification_sent', labelNames: ['notification_type'], help: 'cumulated number of notifications sent' }); - -RocketChat.metrics.ddpSessions = new client.Gauge({ name: 'rocketchat_ddp_sessions_count', help: 'number of open ddp sessions' }); -RocketChat.metrics.ddpAthenticatedSessions = new client.Gauge({ name: 'rocketchat_ddp_sessions_auth', help: 'number of authenticated open ddp sessions' }); -RocketChat.metrics.ddpConnectedUsers = new client.Gauge({ name: 'rocketchat_ddp_connected_users', help: 'number of unique connected users' }); - -RocketChat.metrics.version = new client.Gauge({ name: 'rocketchat_version', labelNames: ['version'], help: 'Rocket.Chat version' }); -RocketChat.metrics.migration = new client.Gauge({ name: 'rocketchat_migration', help: 'migration versoin' }); -RocketChat.metrics.instanceCount = new client.Gauge({ name: 'rocketchat_instance_count', help: 'instances running' }); -RocketChat.metrics.oplogEnabled = new client.Gauge({ name: 'rocketchat_oplog_enabled', labelNames: ['enabled'], help: 'oplog enabled' }); - -// User statistics -RocketChat.metrics.totalUsers = new client.Gauge({ name: 'rocketchat_users_total', help: 'total of users' }); -RocketChat.metrics.activeUsers = new client.Gauge({ name: 'rocketchat_users_active', help: 'total of active users' }); -RocketChat.metrics.nonActiveUsers = new client.Gauge({ name: 'rocketchat_users_non_active', help: 'total of non active users' }); -RocketChat.metrics.onlineUsers = new client.Gauge({ name: 'rocketchat_users_online', help: 'total of users online' }); -RocketChat.metrics.awayUsers = new client.Gauge({ name: 'rocketchat_users_away', help: 'total of users away' }); -RocketChat.metrics.offlineUsers = new client.Gauge({ name: 'rocketchat_users_offline', help: 'total of users offline' }); - -// Room statistics -RocketChat.metrics.totalRooms = new client.Gauge({ name: 'rocketchat_rooms_total', help: 'total of rooms' }); -RocketChat.metrics.totalChannels = new client.Gauge({ name: 'rocketchat_channels_total', help: 'total of public rooms/channels' }); -RocketChat.metrics.totalPrivateGroups = new client.Gauge({ name: 'rocketchat_private_groups_total', help: 'total of private rooms' }); -RocketChat.metrics.totalDirect = new client.Gauge({ name: 'rocketchat_direct_total', help: 'total of direct rooms' }); -RocketChat.metrics.totalLivechat = new client.Gauge({ name: 'rocketchat_livechat_total', help: 'total of livechat rooms' }); - -// Message statistics -RocketChat.metrics.totalMessages = new client.Gauge({ name: 'rocketchat_messages_total', help: 'total of messages' }); -RocketChat.metrics.totalChannelMessages = new client.Gauge({ name: 'rocketchat_channel_messages_total', help: 'total of messages in public rooms' }); -RocketChat.metrics.totalPrivateGroupMessages = new client.Gauge({ name: 'rocketchat_private_group_messages_total', help: 'total of messages in private rooms' }); -RocketChat.metrics.totalDirectMessages = new client.Gauge({ name: 'rocketchat_direct_messages_total', help: 'total of messages in direct rooms' }); -RocketChat.metrics.totalLivechatMessages = new client.Gauge({ name: 'rocketchat_livechat_messages_total', help: 'total of messages in livechat rooms' }); - -client.register.setDefaultLabels({ - uniqueId: RocketChat.settings.get('uniqueID'), - siteUrl: RocketChat.settings.get('Site_Url'), -}); - -const setPrometheusData = () => { - const date = new Date(); - - client.register.setDefaultLabels({ - unique_id: RocketChat.settings.get('uniqueID'), - site_url: RocketChat.settings.get('Site_Url'), - version: RocketChat.Info.version, - }); - - const sessions = Object.values(Meteor.server.sessions); - const authenticatedSessions = sessions.filter((s) => s.userId); - RocketChat.metrics.ddpSessions.set(sessions.length, date); - RocketChat.metrics.ddpAthenticatedSessions.set(authenticatedSessions.length, date); - RocketChat.metrics.ddpConnectedUsers.set(_.unique(authenticatedSessions.map((s) => s.userId)).length, date); - - if (!RocketChat.models.Statistics) { - return; - } - - const statistics = RocketChat.models.Statistics.findLast(); - if (!statistics) { - return; - } - - RocketChat.metrics.version.set({ version: statistics.version }, 1, date); - RocketChat.metrics.migration.set(RocketChat.Migrations._getControl().version, date); - RocketChat.metrics.instanceCount.set(statistics.instanceCount, date); - RocketChat.metrics.oplogEnabled.set({ enabled: statistics.oplogEnabled }, 1, date); - - // User statistics - RocketChat.metrics.totalUsers.set(statistics.totalUsers, date); - RocketChat.metrics.activeUsers.set(statistics.activeUsers, date); - RocketChat.metrics.nonActiveUsers.set(statistics.nonActiveUsers, date); - RocketChat.metrics.onlineUsers.set(statistics.onlineUsers, date); - RocketChat.metrics.awayUsers.set(statistics.awayUsers, date); - RocketChat.metrics.offlineUsers.set(statistics.offlineUsers, date); - - // Room statistics - RocketChat.metrics.totalRooms.set(statistics.totalRooms, date); - RocketChat.metrics.totalChannels.set(statistics.totalChannels, date); - RocketChat.metrics.totalPrivateGroups.set(statistics.totalPrivateGroups, date); - RocketChat.metrics.totalDirect.set(statistics.totalDirect, date); - RocketChat.metrics.totalLivechat.set(statistics.totalLivechat, date); - - // Message statistics - RocketChat.metrics.totalMessages.set(statistics.totalMessages, date); - RocketChat.metrics.totalChannelMessages.set(statistics.totalChannelMessages, date); - RocketChat.metrics.totalPrivateGroupMessages.set(statistics.totalPrivateGroupMessages, date); - RocketChat.metrics.totalDirectMessages.set(statistics.totalDirectMessages, date); - RocketChat.metrics.totalLivechatMessages.set(statistics.totalLivechatMessages, date); -}; - -const app = connect(); - -// const compression = require('compression'); -// app.use(compression()); - -app.use('/metrics', (req, res) => { - res.setHeader('Content-Type', 'text/plain'); - res.end(RocketChat.promclient.register.metrics()); -}); - -app.use('/', (req, res) => { - const html = ` - - Rocket.Chat Prometheus Exporter - - -

Rocket.Chat Prometheus Exporter

-

Metrics

- - `; - - res.write(html); - res.end(); -}); - -const server = http.createServer(app); - -let timer; -const updatePrometheusConfig = () => { - const port = RocketChat.settings.get('Prometheus_Port'); - const enabled = RocketChat.settings.get('Prometheus_Enabled'); - - if (port == null || enabled == null) { - return; - } - - if (enabled === true) { - server.listen({ - port, - host: process.env.BIND_IP || '0.0.0.0', - }); - timer = Meteor.setInterval(setPrometheusData, 5000); - } else { - server.close(); - Meteor.clearInterval(timer); - } -}; - -RocketChat.settings.get('Prometheus_Enabled', updatePrometheusConfig); -RocketChat.settings.get('Prometheus_Port', updatePrometheusConfig); +RocketChat.metrics = metrics; diff --git a/packages/rocketchat-lib/server/startup/statsTracker.js b/packages/rocketchat-lib/server/startup/statsTracker.js index db7e215a8372..9957ee8b29cc 100644 --- a/packages/rocketchat-lib/server/startup/statsTracker.js +++ b/packages/rocketchat-lib/server/startup/statsTracker.js @@ -1,45 +1,3 @@ -import { StatsD } from 'node-dogstatsd'; +import { StatsTracker } from 'meteor/rocketchat:metrics'; -RocketChat.statsTracker = new (class StatsTracker { - constructor() { - this.StatsD = StatsD; - this.dogstatsd = new this.StatsD(); - } - - track(type, stats, ...args) { - this.dogstatsd[type](`RocketChat.${ stats }`, ...args); - } - - now() { - const hrtime = process.hrtime(); - return (hrtime[0] * 1000000 + hrtime[1] / 1000); - } - - timing(stats, time, tags) { - this.track('timing', stats, time, tags); - } - - increment(stats, time, tags) { - this.track('increment', stats, time, tags); - } - - decrement(stats, time, tags) { - this.track('decrement', stats, time, tags); - } - - histogram(stats, time, tags) { - this.track('histogram', stats, time, tags); - } - - gauge(stats, time, tags) { - this.track('gauge', stats, time, tags); - } - - unique(stats, time, tags) { - this.track('unique', stats, time, tags); - } - - set(stats, time, tags) { - this.track('set', stats, time, tags); - } -}); +RocketChat.statsTracker = StatsTracker; diff --git a/packages/rocketchat-metrics/package.js b/packages/rocketchat-metrics/package.js new file mode 100644 index 000000000000..9b53cfae1e21 --- /dev/null +++ b/packages/rocketchat-metrics/package.js @@ -0,0 +1,16 @@ +Package.describe({ + name: 'rocketchat:metrics', + version: '0.0.1', + summary: 'Rocketchat Metrics', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:settings', + 'rocketchat:models', + 'rocketchat:version', + ]); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-metrics/server/index.js b/packages/rocketchat-metrics/server/index.js new file mode 100644 index 000000000000..7ba988baa9ef --- /dev/null +++ b/packages/rocketchat-metrics/server/index.js @@ -0,0 +1,7 @@ +import { metrics } from './lib/metrics'; +import StatsTracker from './lib/statsTracker'; + +export { + metrics, + StatsTracker, +}; diff --git a/packages/rocketchat-metrics/server/lib/metrics.js b/packages/rocketchat-metrics/server/lib/metrics.js new file mode 100644 index 000000000000..3c8e37ee82ff --- /dev/null +++ b/packages/rocketchat-metrics/server/lib/metrics.js @@ -0,0 +1,187 @@ +import { Meteor } from 'meteor/meteor'; +import { settings } from 'meteor/rocketchat:settings'; +import { Statistics } from 'meteor/rocketchat:models'; +import { Info } from 'meteor/rocketchat:utils'; +import { Migrations } from 'meteor/rocketchat:migrations'; +import client from 'prom-client'; +import connect from 'connect'; +import http from 'http'; +import _ from 'underscore'; + +client.collectDefaultMetrics(); + +export const metrics = {}; + +// one sample metrics only - a counter + +metrics.meteorMethods = new client.Summary({ + name: 'rocketchat_meteor_methods', + help: 'summary of meteor methods count and time', + labelNames: ['method', 'has_connection', 'has_user'], +}); + +metrics.rocketchatCallbacks = new client.Summary({ + name: 'rocketchat_callbacks', + help: 'summary of rocketchat callbacks count and time', + labelNames: ['hook', 'callback'], +}); + +metrics.rocketchatHooks = new client.Summary({ + name: 'rocketchat_hooks', + help: 'summary of rocketchat hooks count and time', + labelNames: ['hook', 'callbacks_length'], +}); + +metrics.rocketchatRestApi = new client.Summary({ + name: 'rocketchat_rest_api', + help: 'summary of rocketchat rest api count and time', + labelNames: ['method', 'entrypoint', 'user_agent', 'status', 'version'], +}); + +metrics.meteorSubscriptions = new client.Summary({ + name: 'rocketchat_meteor_subscriptions', + help: 'summary of meteor subscriptions count and time', + labelNames: ['subscription'], +}); + +metrics.messagesSent = new client.Counter({ name: 'rocketchat_message_sent', help: 'cumulated number of messages sent' }); +metrics.notificationsSent = new client.Counter({ name: 'rocketchat_notification_sent', labelNames: ['notification_type'], help: 'cumulated number of notifications sent' }); + +metrics.ddpSessions = new client.Gauge({ name: 'rocketchat_ddp_sessions_count', help: 'number of open ddp sessions' }); +metrics.ddpAthenticatedSessions = new client.Gauge({ name: 'rocketchat_ddp_sessions_auth', help: 'number of authenticated open ddp sessions' }); +metrics.ddpConnectedUsers = new client.Gauge({ name: 'rocketchat_ddp_connected_users', help: 'number of unique connected users' }); + +metrics.version = new client.Gauge({ name: 'rocketchat_version', labelNames: ['version'], help: 'Rocket.Chat version' }); +metrics.migration = new client.Gauge({ name: 'rocketchat_migration', help: 'migration versoin' }); +metrics.instanceCount = new client.Gauge({ name: 'rocketchat_instance_count', help: 'instances running' }); +metrics.oplogEnabled = new client.Gauge({ name: 'rocketchat_oplog_enabled', labelNames: ['enabled'], help: 'oplog enabled' }); + +// User statistics +metrics.totalUsers = new client.Gauge({ name: 'rocketchat_users_total', help: 'total of users' }); +metrics.activeUsers = new client.Gauge({ name: 'rocketchat_users_active', help: 'total of active users' }); +metrics.nonActiveUsers = new client.Gauge({ name: 'rocketchat_users_non_active', help: 'total of non active users' }); +metrics.onlineUsers = new client.Gauge({ name: 'rocketchat_users_online', help: 'total of users online' }); +metrics.awayUsers = new client.Gauge({ name: 'rocketchat_users_away', help: 'total of users away' }); +metrics.offlineUsers = new client.Gauge({ name: 'rocketchat_users_offline', help: 'total of users offline' }); + +// Room statistics +metrics.totalRooms = new client.Gauge({ name: 'rocketchat_rooms_total', help: 'total of rooms' }); +metrics.totalChannels = new client.Gauge({ name: 'rocketchat_channels_total', help: 'total of public rooms/channels' }); +metrics.totalPrivateGroups = new client.Gauge({ name: 'rocketchat_private_groups_total', help: 'total of private rooms' }); +metrics.totalDirect = new client.Gauge({ name: 'rocketchat_direct_total', help: 'total of direct rooms' }); +metrics.totalLivechat = new client.Gauge({ name: 'rocketchat_livechat_total', help: 'total of livechat rooms' }); + +// Message statistics +metrics.totalMessages = new client.Gauge({ name: 'rocketchat_messages_total', help: 'total of messages' }); +metrics.totalChannelMessages = new client.Gauge({ name: 'rocketchat_channel_messages_total', help: 'total of messages in public rooms' }); +metrics.totalPrivateGroupMessages = new client.Gauge({ name: 'rocketchat_private_group_messages_total', help: 'total of messages in private rooms' }); +metrics.totalDirectMessages = new client.Gauge({ name: 'rocketchat_direct_messages_total', help: 'total of messages in direct rooms' }); +metrics.totalLivechatMessages = new client.Gauge({ name: 'rocketchat_livechat_messages_total', help: 'total of messages in livechat rooms' }); + +client.register.setDefaultLabels({ + uniqueId: settings.get('uniqueID'), + siteUrl: settings.get('Site_Url'), +}); + +const setPrometheusData = () => { + const date = new Date(); + + client.register.setDefaultLabels({ + unique_id: settings.get('uniqueID'), + site_url: settings.get('Site_Url'), + version: Info.version, + }); + + const sessions = Object.values(Meteor.server.sessions); + const authenticatedSessions = sessions.filter((s) => s.userId); + metrics.ddpSessions.set(sessions.length, date); + metrics.ddpAthenticatedSessions.set(authenticatedSessions.length, date); + metrics.ddpConnectedUsers.set(_.unique(authenticatedSessions.map((s) => s.userId)).length, date); + + if (!Statistics) { + return; + } + + const statistics = Statistics.findLast(); + if (!statistics) { + return; + } + + metrics.version.set({ version: statistics.version }, 1, date); + metrics.migration.set(Migrations._getControl().version, date); + metrics.instanceCount.set(statistics.instanceCount, date); + metrics.oplogEnabled.set({ enabled: statistics.oplogEnabled }, 1, date); + + // User statistics + metrics.totalUsers.set(statistics.totalUsers, date); + metrics.activeUsers.set(statistics.activeUsers, date); + metrics.nonActiveUsers.set(statistics.nonActiveUsers, date); + metrics.onlineUsers.set(statistics.onlineUsers, date); + metrics.awayUsers.set(statistics.awayUsers, date); + metrics.offlineUsers.set(statistics.offlineUsers, date); + + // Room statistics + metrics.totalRooms.set(statistics.totalRooms, date); + metrics.totalChannels.set(statistics.totalChannels, date); + metrics.totalPrivateGroups.set(statistics.totalPrivateGroups, date); + metrics.totalDirect.set(statistics.totalDirect, date); + metrics.totalLivechat.set(statistics.totalLivechat, date); + + // Message statistics + metrics.totalMessages.set(statistics.totalMessages, date); + metrics.totalChannelMessages.set(statistics.totalChannelMessages, date); + metrics.totalPrivateGroupMessages.set(statistics.totalPrivateGroupMessages, date); + metrics.totalDirectMessages.set(statistics.totalDirectMessages, date); + metrics.totalLivechatMessages.set(statistics.totalLivechatMessages, date); +}; + +const app = connect(); + +// const compression = require('compression'); +// app.use(compression()); + +app.use('/metrics', (req, res) => { + res.setHeader('Content-Type', 'text/plain'); + res.end(client.register.metrics()); +}); + +app.use('/', (req, res) => { + const html = ` + + Rocket.Chat Prometheus Exporter + + +

Rocket.Chat Prometheus Exporter

+

Metrics

+ + `; + + res.write(html); + res.end(); +}); + +const server = http.createServer(app); + +let timer; +const updatePrometheusConfig = () => { + const port = settings.get('Prometheus_Port'); + const enabled = settings.get('Prometheus_Enabled'); + + if (port == null || enabled == null) { + return; + } + + if (enabled === true) { + server.listen({ + port, + host: process.env.BIND_IP || '0.0.0.0', + }); + timer = Meteor.setInterval(setPrometheusData, 5000); + } else { + server.close(); + Meteor.clearInterval(timer); + } +}; + +settings.get('Prometheus_Enabled', updatePrometheusConfig); +settings.get('Prometheus_Port', updatePrometheusConfig); diff --git a/packages/rocketchat-metrics/server/lib/statsTracker.js b/packages/rocketchat-metrics/server/lib/statsTracker.js new file mode 100644 index 000000000000..5073e303bf65 --- /dev/null +++ b/packages/rocketchat-metrics/server/lib/statsTracker.js @@ -0,0 +1,48 @@ +import { StatsD } from 'node-dogstatsd'; + +export class StatsTracker { + constructor() { + this.StatsD = StatsD; + this.dogstatsd = new this.StatsD(); + } + + track(type, stats, ...args) { + this.dogstatsd[type](`RocketChat.${ stats }`, ...args); + } + + now() { + const hrtime = process.hrtime(); + return (hrtime[0] * 1000000 + hrtime[1] / 1000); + } + + timing(stats, time, tags) { + this.track('timing', stats, time, tags); + } + + increment(stats, time, tags) { + this.track('increment', stats, time, tags); + } + + decrement(stats, time, tags) { + this.track('decrement', stats, time, tags); + } + + histogram(stats, time, tags) { + this.track('histogram', stats, time, tags); + } + + gauge(stats, time, tags) { + this.track('gauge', stats, time, tags); + } + + unique(stats, time, tags) { + this.track('unique', stats, time, tags); + } + + set(stats, time, tags) { + this.track('set', stats, time, tags); + } +} + +export default new StatsTracker(); + From 289c47869f58e6041643e1614bc2f2416112e537 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 14:31:18 -0200 Subject: [PATCH 10/14] Move callbacks to specific package --- .meteor/packages | 3 +- .meteor/versions | 1 + packages/rocketchat-callbacks/client/index.js | 5 + .../rocketchat-callbacks/lib/callbacks.js | 152 ++++++++++++++++++ packages/rocketchat-callbacks/package.js | 15 ++ packages/rocketchat-callbacks/server/index.js | 5 + packages/rocketchat-lib/lib/callbacks.js | 146 +---------------- packages/rocketchat-lib/package.js | 1 + 8 files changed, 183 insertions(+), 145 deletions(-) create mode 100644 packages/rocketchat-callbacks/client/index.js create mode 100644 packages/rocketchat-callbacks/lib/callbacks.js create mode 100644 packages/rocketchat-callbacks/package.js create mode 100644 packages/rocketchat-callbacks/server/index.js diff --git a/.meteor/packages b/.meteor/packages index 9d37a9746896..aa8c0be5aaa7 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -203,4 +203,5 @@ littledata:synced-cron rocketchat:utils rocketchat:settings rocketchat:models -rocketchat:metrics \ No newline at end of file +rocketchat:metrics +rocketchat:callbacks \ No newline at end of file diff --git a/.meteor/versions b/.meteor/versions index 872ff32b8816..76cbc06a8557 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -141,6 +141,7 @@ rocketchat:autotranslate@0.0.1 rocketchat:bigbluebutton@0.0.1 rocketchat:blockstack@0.0.1 rocketchat:bot-helpers@0.0.1 +rocketchat:callbacks@0.0.1 rocketchat:cas@1.0.0 rocketchat:channel-settings@0.0.1 rocketchat:channel-settings-mail-messages@0.0.1 diff --git a/packages/rocketchat-callbacks/client/index.js b/packages/rocketchat-callbacks/client/index.js new file mode 100644 index 000000000000..486af6f60697 --- /dev/null +++ b/packages/rocketchat-callbacks/client/index.js @@ -0,0 +1,5 @@ +import { callbacks } from '../lib/callbacks'; + +export { + callbacks, +}; diff --git a/packages/rocketchat-callbacks/lib/callbacks.js b/packages/rocketchat-callbacks/lib/callbacks.js new file mode 100644 index 000000000000..8a3e34ab0a5e --- /dev/null +++ b/packages/rocketchat-callbacks/lib/callbacks.js @@ -0,0 +1,152 @@ +import { Meteor } from 'meteor/meteor'; +import { Random } from 'meteor/random'; +import { metrics, StatsTracker } from 'meteor/rocketchat:metrics'; +import _ from 'underscore'; + +/* +* Callback hooks provide an easy way to add extra steps to common operations. +* @namespace RocketChat.callbacks +*/ + +export const callbacks = {}; + +if (Meteor.isServer) { + callbacks.showTime = true; + callbacks.showTotalTime = true; +} else { + callbacks.showTime = false; + callbacks.showTotalTime = false; +} + + +/* +* Callback priorities +*/ + +callbacks.priority = { + HIGH: -1000, + MEDIUM: 0, + LOW: 1000, +}; + +const getHooks = (hookName) => callbacks[hookName] || []; + +/* +* Add a callback function to a hook +* @param {String} hook - The name of the hook +* @param {Function} callback - The callback function +*/ + +callbacks.add = function(hook, callback, priority, id = Random.id()) { + if (!_.isNumber(priority)) { + priority = callbacks.priority.MEDIUM; + } + callback.priority = priority; + callback.id = id; + callbacks[hook] = getHooks(hook); + + if (callbacks.showTime === true) { + const err = new Error; + callback.stack = err.stack; + } + + if (callbacks[hook].find((cb) => cb.id === callback.id)) { + return; + } + callbacks[hook].push(callback); + callbacks[hook] = _.sortBy(callbacks[hook], function(callback) { + return callback.priority || callbacks.priority.MEDIUM; + }); +}; + + +/* +* Remove a callback from a hook +* @param {string} hook - The name of the hook +* @param {string} id - The callback's id +*/ + +callbacks.remove = function(hook, id) { + callbacks[hook] = getHooks(hook).filter((callback) => callback.id !== id); +}; + + +/* +* Successively run all of a hook's callbacks on an item +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +* @returns {Object} Returns the item after it's been through all the callbacks for this hook +*/ + +callbacks.run = function(hook, item, constant) { + const callbackItems = callbacks[hook]; + if (!callbackItems || !callbackItems.length) { + return item; + } + + let rocketchatHooksEnd; + if (Meteor.isServer) { + rocketchatHooksEnd = metrics.rocketchatHooks.startTimer({ hook, callbacks_length: callbacks.length }); + } + + let totalTime = 0; + const result = callbackItems.reduce(function(result, callback) { + let rocketchatCallbacksEnd; + if (Meteor.isServer) { + rocketchatCallbacksEnd = metrics.rocketchatCallbacks.startTimer({ hook, callback: callback.id }); + } + const time = callbacks.showTime === true || callbacks.showTotalTime === true ? Date.now() : 0; + + const callbackResult = callback(result, constant); + + if (callbacks.showTime === true || callbacks.showTotalTime === true) { + const currentTime = Date.now() - time; + totalTime += currentTime; + if (callbacks.showTime === true) { + if (Meteor.isServer) { + rocketchatCallbacksEnd(); + StatsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]); + } else { + let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); + stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0]; + console.log(String(currentTime), hook, callback.id, stack); + } + } + } + return (typeof callbackResult === 'undefined') ? result : callbackResult; + }, item); + + if (Meteor.isServer) { + rocketchatHooksEnd(); + } + + if (callbacks.showTotalTime === true) { + if (Meteor.isServer) { + StatsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]); + } else { + console.log(`${ hook }:`, totalTime); + } + } + + return result; + +}; + + +/* +* Successively run all of a hook's callbacks on an item, in async mode (only works on server) +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +*/ + +callbacks.runAsync = function(hook, item, constant) { + const callbackItems = callbacks[hook]; + if (Meteor.isServer && callbackItems && callbackItems.length) { + Meteor.defer(function() { + callbackItems.forEach((callback) => callback(item, constant)); + }); + } + return item; +}; diff --git a/packages/rocketchat-callbacks/package.js b/packages/rocketchat-callbacks/package.js new file mode 100644 index 000000000000..c90fa787f2cb --- /dev/null +++ b/packages/rocketchat-callbacks/package.js @@ -0,0 +1,15 @@ +Package.describe({ + name: 'rocketchat:callbacks', + summary: 'Rocketchat Callbacks', + version: '0.0.1', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:metrics', + ]); + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-callbacks/server/index.js b/packages/rocketchat-callbacks/server/index.js new file mode 100644 index 000000000000..486af6f60697 --- /dev/null +++ b/packages/rocketchat-callbacks/server/index.js @@ -0,0 +1,5 @@ +import { callbacks } from '../lib/callbacks'; + +export { + callbacks, +}; diff --git a/packages/rocketchat-lib/lib/callbacks.js b/packages/rocketchat-lib/lib/callbacks.js index f4c6c2c8a5e9..00a472c89f48 100644 --- a/packages/rocketchat-lib/lib/callbacks.js +++ b/packages/rocketchat-lib/lib/callbacks.js @@ -1,5 +1,4 @@ -import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; +import { callbacks } from 'meteor/rocketchat:callbacks'; import _ from 'underscore'; /* @@ -7,145 +6,4 @@ import _ from 'underscore'; * @namespace RocketChat.callbacks */ -RocketChat.callbacks = {}; - -if (Meteor.isServer) { - RocketChat.callbacks.showTime = true; - RocketChat.callbacks.showTotalTime = true; -} else { - RocketChat.callbacks.showTime = false; - RocketChat.callbacks.showTotalTime = false; -} - - -/* -* Callback priorities -*/ - -RocketChat.callbacks.priority = { - HIGH: -1000, - MEDIUM: 0, - LOW: 1000, -}; - -const getHooks = (hookName) => RocketChat.callbacks[hookName] || []; - -/* -* Add a callback function to a hook -* @param {String} hook - The name of the hook -* @param {Function} callback - The callback function -*/ - -RocketChat.callbacks.add = function(hook, callback, priority, id = Random.id()) { - if (!_.isNumber(priority)) { - priority = RocketChat.callbacks.priority.MEDIUM; - } - callback.priority = priority; - callback.id = id; - RocketChat.callbacks[hook] = getHooks(hook); - - if (RocketChat.callbacks.showTime === true) { - const err = new Error; - callback.stack = err.stack; - } - - if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) { - return; - } - RocketChat.callbacks[hook].push(callback); - RocketChat.callbacks[hook] = _.sortBy(RocketChat.callbacks[hook], function(callback) { - return callback.priority || RocketChat.callbacks.priority.MEDIUM; - }); -}; - - -/* -* Remove a callback from a hook -* @param {string} hook - The name of the hook -* @param {string} id - The callback's id -*/ - -RocketChat.callbacks.remove = function(hook, id) { - RocketChat.callbacks[hook] = getHooks(hook).filter((callback) => callback.id !== id); -}; - - -/* -* Successively run all of a hook's callbacks on an item -* @param {String} hook - The name of the hook -* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -* @param {Object} [constant] - An optional constant that will be passed along to each callback -* @returns {Object} Returns the item after it's been through all the callbacks for this hook -*/ - -RocketChat.callbacks.run = function(hook, item, constant) { - const callbacks = RocketChat.callbacks[hook]; - if (!callbacks || !callbacks.length) { - return item; - } - - let rocketchatHooksEnd; - if (Meteor.isServer) { - rocketchatHooksEnd = RocketChat.metrics.rocketchatHooks.startTimer({ hook, callbacks_length: callbacks.length }); - } - - let totalTime = 0; - const result = callbacks.reduce(function(result, callback) { - let rocketchatCallbacksEnd; - if (Meteor.isServer) { - rocketchatCallbacksEnd = RocketChat.metrics.rocketchatCallbacks.startTimer({ hook, callback: callback.id }); - } - const time = RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true ? Date.now() : 0; - - const callbackResult = callback(result, constant); - - if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { - const currentTime = Date.now() - time; - totalTime += currentTime; - if (RocketChat.callbacks.showTime === true) { - if (Meteor.isServer) { - rocketchatCallbacksEnd(); - RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]); - } else { - let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); - stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0]; - console.log(String(currentTime), hook, callback.id, stack); - } - } - } - return (typeof callbackResult === 'undefined') ? result : callbackResult; - }, item); - - if (Meteor.isServer) { - rocketchatHooksEnd(); - } - - if (RocketChat.callbacks.showTotalTime === true) { - if (Meteor.isServer) { - RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]); - } else { - console.log(`${ hook }:`, totalTime); - } - } - - return result; - -}; - - -/* -* Successively run all of a hook's callbacks on an item, in async mode (only works on server) -* @param {String} hook - The name of the hook -* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -* @param {Object} [constant] - An optional constant that will be passed along to each callback -*/ - -RocketChat.callbacks.runAsync = function(hook, item, constant) { - const callbacks = RocketChat.callbacks[hook]; - if (Meteor.isServer && callbacks && callbacks.length) { - Meteor.defer(function() { - callbacks.forEach((callback) => callback(item, constant)); - }); - } - return item; -}; +RocketChat.callbacks = callbacks; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 354cfa33b8fa..6c0adfafa5ac 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -28,6 +28,7 @@ Package.onUse(function(api) { api.use('rocketchat:models'); api.use('rocketchat:migrations'); api.use('rocketchat:metrics'); + api.use('rocketchat:callbacks'); api.use('rocketchat:accounts'); api.use('modules'); api.use('rocketchat:i18n'); From ccba459815c38cd1a3345470f0078eeca5df3a40 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 14:36:27 -0200 Subject: [PATCH 11/14] Remove unused dependency --- packages/rocketchat-lib/lib/callbacks.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rocketchat-lib/lib/callbacks.js b/packages/rocketchat-lib/lib/callbacks.js index 00a472c89f48..0199a6f05921 100644 --- a/packages/rocketchat-lib/lib/callbacks.js +++ b/packages/rocketchat-lib/lib/callbacks.js @@ -1,5 +1,4 @@ import { callbacks } from 'meteor/rocketchat:callbacks'; -import _ from 'underscore'; /* * Callback hooks provide an easy way to add extra steps to common operations. From 869c15d2c62d2de8f69510e0e11184fa638c6276 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 15:24:13 -0200 Subject: [PATCH 12/14] Move rocketchat-notifications to a specific package --- .meteor/packages | 3 +- .meteor/versions | 1 + .../rocketchat-lib/client/Notifications.js | 88 +------- packages/rocketchat-lib/package.js | 1 + .../server/functions/Notifications.js | 200 +---------------- .../rocketchat-notifications/client/index.js | 5 + .../client/lib/Notifications.js | 88 ++++++++ packages/rocketchat-notifications/package.js | 17 ++ .../rocketchat-notifications/server/index.js | 5 + .../server/lib/Notifications.js | 205 ++++++++++++++++++ 10 files changed, 328 insertions(+), 285 deletions(-) create mode 100644 packages/rocketchat-notifications/client/index.js create mode 100644 packages/rocketchat-notifications/client/lib/Notifications.js create mode 100644 packages/rocketchat-notifications/package.js create mode 100644 packages/rocketchat-notifications/server/index.js create mode 100644 packages/rocketchat-notifications/server/lib/Notifications.js diff --git a/.meteor/packages b/.meteor/packages index aa8c0be5aaa7..5f1a607c9c43 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -204,4 +204,5 @@ rocketchat:utils rocketchat:settings rocketchat:models rocketchat:metrics -rocketchat:callbacks \ No newline at end of file +rocketchat:callbacks +rocketchat:notifications \ No newline at end of file diff --git a/.meteor/versions b/.meteor/versions index 76cbc06a8557..62a4ab476cf2 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -204,6 +204,7 @@ rocketchat:message-star@0.0.1 rocketchat:migrations@0.0.1 rocketchat:models@1.0.0 rocketchat:monitoring@2.30.2_3 +rocketchat:notifications@0.0.1 rocketchat:nrr@1.0.0 rocketchat:oauth2-server@2.0.0 rocketchat:oauth2-server-config@1.0.0 diff --git a/packages/rocketchat-lib/client/Notifications.js b/packages/rocketchat-lib/client/Notifications.js index e24840f2baae..beb33a23d219 100644 --- a/packages/rocketchat-lib/client/Notifications.js +++ b/packages/rocketchat-lib/client/Notifications.js @@ -1,87 +1,3 @@ -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; +import { Notifications } from 'meteor/rocketchat:notifications'; -RocketChat.Notifications = new class { - constructor(...args) { - this.logged = Meteor.userId() !== null; - this.loginCb = []; - Tracker.autorun(() => { - if (Meteor.userId() !== null && this.logged === false) { - this.loginCb.forEach((cb) => cb()); - } - return this.logged = Meteor.userId() !== null; - }); - this.debug = false; - this.streamAll = new Meteor.Streamer('notify-all'); - this.streamLogged = new Meteor.Streamer('notify-logged'); - this.streamRoom = new Meteor.Streamer('notify-room'); - this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); - this.streamUser = new Meteor.Streamer('notify-user'); - if (this.debug === true) { - this.onAll(function() { - return console.log('RocketChat.Notifications: onAll', args); - }); - this.onUser(function() { - return console.log('RocketChat.Notifications: onAll', args); - }); - } - } - - onLogin(cb) { - this.loginCb.push(cb); - if (this.logged) { - return cb(); - } - } - notifyRoom(room, eventName, ...args) { - if (this.debug === true) { - console.log('RocketChat.Notifications: notifyRoom', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoom.emit.apply(this.streamRoom, args); - } - notifyUser(userId, eventName, ...args) { - if (this.debug === true) { - console.log('RocketChat.Notifications: notifyUser', [userId, eventName, ...args]); - } - args.unshift(`${ userId }/${ eventName }`); - return this.streamUser.emit.apply(this.streamUser, args); - } - notifyUsersOfRoom(room, eventName, ...args) { - if (this.debug === true) { - console.log('RocketChat.Notifications: notifyUsersOfRoom', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoomUsers.emit.apply(this.streamRoomUsers, args); - } - onAll(eventName, callback) { - return this.streamAll.on(eventName, callback); - } - onLogged(eventName, callback) { - return this.onLogin(() => this.streamLogged.on(eventName, callback)); - } - onRoom(room, eventName, callback) { - if (this.debug === true) { - this.streamRoom.on(room, function() { - return console.log(`RocketChat.Notifications: onRoom ${ room }`, [room, eventName, callback]); - }); - } - return this.streamRoom.on(`${ room }/${ eventName }`, callback); - } - onUser(eventName, callback) { - return this.streamUser.on(`${ Meteor.userId() }/${ eventName }`, callback); - } - unAll(callback) { - return this.streamAll.removeListener('notify', callback); - } - unLogged(callback) { - return this.streamLogged.removeListener('notify', callback); - } - unRoom(room, eventName, callback) { - return this.streamRoom.removeListener(`${ room }/${ eventName }`, callback); - } - unUser(eventName, callback) { - return this.streamUser.removeListener(`${ Meteor.userId() }/${ eventName }`, callback); - } - -}; +RocketChat.Notifications = Notifications; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 6c0adfafa5ac..2fa245e2e3d5 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -29,6 +29,7 @@ Package.onUse(function(api) { api.use('rocketchat:migrations'); api.use('rocketchat:metrics'); api.use('rocketchat:callbacks'); + api.use('rocketchat:notifications'); api.use('rocketchat:accounts'); api.use('modules'); api.use('rocketchat:i18n'); diff --git a/packages/rocketchat-lib/server/functions/Notifications.js b/packages/rocketchat-lib/server/functions/Notifications.js index 221c47fb1156..beb33a23d219 100644 --- a/packages/rocketchat-lib/server/functions/Notifications.js +++ b/packages/rocketchat-lib/server/functions/Notifications.js @@ -1,199 +1,3 @@ +import { Notifications } from 'meteor/rocketchat:notifications'; -import { Meteor } from 'meteor/meteor'; -import { DDPCommon } from 'meteor/ddp-common'; - -const changedPayload = function(collection, id, fields) { - return DDPCommon.stringifyDDP({ - msg: 'changed', - collection, - id, - fields, - }); -}; -const send = function(self, msg) { - if (!self.socket) { - return; - } - self.socket.send(msg); -}; -class RoomStreamer extends Meteor.Streamer { - _publish(publication, eventName, options) { - super._publish(publication, eventName, options); - const uid = Meteor.userId(); - if (/rooms-changed/.test(eventName)) { - const roomEvent = (...args) => send(publication._session, changedPayload(this.subscriptionName, 'id', { - eventName: `${ uid }/rooms-changed`, - args, - })); - const rooms = RocketChat.models.Subscriptions.find({ 'u._id': uid }, { fields: { rid: 1 } }).fetch(); - rooms.forEach(({ rid }) => { - this.on(rid, roomEvent); - }); - - const userEvent = (clientAction, { rid }) => { - switch (clientAction) { - case 'inserted': - rooms.push({ rid }); - this.on(rid, roomEvent); - break; - - case 'removed': - this.removeListener(rid, roomEvent); - break; - } - }; - this.on(uid, userEvent); - - publication.onStop(() => { - this.removeListener(uid, userEvent); - rooms.forEach(({ rid }) => this.removeListener(rid, roomEvent)); - }); - } - } -} - -RocketChat.Notifications = new class { - constructor() { - this.debug = false; - this.streamAll = new Meteor.Streamer('notify-all'); - this.streamLogged = new Meteor.Streamer('notify-logged'); - this.streamRoom = new Meteor.Streamer('notify-room'); - this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); - this.streamUser = new RoomStreamer('notify-user'); - this.streamAll.allowWrite('none'); - this.streamLogged.allowWrite('none'); - this.streamRoom.allowWrite('none'); - this.streamRoomUsers.allowWrite(function(eventName, ...args) { - const [roomId, e] = eventName.split('/'); - // const user = Meteor.users.findOne(this.userId, { - // fields: { - // username: 1 - // } - // }); - if (RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { - const subscriptions = RocketChat.models.Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); - subscriptions.forEach((subscription) => RocketChat.Notifications.notifyUser(subscription.u._id, e, ...args)); - } - return false; - }); - this.streamUser.allowWrite('logged'); - this.streamAll.allowRead('all'); - this.streamLogged.allowRead('logged'); - this.streamRoom.allowRead(function(eventName, extraData) { - const [roomId] = eventName.split('/'); - const room = RocketChat.models.Rooms.findOneById(roomId); - if (!room) { - console.warn(`Invalid streamRoom eventName: "${ eventName }"`); - return false; - } - if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { - return true; - } - if (this.userId == null) { - return false; - } - const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); - return subscription != null; - }); - this.streamRoomUsers.allowRead('none'); - this.streamUser.allowRead(function(eventName) { - const [userId] = eventName.split('/'); - return (this.userId != null) && this.userId === userId; - }); - } - - notifyAll(eventName, ...args) { - if (this.debug === true) { - console.log('notifyAll', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamAll.emit.apply(this.streamAll, args); - } - - notifyLogged(eventName, ...args) { - if (this.debug === true) { - console.log('notifyLogged', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamLogged.emit.apply(this.streamLogged, args); - } - - notifyRoom(room, eventName, ...args) { - if (this.debug === true) { - console.log('notifyRoom', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoom.emit.apply(this.streamRoom, args); - } - - notifyUser(userId, eventName, ...args) { - if (this.debug === true) { - console.log('notifyUser', [userId, eventName, ...args]); - } - args.unshift(`${ userId }/${ eventName }`); - return this.streamUser.emit.apply(this.streamUser, args); - } - - notifyAllInThisInstance(eventName, ...args) { - if (this.debug === true) { - console.log('notifyAll', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamAll.emitWithoutBroadcast.apply(this.streamAll, args); - } - - notifyLoggedInThisInstance(eventName, ...args) { - if (this.debug === true) { - console.log('notifyLogged', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamLogged.emitWithoutBroadcast.apply(this.streamLogged, args); - } - - notifyRoomInThisInstance(room, eventName, ...args) { - if (this.debug === true) { - console.log('notifyRoomAndBroadcast', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoom.emitWithoutBroadcast.apply(this.streamRoom, args); - } - - notifyUserInThisInstance(userId, eventName, ...args) { - if (this.debug === true) { - console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); - } - args.unshift(`${ userId }/${ eventName }`); - return this.streamUser.emitWithoutBroadcast.apply(this.streamUser, args); - } -}; - -RocketChat.Notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { - const [roomId, e] = eventName.split('/'); - - if (e === 'webrtc') { - return true; - } - if (e === 'typing') { - const key = RocketChat.settings.get('UI_Use_Real_Name') ? 'name' : 'username'; - // typing from livechat widget - if (extraData && extraData.token) { - const room = RocketChat.models.Rooms.findOneById(roomId); - if (room && room.t === 'l' && room.v.token === extraData.token) { - return true; - } - } - - const user = Meteor.users.findOne(this.userId, { - fields: { - [key]: 1, - }, - }); - - if (!user) { - return false; - } - - return user[key] === username; - } - return false; -}); +RocketChat.Notifications = Notifications; diff --git a/packages/rocketchat-notifications/client/index.js b/packages/rocketchat-notifications/client/index.js new file mode 100644 index 000000000000..edafa8c4a7fd --- /dev/null +++ b/packages/rocketchat-notifications/client/index.js @@ -0,0 +1,5 @@ +import Notifications from './lib/Notifications'; + +export { + Notifications, +}; diff --git a/packages/rocketchat-notifications/client/lib/Notifications.js b/packages/rocketchat-notifications/client/lib/Notifications.js new file mode 100644 index 000000000000..e9dd1853189d --- /dev/null +++ b/packages/rocketchat-notifications/client/lib/Notifications.js @@ -0,0 +1,88 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; + +class Notifications { + constructor(...args) { + this.logged = Meteor.userId() !== null; + this.loginCb = []; + Tracker.autorun(() => { + if (Meteor.userId() !== null && this.logged === false) { + this.loginCb.forEach((cb) => cb()); + } + return this.logged = Meteor.userId() !== null; + }); + this.debug = false; + this.streamAll = new Meteor.Streamer('notify-all'); + this.streamLogged = new Meteor.Streamer('notify-logged'); + this.streamRoom = new Meteor.Streamer('notify-room'); + this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); + this.streamUser = new Meteor.Streamer('notify-user'); + if (this.debug === true) { + this.onAll(function() { + return console.log('RocketChat.Notifications: onAll', args); + }); + this.onUser(function() { + return console.log('RocketChat.Notifications: onAll', args); + }); + } + } + + onLogin(cb) { + this.loginCb.push(cb); + if (this.logged) { + return cb(); + } + } + notifyRoom(room, eventName, ...args) { + if (this.debug === true) { + console.log('RocketChat.Notifications: notifyRoom', [room, eventName, ...args]); + } + args.unshift(`${ room }/${ eventName }`); + return this.streamRoom.emit.apply(this.streamRoom, args); + } + notifyUser(userId, eventName, ...args) { + if (this.debug === true) { + console.log('RocketChat.Notifications: notifyUser', [userId, eventName, ...args]); + } + args.unshift(`${ userId }/${ eventName }`); + return this.streamUser.emit.apply(this.streamUser, args); + } + notifyUsersOfRoom(room, eventName, ...args) { + if (this.debug === true) { + console.log('RocketChat.Notifications: notifyUsersOfRoom', [room, eventName, ...args]); + } + args.unshift(`${ room }/${ eventName }`); + return this.streamRoomUsers.emit.apply(this.streamRoomUsers, args); + } + onAll(eventName, callback) { + return this.streamAll.on(eventName, callback); + } + onLogged(eventName, callback) { + return this.onLogin(() => this.streamLogged.on(eventName, callback)); + } + onRoom(room, eventName, callback) { + if (this.debug === true) { + this.streamRoom.on(room, function() { + return console.log(`RocketChat.Notifications: onRoom ${ room }`, [room, eventName, callback]); + }); + } + return this.streamRoom.on(`${ room }/${ eventName }`, callback); + } + onUser(eventName, callback) { + return this.streamUser.on(`${ Meteor.userId() }/${ eventName }`, callback); + } + unAll(callback) { + return this.streamAll.removeListener('notify', callback); + } + unLogged(callback) { + return this.streamLogged.removeListener('notify', callback); + } + unRoom(room, eventName, callback) { + return this.streamRoom.removeListener(`${ room }/${ eventName }`, callback); + } + unUser(eventName, callback) { + return this.streamUser.removeListener(`${ Meteor.userId() }/${ eventName }`, callback); + } +} + +export default new Notifications(); diff --git a/packages/rocketchat-notifications/package.js b/packages/rocketchat-notifications/package.js new file mode 100644 index 000000000000..78368ec7c4ad --- /dev/null +++ b/packages/rocketchat-notifications/package.js @@ -0,0 +1,17 @@ +Package.describe({ + name: 'rocketchat:notifications', + version: '0.0.1', + summary: 'Rocketchat Notifications', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:models', + 'rocketchat:settings', + 'rocketchat:streamer', + ]); + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-notifications/server/index.js b/packages/rocketchat-notifications/server/index.js new file mode 100644 index 000000000000..edafa8c4a7fd --- /dev/null +++ b/packages/rocketchat-notifications/server/index.js @@ -0,0 +1,5 @@ +import Notifications from './lib/Notifications'; + +export { + Notifications, +}; diff --git a/packages/rocketchat-notifications/server/lib/Notifications.js b/packages/rocketchat-notifications/server/lib/Notifications.js new file mode 100644 index 000000000000..f424c2222c3e --- /dev/null +++ b/packages/rocketchat-notifications/server/lib/Notifications.js @@ -0,0 +1,205 @@ +import { Meteor } from 'meteor/meteor'; +import { DDPCommon } from 'meteor/ddp-common'; +import { Subscriptions, Rooms } from 'meteor/rocketchat:models'; +import { settings } from 'meteor/rocketchat:settings'; + +const changedPayload = function(collection, id, fields) { + return DDPCommon.stringifyDDP({ + msg: 'changed', + collection, + id, + fields, + }); +}; +const send = function(self, msg) { + if (!self.socket) { + return; + } + self.socket.send(msg); +}; +class RoomStreamer extends Meteor.Streamer { + _publish(publication, eventName, options) { + super._publish(publication, eventName, options); + const uid = Meteor.userId(); + if (/rooms-changed/.test(eventName)) { + const roomEvent = (...args) => send(publication._session, changedPayload(this.subscriptionName, 'id', { + eventName: `${ uid }/rooms-changed`, + args, + })); + const rooms = Subscriptions.find({ 'u._id': uid }, { fields: { rid: 1 } }).fetch(); + rooms.forEach(({ rid }) => { + this.on(rid, roomEvent); + }); + + const userEvent = (clientAction, { rid }) => { + switch (clientAction) { + case 'inserted': + rooms.push({ rid }); + this.on(rid, roomEvent); + break; + + case 'removed': + this.removeListener(rid, roomEvent); + break; + } + }; + this.on(uid, userEvent); + + publication.onStop(() => { + this.removeListener(uid, userEvent); + rooms.forEach(({ rid }) => this.removeListener(rid, roomEvent)); + }); + } + } +} + +class Notifications { + constructor() { + this.debug = false; + this.notifyUser = this.notifyUser.bind(this); + this.streamAll = new Meteor.Streamer('notify-all'); + this.streamLogged = new Meteor.Streamer('notify-logged'); + this.streamRoom = new Meteor.Streamer('notify-room'); + this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); + this.streamUser = new RoomStreamer('notify-user'); + this.streamAll.allowWrite('none'); + this.streamLogged.allowWrite('none'); + this.streamRoom.allowWrite('none'); + this.streamRoomUsers.allowWrite(function(eventName, ...args) { + const [roomId, e] = eventName.split('/'); + // const user = Meteor.users.findOne(this.userId, { + // fields: { + // username: 1 + // } + // }); + if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { + const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); + subscriptions.forEach((subscription) => this.notifyUser(subscription.u._id, e, ...args)); + } + return false; + }); + this.streamUser.allowWrite('logged'); + this.streamAll.allowRead('all'); + this.streamLogged.allowRead('logged'); + this.streamRoom.allowRead(function(eventName, extraData) { + const [roomId] = eventName.split('/'); + const room = Rooms.findOneById(roomId); + if (!room) { + console.warn(`Invalid streamRoom eventName: "${ eventName }"`); + return false; + } + if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { + return true; + } + if (this.userId == null) { + return false; + } + const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); + return subscription != null; + }); + this.streamRoomUsers.allowRead('none'); + this.streamUser.allowRead(function(eventName) { + const [userId] = eventName.split('/'); + return (this.userId != null) && this.userId === userId; + }); + } + + notifyAll(eventName, ...args) { + if (this.debug === true) { + console.log('notifyAll', [eventName, ...args]); + } + args.unshift(eventName); + return this.streamAll.emit.apply(this.streamAll, args); + } + + notifyLogged(eventName, ...args) { + if (this.debug === true) { + console.log('notifyLogged', [eventName, ...args]); + } + args.unshift(eventName); + return this.streamLogged.emit.apply(this.streamLogged, args); + } + + notifyRoom(room, eventName, ...args) { + if (this.debug === true) { + console.log('notifyRoom', [room, eventName, ...args]); + } + args.unshift(`${ room }/${ eventName }`); + return this.streamRoom.emit.apply(this.streamRoom, args); + } + + notifyUser(userId, eventName, ...args) { + if (this.debug === true) { + console.log('notifyUser', [userId, eventName, ...args]); + } + args.unshift(`${ userId }/${ eventName }`); + return this.streamUser.emit.apply(this.streamUser, args); + } + + notifyAllInThisInstance(eventName, ...args) { + if (this.debug === true) { + console.log('notifyAll', [eventName, ...args]); + } + args.unshift(eventName); + return this.streamAll.emitWithoutBroadcast.apply(this.streamAll, args); + } + + notifyLoggedInThisInstance(eventName, ...args) { + if (this.debug === true) { + console.log('notifyLogged', [eventName, ...args]); + } + args.unshift(eventName); + return this.streamLogged.emitWithoutBroadcast.apply(this.streamLogged, args); + } + + notifyRoomInThisInstance(room, eventName, ...args) { + if (this.debug === true) { + console.log('notifyRoomAndBroadcast', [room, eventName, ...args]); + } + args.unshift(`${ room }/${ eventName }`); + return this.streamRoom.emitWithoutBroadcast.apply(this.streamRoom, args); + } + + notifyUserInThisInstance(userId, eventName, ...args) { + if (this.debug === true) { + console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); + } + args.unshift(`${ userId }/${ eventName }`); + return this.streamUser.emitWithoutBroadcast.apply(this.streamUser, args); + } +} + +const notifications = new Notifications(); + +notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { + const [roomId, e] = eventName.split('/'); + + if (e === 'webrtc') { + return true; + } + if (e === 'typing') { + const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; + // typing from livechat widget + if (extraData && extraData.token) { + const room = Rooms.findOneById(roomId); + if (room && room.t === 'l' && room.v.token === extraData.token) { + return true; + } + } + + const user = Meteor.users.findOne(this.userId, { + fields: { + [key]: 1, + }, + }); + + if (!user) { + return false; + } + + return user[key] === username; + } + return false; +}); + +export default notifications; From cbb352928b328d9483fdf224c1e2d73a5d74e116 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 27 Dec 2018 15:40:50 -0200 Subject: [PATCH 13/14] Remove dependency between mailer and lib --- packages/rocketchat-mailer/package.js | 1 + packages/rocketchat-mailer/server/api.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-mailer/package.js b/packages/rocketchat-mailer/package.js index f2b41e9ceed7..66b31392a1e6 100644 --- a/packages/rocketchat-mailer/package.js +++ b/packages/rocketchat-mailer/package.js @@ -10,6 +10,7 @@ Package.onUse(function(api) { 'email', 'ddp-rate-limiter', 'rocketchat:i18n', + 'rocketchat:settings', ]); api.mainModule('server/api.js', 'server'); diff --git a/packages/rocketchat-mailer/server/api.js b/packages/rocketchat-mailer/server/api.js index bf5ec59b972b..c43830c276ea 100644 --- a/packages/rocketchat-mailer/server/api.js +++ b/packages/rocketchat-mailer/server/api.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Email } from 'meteor/email'; import { TAPi18n } from 'meteor/tap:i18n'; +import { settings } from 'meteor/rocketchat:settings'; import _ from 'underscore'; import s from 'underscore.string'; import juice from 'juice'; @@ -33,8 +34,8 @@ export const replace = function replace(str, data = {}) { }; export const replaceEscaped = (str, data = {}) => replace(str, { - Site_Name: s.escapeHTML(RocketChat.settings.get('Site_Name')), - Site_Url: s.escapeHTML(RocketChat.settings.get('Site_Url')), + Site_Name: s.escapeHTML(settings.get('Site_Name')), + Site_Url: s.escapeHTML(settings.get('Site_Url')), ...Object.entries(data).reduce((ret, [key, value]) => { ret[key] = s.escapeHTML(value); return ret; From b40ea2d7e0c9f2c827f77733b15c5b6f3fb47e29 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Thu, 10 Jan 2019 16:10:50 -0200 Subject: [PATCH 14/14] Merge branch 'develop' into globals/move-rocketchat-callbacks --- .meteor/packages | 1 + .meteor/versions | 1 + packages/rocketchat-api/server/api.js | 1 + .../client/admin/callback.html | 16 +++ .../rocketchat-cloud/client/admin/callback.js | 37 +++++ .../rocketchat-cloud/client/admin/cloud.html | 83 ++++++++++++ .../rocketchat-cloud/client/admin/cloud.js | 93 +++++++++++++ packages/rocketchat-cloud/client/index.js | 28 ++++ packages/rocketchat-cloud/package.js | 17 +++ .../server/functions/connectWorkspace.js | 70 ++++++++++ .../functions/finishOAuthAuthorization.js | 50 +++++++ .../functions/getOAuthAuthorizationUrl.js | 15 ++ .../server/functions/getRedirectUri.js | 3 + .../functions/getWorkspaceAccessTokens.js | 50 +++++++ .../server/functions/getWorkspaceLicense.js | 34 +++++ .../functions/retrieveRegistrationStatus.js | 18 +++ packages/rocketchat-cloud/server/index.js | 11 ++ packages/rocketchat-cloud/server/methods.js | 72 ++++++++++ .../rocketchat-e2e/client/rocketchat.e2e.js | 2 +- packages/rocketchat-i18n/i18n/en.i18n.json | 19 ++- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 3 +- .../rocketchat-lib/client/models/Avatars.js | 3 - .../rocketchat-lib/client/models/Uploads.js | 3 - .../client/models/UserDataFiles.js | 3 - .../rocketchat-lib/client/models/_Base.js | 3 - .../rocketchat-lib/client/models/index.js | 11 ++ packages/rocketchat-lib/package.js | 20 +-- .../lib/{metrics.js => metrics_import.js} | 0 .../rocketchat-lib/server/models/Avatars.js | 3 - .../server/models/ExportOperations.js | 3 - .../rocketchat-lib/server/models/Messages.js | 3 - .../rocketchat-lib/server/models/Reports.js | 3 - .../rocketchat-lib/server/models/Rooms.js | 4 - .../rocketchat-lib/server/models/Settings.js | 3 - .../server/models/Subscriptions.js | 4 - .../rocketchat-lib/server/models/Uploads.js | 3 - .../server/models/UserDataFiles.js | 3 - .../rocketchat-lib/server/models/Users.js | 4 - .../rocketchat-lib/server/models/_Base.js | 3 - .../rocketchat-lib/server/models/index.js | 25 ++++ .../rocketchat-lib/server/startup/settings.js | 128 ++++++++++++++++++ ...statsTracker.js => statsTracker_import.js} | 0 .../client/setupWizard.js | 8 ++ .../server/functions/get.js | 10 +- .../rocketchat-statistics/server/index.js | 2 +- .../{Statistics.js => Statistics_import.js} | 0 .../client/tabs/userInfo.html | 4 +- .../client/components/header/header.html | 4 +- packages/rocketchat-version-check/package.js | 1 + .../server/functions/getNewUpdates.js | 8 ++ server/lib/cordova.js | 8 ++ server/startup/cron.js | 9 ++ server/startup/migrations/v099.js | 10 +- server/startup/migrations/v137.js | 10 ++ tests/end-to-end/ui/00-login.js | 2 +- 55 files changed, 851 insertions(+), 81 deletions(-) create mode 100644 packages/rocketchat-cloud/client/admin/callback.html create mode 100644 packages/rocketchat-cloud/client/admin/callback.js create mode 100644 packages/rocketchat-cloud/client/admin/cloud.html create mode 100644 packages/rocketchat-cloud/client/admin/cloud.js create mode 100644 packages/rocketchat-cloud/client/index.js create mode 100644 packages/rocketchat-cloud/package.js create mode 100644 packages/rocketchat-cloud/server/functions/connectWorkspace.js create mode 100644 packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js create mode 100644 packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js create mode 100644 packages/rocketchat-cloud/server/functions/getRedirectUri.js create mode 100644 packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js create mode 100644 packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js create mode 100644 packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js create mode 100644 packages/rocketchat-cloud/server/index.js create mode 100644 packages/rocketchat-cloud/server/methods.js delete mode 100644 packages/rocketchat-lib/client/models/Avatars.js delete mode 100644 packages/rocketchat-lib/client/models/Uploads.js delete mode 100644 packages/rocketchat-lib/client/models/UserDataFiles.js delete mode 100644 packages/rocketchat-lib/client/models/_Base.js create mode 100644 packages/rocketchat-lib/client/models/index.js rename packages/rocketchat-lib/server/lib/{metrics.js => metrics_import.js} (100%) delete mode 100644 packages/rocketchat-lib/server/models/Avatars.js delete mode 100644 packages/rocketchat-lib/server/models/ExportOperations.js delete mode 100644 packages/rocketchat-lib/server/models/Messages.js delete mode 100644 packages/rocketchat-lib/server/models/Reports.js delete mode 100644 packages/rocketchat-lib/server/models/Rooms.js delete mode 100644 packages/rocketchat-lib/server/models/Settings.js delete mode 100644 packages/rocketchat-lib/server/models/Subscriptions.js delete mode 100644 packages/rocketchat-lib/server/models/Uploads.js delete mode 100644 packages/rocketchat-lib/server/models/UserDataFiles.js delete mode 100644 packages/rocketchat-lib/server/models/Users.js delete mode 100644 packages/rocketchat-lib/server/models/_Base.js create mode 100644 packages/rocketchat-lib/server/models/index.js rename packages/rocketchat-lib/server/startup/{statsTracker.js => statsTracker_import.js} (100%) rename packages/rocketchat-statistics/server/models/{Statistics.js => Statistics_import.js} (100%) create mode 100644 server/startup/migrations/v137.js diff --git a/.meteor/packages b/.meteor/packages index aa8c0be5aaa7..f5643db382fe 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -51,6 +51,7 @@ rocketchat:bot-helpers rocketchat:cas rocketchat:channel-settings rocketchat:channel-settings-mail-messages +rocketchat:cloud rocketchat:colors rocketchat:crowd rocketchat:custom-oauth diff --git a/.meteor/versions b/.meteor/versions index 76cbc06a8557..6098cf93abe3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -145,6 +145,7 @@ rocketchat:callbacks@0.0.1 rocketchat:cas@1.0.0 rocketchat:channel-settings@0.0.1 rocketchat:channel-settings-mail-messages@0.0.1 +rocketchat:cloud@0.0.1 rocketchat:colors@0.0.1 rocketchat:cors@0.0.1 rocketchat:crowd@1.0.0 diff --git a/packages/rocketchat-api/server/api.js b/packages/rocketchat-api/server/api.js index d28a95d95f49..ff10ec4a4b1c 100644 --- a/packages/rocketchat-api/server/api.js +++ b/packages/rocketchat-api/server/api.js @@ -386,6 +386,7 @@ const defaultOptionsEndpoint = function _defaultOptionsEndpoint() { if (RocketChat.settings.get('API_Enable_CORS') === true) { this.response.writeHead(200, { 'Access-Control-Allow-Origin': RocketChat.settings.get('API_CORS_Origin'), + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, HEAD, PATCH', 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-User-Id, X-Auth-Token, x-visitor-token', }); } else { diff --git a/packages/rocketchat-cloud/client/admin/callback.html b/packages/rocketchat-cloud/client/admin/callback.html new file mode 100644 index 000000000000..5d6c9c432f4c --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/callback.html @@ -0,0 +1,16 @@ + diff --git a/packages/rocketchat-cloud/client/admin/callback.js b/packages/rocketchat-cloud/client/admin/callback.js new file mode 100644 index 000000000000..adf4bac1d211 --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/callback.js @@ -0,0 +1,37 @@ +import './callback.html'; + +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Template } from 'meteor/templating'; + +import { FlowRouter } from 'meteor/kadira:flow-router'; + +import queryString from 'query-string'; + +Template.cloudCallback.onCreated(function() { + const instance = this; + + instance.loading = new ReactiveVar(true); + instance.callbackError = new ReactiveVar({ error: false }); + + const params = queryString.parse(location.search); + + if (params.error_code) { + instance.callbackError.set({ error: true, errorCode: params.error_code }); + } else { + Meteor.call('cloud:finishOAuthAuthorization', params.code, params.state, (error) => { + if (error) { + console.warn('cloud:finishOAuthAuthorization', error); + return; + } + + FlowRouter.go('/admin/cloud'); + }); + } +}); + +Template.cloudCallback.helpers({ + callbackError() { + return Template.instance().callbackError.get(); + }, +}); diff --git a/packages/rocketchat-cloud/client/admin/cloud.html b/packages/rocketchat-cloud/client/admin/cloud.html new file mode 100644 index 000000000000..c09e1281a77e --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/cloud.html @@ -0,0 +1,83 @@ + diff --git a/packages/rocketchat-cloud/client/admin/cloud.js b/packages/rocketchat-cloud/client/admin/cloud.js new file mode 100644 index 000000000000..e68f0e890532 --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/cloud.js @@ -0,0 +1,93 @@ +import './cloud.html'; + +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Template } from 'meteor/templating'; +import { t } from 'meteor/rocketchat:utils'; + +import queryString from 'query-string'; +import toastr from 'toastr'; + +Template.cloud.onCreated(function() { + const instance = this; + instance.info = new ReactiveVar(); + instance.loading = new ReactiveVar(true); + + instance.loadRegStatus = function _loadRegStatus() { + Meteor.call('cloud:checkRegisterStatus', (error, info) => { + if (error) { + console.warn('cloud:checkRegisterStatus', error); + return; + } + + instance.info.set(info); + instance.loading.set(false); + }); + }; + + instance.connectWorkspace = function _connectWorkspace(token) { + Meteor.call('cloud:connectWorkspace', token, (error, success) => { + if (error) { + toastr.error(error); + instance.loadRegStatus(); + return; + } + + if (!success) { + toastr.error('Invalid token'); + instance.loadRegStatus(); + return; + } + + toastr.success(t('Connected')); + + instance.loadRegStatus(); + }); + }; + + const params = queryString.parse(location.search); + + if (params.token) { + instance.connectWorkspace(); + } else { + instance.loadRegStatus(); + } +}); + +Template.cloud.helpers({ + info() { + return Template.instance().info.get(); + }, +}); + +Template.cloud.events({ + 'click .update-email-btn'() { + const val = $('input[name=cloudEmail]').val(); + + Meteor.call('cloud:updateEmail', val, (error) => { + if (error) { + console.warn(error); + return; + } + + toastr.success(t('Saved')); + }); + }, + + 'click .login-btn'() { + Meteor.call('cloud:getOAuthAuthorizationUrl', (error, url) => { + if (error) { + console.warn(error); + return; + } + + window.location.href = url; + }); + }, + + 'click .connect-btn'(e, i) { + const token = $('input[name=cloudToken]').val(); + + i.connectWorkspace(token); + }, +}); diff --git a/packages/rocketchat-cloud/client/index.js b/packages/rocketchat-cloud/client/index.js new file mode 100644 index 000000000000..e5dddf8944bf --- /dev/null +++ b/packages/rocketchat-cloud/client/index.js @@ -0,0 +1,28 @@ +import './admin/callback'; +import './admin/cloud'; + +import { BlazeLayout } from 'meteor/kadira:blaze-layout'; +import { FlowRouter } from 'meteor/kadira:flow-router'; + +FlowRouter.route('/admin/cloud', { + name: 'cloud-config', + action() { + BlazeLayout.render('main', { center: 'cloud', old: true }); + }, +}); + +FlowRouter.route('/admin/cloud/oauth-callback', { + name: 'cloud-oauth-callback', + action() { + BlazeLayout.render('main', { center: 'cloudCallback', old: true }); + }, +}); + +RocketChat.AdminBox.addOption({ + icon: 'cloud-plus', + href: 'admin/cloud', + i18nLabel: 'Cloud', + permissionGranted() { + return RocketChat.authz.hasAtLeastOnePermission(['manage-cloud']); + }, +}); diff --git a/packages/rocketchat-cloud/package.js b/packages/rocketchat-cloud/package.js new file mode 100644 index 000000000000..52480e9e66d6 --- /dev/null +++ b/packages/rocketchat-cloud/package.js @@ -0,0 +1,17 @@ +Package.describe({ + name: 'rocketchat:cloud', + version: '0.0.1', + summary: 'Package which interacts with the Rocket.Chat Cloud offerings.', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:lib', + 'templating', + ]); + + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-cloud/server/functions/connectWorkspace.js b/packages/rocketchat-cloud/server/functions/connectWorkspace.js new file mode 100644 index 000000000000..f34cecd22449 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/connectWorkspace.js @@ -0,0 +1,70 @@ +import querystring from 'querystring'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; + +export function connectWorkspace(token) { + const { registeredWithWizard } = retrieveRegistrationStatus(); + if (!registeredWithWizard) { + return false; + } + + const redirectUri = getRedirectUri(); + + const regInfo = { + email: RocketChat.settings.get('Organization_Email'), + client_name: RocketChat.settings.get('Site_Name'), + redirect_uris: [redirectUri], + }; + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + let result; + try { + result = HTTP.post(`${ cloudUrl }/api/oauth/clients`, { + headers: { + Authorization: `Bearer ${ token }`, + }, + data: regInfo, + }); + } catch (e) { + return false; + } + + const { data } = result; + + if (!data) { + return false; + } + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Id', data.workspaceId); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Name', data.client_name); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Id', data.client_id); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Secret', data.client_secret); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Secret_Expires_At', data.client_secret_expires_at); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Registration_Client_Uri', data.registration_client_uri); + + // Now that we have the client id and secret, let's get the access token + let authTokenResult; + try { + authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id: data.client_id, + client_secret: data.client_secret, + grant_type: 'client_credentials', + redirect_uri: redirectUri, + }), + }); + } catch (e) { + return false; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + + return true; +} diff --git a/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js b/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js new file mode 100644 index 000000000000..80e2a67e17ea --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js @@ -0,0 +1,50 @@ +import querystring from 'querystring'; + +import { Meteor } from 'meteor/meteor'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; + +export function finishOAuthAuthorization(code, state) { + if (RocketChat.settings.get('Cloud_Workspace_Registration_State') !== state) { + throw new Meteor.Error('error-invalid-state', 'Invalid state provided', { method: 'cloud:finishOAuthAuthorization' }); + } + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const clientId = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + const clientSecret = RocketChat.settings.get('Cloud_Workspace_Client_Secret'); + + let result; + try { + result = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id: clientId, + client_secret: clientSecret, + grant_type: 'authorization_code', + code, + redirect_uri: getRedirectUri(), + }), + }); + } catch (e) { + return false; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + result.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Account_Associated', true); + RocketChat.models.Users.update({ _id: Meteor.userId() }, { + $set: { + 'services.cloud': { + accessToken: result.data.access_token, + expiresAt, + scope: result.data.scope, + tokenType: result.data.token_type, + refreshToken: result.data.refresh_token, + }, + }, + }); + + return true; +} diff --git a/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js b/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js new file mode 100644 index 000000000000..b3edc95090f9 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js @@ -0,0 +1,15 @@ +import { Random } from 'meteor/random'; + +import { getRedirectUri } from './getRedirectUri'; + +export function getOAuthAuthorizationUrl() { + const state = Random.id(); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Registration_State', state); + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const client_id = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + const redirectUri = getRedirectUri(); + + return `${ cloudUrl }/authorize?response_type=code&client_id=${ client_id }&redirect_uri=${ redirectUri }&scope=offline_access&state=${ state }`; +} diff --git a/packages/rocketchat-cloud/server/functions/getRedirectUri.js b/packages/rocketchat-cloud/server/functions/getRedirectUri.js new file mode 100644 index 000000000000..21a219791c9e --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getRedirectUri.js @@ -0,0 +1,3 @@ +export function getRedirectUri() { + return `${ RocketChat.settings.get('Site_Url') }/admin/cloud/oauth-callback`.replace(/\/\/admin+/g, '/admin'); +} diff --git a/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js b/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js new file mode 100644 index 000000000000..d853ce5b6df3 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js @@ -0,0 +1,50 @@ +import querystring from 'querystring'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; + +export function getWorkspaceAccessToken() { + if (!RocketChat.settings.get('Register_Server')) { + return ''; + } + + const client_id = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + if (!client_id) { + return ''; + } + + const expires = RocketChat.models.Settings.findOneById('Cloud_Workspace_Access_Token_Expires_At'); + const now = new Date(); + + if (now < expires.value) { + return RocketChat.settings.get('Cloud_Workspace_Access_Token'); + } + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const client_secret = RocketChat.settings.get('Cloud_Workspace_Client_Secret'); + const redirectUri = getRedirectUri(); + + let authTokenResult; + try { + authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id, + client_secret, + grant_type: 'client_credentials', + redirect_uri: redirectUri, + }), + }); + } catch (e) { + return ''; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + + + return authTokenResult.data.access_token; +} diff --git a/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js b/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js new file mode 100644 index 000000000000..d9278a65d519 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js @@ -0,0 +1,34 @@ +import { HTTP } from 'meteor/http'; + +import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; + +export function getWorkspaceLicense() { + const token = getWorkspaceAccessToken(); + + if (!token) { + return { updated: false, license: '' }; + } + + + let licenseResult; + try { + licenseResult = HTTP.get(`${ RocketChat.settings.get('Cloud_Workspace_Registration_Client_Uri') }/license`, { + headers: { + Authorization: `Bearer ${ token }`, + }, + }); + } catch (e) { + return { updated: false, license: '' }; + } + + const remoteLicense = licenseResult.data; + const currentLicense = RocketChat.settings.get('Cloud_Workspace_License'); + + if (remoteLicense.updatedAt <= currentLicense._updatedAt) { + return { updated: false, license: '' }; + } + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_License', remoteLicense.license); + + return { updated: true, license: remoteLicense.license }; +} diff --git a/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js b/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js new file mode 100644 index 000000000000..dd109853234e --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js @@ -0,0 +1,18 @@ +export function retrieveRegistrationStatus() { + const info = { + registeredWithWizard: RocketChat.settings.get('Register_Server'), + workspaceConnected: (RocketChat.settings.get('Cloud_Workspace_Client_Id')) ? true : false, + userAssociated: (RocketChat.settings.get('Cloud_Workspace_Account_Associated')) ? true : false, + token: '', + email: '', + }; + + const firstUser = RocketChat.models.Users.getOldest({ emails: 1 }); + info.email = firstUser && firstUser.emails[0].address; + + if (RocketChat.settings.get('Organization_Email')) { + info.email = RocketChat.settings.get('Organization_Email'); + } + + return info; +} diff --git a/packages/rocketchat-cloud/server/index.js b/packages/rocketchat-cloud/server/index.js new file mode 100644 index 000000000000..c64f14941528 --- /dev/null +++ b/packages/rocketchat-cloud/server/index.js @@ -0,0 +1,11 @@ +import './methods'; +import { getWorkspaceAccessToken } from './functions/getWorkspaceAccessTokens'; + +if (RocketChat.models && RocketChat.models.Permissions) { + RocketChat.models.Permissions.createOrUpdate('manage-cloud', ['admin']); +} + +// Ensure the client/workspace access token is valid +getWorkspaceAccessToken(); + +export { getWorkspaceAccessToken }; diff --git a/packages/rocketchat-cloud/server/methods.js b/packages/rocketchat-cloud/server/methods.js new file mode 100644 index 000000000000..480274bd14ab --- /dev/null +++ b/packages/rocketchat-cloud/server/methods.js @@ -0,0 +1,72 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; + +import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStatus'; +import { connectWorkspace } from './functions/connectWorkspace'; +import { getOAuthAuthorizationUrl } from './functions/getOAuthAuthorizationUrl'; +import { finishOAuthAuthorization } from './functions/finishOAuthAuthorization'; + +Meteor.methods({ + 'cloud:checkRegisterStatus'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:checkRegisterStatus' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:checkRegisterStatus' }); + } + + return retrieveRegistrationStatus(); + }, + 'cloud:updateEmail'(email) { + check(email, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:updateEmail' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:updateEmail' }); + } + + RocketChat.models.Settings.updateValueById('Organization_Email', email); + }, + 'cloud:connectWorkspace'(token) { + check(token, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return connectWorkspace(token); + }, + 'cloud:getOAuthAuthorizationUrl'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return getOAuthAuthorizationUrl(); + }, + 'cloud:finishOAuthAuthorization'(code, state) { + check(code, String); + check(state, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:finishOAuthAuthorization' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return finishOAuthAuthorization(code, state); + }, +}); diff --git a/packages/rocketchat-e2e/client/rocketchat.e2e.js b/packages/rocketchat-e2e/client/rocketchat.e2e.js index 2ef8992bd0ab..5db2b5fc471b 100644 --- a/packages/rocketchat-e2e/client/rocketchat.e2e.js +++ b/packages/rocketchat-e2e/client/rocketchat.e2e.js @@ -347,7 +347,7 @@ class E2E { modal.open({ title: TAPi18n.__('Enter_E2E_password_to_decode_your_key'), type: 'input', - inputType: 'text', + inputType: 'password', html: true, text: `
${ TAPi18n.__('E2E_password_request_text') }
`, showConfirmButton: true, diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 4c12e0c15b40..5b590d9f54de 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -576,6 +576,21 @@ "Closed": "Closed", "Closed_by_visitor": "Closed by visitor", "Closing_chat": "Closing chat", + "Cloud": "Cloud", + "Cloud_connect": "Rocket.Chat Cloud Connect", + "Cloud_what_is_it": "What is this?", + "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect allows you to connect your self-hosted Rocket.Chat Workspace to our Cloud. Doing so enables you to manage your licenses, Billing and Support in Rocket.Chat Cloud.", + "Cloud_workspace_connected_plus_account": "Your workspace is now connected to the Rocket.Chat Cloud and an account is associated.", + "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.", + "Cloud_login_to_cloud": "Login to Rocket.Chat Cloud", + "Cloud_address_to_send_registration_to": "The address to send your Cloud registration email to.", + "Cloud_update_email": "Update Email", + "Cloud_manually_input_token": "Manually enter the token received from the Cloud Registration Email.", + "Cloud_registration_required": "Registration Required", + "Cloud_registration_required_description": "Looks like during setup you didn't chose to register your workspace.", + "Cloud_registration_requried_link_text": "Click here to register your workspace.", + "Cloud_error_in_authenticating": "Error received while authenticating", + "Cloud_error_code": "Code: ", "Collaborative": "Collaborative", "Collapse_Embedded_Media_By_Default": "Collapse Embedded Media by Default", "color": "Color", @@ -590,6 +605,7 @@ "Computer": "Computer", "Confirm_new_encryption_password": "Confirm new encryption password", "Confirm_password": "Confirm your password", + "Connect": "Connect", "Connection_Closed": "Connection closed", "Connection_Reset": "Connection reset", "Consulting": "Consulting", @@ -2691,6 +2707,7 @@ "to_see_more_details_on_how_to_integrate": "to see more details on how to integrate.", "To_users": "To Users", "Toggle_original_translated": "Toggle original/translated", + "Token": "Token", "Token_Access": "Token Access", "Token_Controlled_Access": "Token Controlled Access", "Token_required": "Token required", @@ -3029,4 +3046,4 @@ "Your_push_was_sent_to_s_devices": "Your push was sent to %s devices", "Your_server_link": "Your server link", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index d229e59d902f..cbd5555bb4e7 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -1153,7 +1153,6 @@ "Facebook_Page": "Página do Facebook", "False": "Não", "Favorite_Rooms": "Ativar Salas Favoritas", - "Favorite": "F", "Favorites": "Favoritos", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esse recurso depende de \"Enviar histórico de navegação do visitante como uma mensagem\" para ser ativado.", "Features_Enabled": "Funcionalidades habilitadas", @@ -2880,4 +2879,4 @@ "Your_push_was_sent_to_s_devices": "Sua notificação foi enviada para %s dispositivos", "Your_server_link": "O link do seu servidor", "Your_workspace_is_ready": "O seu espaço de trabalho está pronto a usar 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-lib/client/models/Avatars.js b/packages/rocketchat-lib/client/models/Avatars.js deleted file mode 100644 index 455ca64a97d1..000000000000 --- a/packages/rocketchat-lib/client/models/Avatars.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Avatars } from 'meteor/rocketchat:models'; - -RocketChat.models.Avatars = Avatars; diff --git a/packages/rocketchat-lib/client/models/Uploads.js b/packages/rocketchat-lib/client/models/Uploads.js deleted file mode 100644 index 7dc92cf6f67b..000000000000 --- a/packages/rocketchat-lib/client/models/Uploads.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Uploads } from 'meteor/rocketchat:models'; - -RocketChat.models.Uploads = Uploads; diff --git a/packages/rocketchat-lib/client/models/UserDataFiles.js b/packages/rocketchat-lib/client/models/UserDataFiles.js deleted file mode 100644 index b3eff0ac6125..000000000000 --- a/packages/rocketchat-lib/client/models/UserDataFiles.js +++ /dev/null @@ -1,3 +0,0 @@ -import { UserDataFiles } from 'meteor/rocketchat:models'; - -RocketChat.models.UserDataFiles = UserDataFiles; diff --git a/packages/rocketchat-lib/client/models/_Base.js b/packages/rocketchat-lib/client/models/_Base.js deleted file mode 100644 index bfda6cf65cce..000000000000 --- a/packages/rocketchat-lib/client/models/_Base.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Base } from 'meteor/rocketchat:models'; - -RocketChat.models._Base = Base; diff --git a/packages/rocketchat-lib/client/models/index.js b/packages/rocketchat-lib/client/models/index.js new file mode 100644 index 000000000000..d9f46c728520 --- /dev/null +++ b/packages/rocketchat-lib/client/models/index.js @@ -0,0 +1,11 @@ +import { Avatars } from 'meteor/rocketchat:models'; +import { Base as _Base } from 'meteor/rocketchat:models'; +import { Uploads } from 'meteor/rocketchat:models'; +import { UserDataFiles } from 'meteor/rocketchat:models'; + +Object.assign(RocketChat.models, { + _Base, + Avatars, + Uploads, + UserDataFiles, +}); diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 6c0adfafa5ac..d005d99aa64a 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -90,7 +90,7 @@ Package.onUse(function(api) { api.addFiles('lib/getUserPreference.js'); api.addFiles('server/lib/bugsnag.js', 'server'); - api.addFiles('server/lib/metrics.js', 'server'); + api.addFiles('server/lib/metrics_import.js', 'server'); api.addFiles('server/lib/RateLimiter.js', 'server'); @@ -140,17 +140,7 @@ Package.onUse(function(api) { api.addFiles('server/lib/migrations.js', 'server'); // SERVER MODELS - api.addFiles('server/models/_Base.js', 'server'); - api.addFiles('server/models/Avatars.js', 'server'); - api.addFiles('server/models/Messages.js', 'server'); - api.addFiles('server/models/Reports.js', 'server'); - api.addFiles('server/models/Rooms.js', 'server'); - api.addFiles('server/models/Settings.js', 'server'); - api.addFiles('server/models/Subscriptions.js', 'server'); - api.addFiles('server/models/Uploads.js', 'server'); - api.addFiles('server/models/Users.js', 'server'); - api.addFiles('server/models/ExportOperations.js', 'server'); - api.addFiles('server/models/UserDataFiles.js', 'server'); + api.addFiles('server/models/index.js', 'server'); api.addFiles('server/oauth/oauth.js', 'server'); api.addFiles('server/oauth/facebook.js', 'server'); @@ -158,7 +148,7 @@ Package.onUse(function(api) { api.addFiles('server/oauth/google.js', 'server'); api.addFiles('server/oauth/proxy.js', 'server'); - api.addFiles('server/startup/statsTracker.js', 'server'); + api.addFiles('server/startup/statsTracker_import.js', 'server'); api.addFiles('server/startup/robots.js', 'server'); // SERVER PUBLICATIONS @@ -249,9 +239,7 @@ Package.onUse(function(api) { api.addFiles('client/CustomTranslations.js', 'client'); // CLIENT MODELS - api.addFiles('client/models/_Base.js', 'client'); - api.addFiles('client/models/Avatars.js', 'client'); - api.addFiles('client/models/Uploads.js', 'client'); + api.addFiles('client/models/index.js', 'client'); // CLIENT VIEWS api.addFiles('client/views/customFieldsForm.html', 'client'); diff --git a/packages/rocketchat-lib/server/lib/metrics.js b/packages/rocketchat-lib/server/lib/metrics_import.js similarity index 100% rename from packages/rocketchat-lib/server/lib/metrics.js rename to packages/rocketchat-lib/server/lib/metrics_import.js diff --git a/packages/rocketchat-lib/server/models/Avatars.js b/packages/rocketchat-lib/server/models/Avatars.js deleted file mode 100644 index 455ca64a97d1..000000000000 --- a/packages/rocketchat-lib/server/models/Avatars.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Avatars } from 'meteor/rocketchat:models'; - -RocketChat.models.Avatars = Avatars; diff --git a/packages/rocketchat-lib/server/models/ExportOperations.js b/packages/rocketchat-lib/server/models/ExportOperations.js deleted file mode 100644 index 5f2375cc281c..000000000000 --- a/packages/rocketchat-lib/server/models/ExportOperations.js +++ /dev/null @@ -1,3 +0,0 @@ -import { ExportOperations } from 'meteor/rocketchat:models'; - -RocketChat.models.ExportOperations = ExportOperations; diff --git a/packages/rocketchat-lib/server/models/Messages.js b/packages/rocketchat-lib/server/models/Messages.js deleted file mode 100644 index 8a9de256ce0b..000000000000 --- a/packages/rocketchat-lib/server/models/Messages.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Messages } from 'meteor/rocketchat:models'; - -RocketChat.models.Messages = Messages; diff --git a/packages/rocketchat-lib/server/models/Reports.js b/packages/rocketchat-lib/server/models/Reports.js deleted file mode 100644 index ec1aac6ee255..000000000000 --- a/packages/rocketchat-lib/server/models/Reports.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Reports } from 'meteor/rocketchat:models'; - -RocketChat.models.Reports = Reports; diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js deleted file mode 100644 index d664591ad794..000000000000 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Rooms } from 'meteor/rocketchat:models'; - -RocketChat.models.Rooms = Rooms; - diff --git a/packages/rocketchat-lib/server/models/Settings.js b/packages/rocketchat-lib/server/models/Settings.js deleted file mode 100644 index 6fc6d0da7df2..000000000000 --- a/packages/rocketchat-lib/server/models/Settings.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Settings } from 'meteor/rocketchat:models'; - -RocketChat.models.Settings = Settings; diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js deleted file mode 100644 index 36931f09b5ca..000000000000 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Subscriptions } from 'meteor/rocketchat:models'; - -RocketChat.models.Subscriptions = Subscriptions; - diff --git a/packages/rocketchat-lib/server/models/Uploads.js b/packages/rocketchat-lib/server/models/Uploads.js deleted file mode 100644 index 7dc92cf6f67b..000000000000 --- a/packages/rocketchat-lib/server/models/Uploads.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Uploads } from 'meteor/rocketchat:models'; - -RocketChat.models.Uploads = Uploads; diff --git a/packages/rocketchat-lib/server/models/UserDataFiles.js b/packages/rocketchat-lib/server/models/UserDataFiles.js deleted file mode 100644 index b3eff0ac6125..000000000000 --- a/packages/rocketchat-lib/server/models/UserDataFiles.js +++ /dev/null @@ -1,3 +0,0 @@ -import { UserDataFiles } from 'meteor/rocketchat:models'; - -RocketChat.models.UserDataFiles = UserDataFiles; diff --git a/packages/rocketchat-lib/server/models/Users.js b/packages/rocketchat-lib/server/models/Users.js deleted file mode 100644 index 8918c19aa575..000000000000 --- a/packages/rocketchat-lib/server/models/Users.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Users } from 'meteor/rocketchat:models'; - -RocketChat.models.Users = Users; - diff --git a/packages/rocketchat-lib/server/models/_Base.js b/packages/rocketchat-lib/server/models/_Base.js deleted file mode 100644 index bfda6cf65cce..000000000000 --- a/packages/rocketchat-lib/server/models/_Base.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Base } from 'meteor/rocketchat:models'; - -RocketChat.models._Base = Base; diff --git a/packages/rocketchat-lib/server/models/index.js b/packages/rocketchat-lib/server/models/index.js new file mode 100644 index 000000000000..567fcf47b642 --- /dev/null +++ b/packages/rocketchat-lib/server/models/index.js @@ -0,0 +1,25 @@ +import { Avatars } from 'meteor/rocketchat:models'; +import { Base as _Base } from 'meteor/rocketchat:models'; +import { ExportOperations } from 'meteor/rocketchat:models'; +import { Messages } from 'meteor/rocketchat:models'; +import { Reports } from 'meteor/rocketchat:models'; +import { Rooms } from 'meteor/rocketchat:models'; +import { Settings } from 'meteor/rocketchat:models'; +import { Subscriptions } from 'meteor/rocketchat:models'; +import { Uploads } from 'meteor/rocketchat:models'; +import { UserDataFiles } from 'meteor/rocketchat:models'; +import { Users } from 'meteor/rocketchat:models'; + +Object.assign(RocketChat.models, { + _Base, + Avatars, + ExportOperations, + Messages, + Reports, + Rooms, + Settings, + Subscriptions, + Uploads, + UserDataFiles, + Users, +}); diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 7cb5f032b9dc..464314fbce11 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -2529,6 +2529,134 @@ RocketChat.settings.addGroup('Setup_Wizard', function() { this.add('Allow_Marketing_Emails', true, { type: 'boolean', }); + this.add('Register_Server', true, { + type: 'boolean', + }); + this.add('Organization_Email', '', { + type: 'string', + }); + }); + + this.section('Cloud_Info', function() { + this.add('Cloud_Url', 'https://cloud.rocket.chat', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Id', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Name', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Id', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Secret', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Secret_Expires_At', '', { + type: 'int', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Registration_Client_Uri', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_License', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Access_Token', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(), { + type: 'date', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Registration_State', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Account_Associated', false, { + type: 'boolean', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); }); }); diff --git a/packages/rocketchat-lib/server/startup/statsTracker.js b/packages/rocketchat-lib/server/startup/statsTracker_import.js similarity index 100% rename from packages/rocketchat-lib/server/startup/statsTracker.js rename to packages/rocketchat-lib/server/startup/statsTracker_import.js diff --git a/packages/rocketchat-setup-wizard/client/setupWizard.js b/packages/rocketchat-setup-wizard/client/setupWizard.js index 817868adda84..d79dd8288f50 100644 --- a/packages/rocketchat-setup-wizard/client/setupWizard.js +++ b/packages/rocketchat-setup-wizard/client/setupWizard.js @@ -74,6 +74,14 @@ const persistSettings = (state, callback) => { _id: 'Statistics_reporting', value: state.registerServer, }, + { + _id: 'Apps_Framework_enabled', + value: state.registerServer, + }, + { + _id: 'Register_Server', + value: state.registerServer, + }, { _id: 'Allow_Marketing_Emails', value: state.optIn, diff --git a/packages/rocketchat-statistics/server/functions/get.js b/packages/rocketchat-statistics/server/functions/get.js index cc6a70c2f146..0ecfa629d74f 100644 --- a/packages/rocketchat-statistics/server/functions/get.js +++ b/packages/rocketchat-statistics/server/functions/get.js @@ -32,10 +32,12 @@ RocketChat.statistics.get = function _getStatistics() { } }); - if (statistics.wizard.allowMarketingEmails) { - const firstUser = RocketChat.models.Users.getOldest({ name: 1, emails: 1 }); - statistics.wizard.contactName = firstUser && firstUser.name; - statistics.wizard.contactEmail = firstUser && firstUser.emails[0].address; + const firstUser = RocketChat.models.Users.getOldest({ name: 1, emails: 1 }); + statistics.wizard.contactName = firstUser && firstUser.name; + statistics.wizard.contactEmail = firstUser && firstUser.emails && firstUser.emails[0].address; + + if (RocketChat.settings.get('Organization_Email')) { + statistics.wizard.contactEmail = RocketChat.settings.get('Organization_Email'); } // Version diff --git a/packages/rocketchat-statistics/server/index.js b/packages/rocketchat-statistics/server/index.js index 91b887ae52af..b6c264b84f2b 100644 --- a/packages/rocketchat-statistics/server/index.js +++ b/packages/rocketchat-statistics/server/index.js @@ -1,5 +1,5 @@ import '../lib/rocketchat'; -import './models/Statistics'; +import './models/Statistics_import'; import './functions/get'; import './functions/save'; import './methods/getStatistics'; diff --git a/packages/rocketchat-statistics/server/models/Statistics.js b/packages/rocketchat-statistics/server/models/Statistics_import.js similarity index 100% rename from packages/rocketchat-statistics/server/models/Statistics.js rename to packages/rocketchat-statistics/server/models/Statistics_import.js diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html index e5a8d827b723..578126cdc76a 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html +++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html @@ -27,8 +27,8 @@

{{_ "User_Info"}}

{{#if username}}{{/if}} -
-
{{userStatus}}
+
+
{{_ userStatus}}
diff --git a/packages/rocketchat-ui/client/components/header/header.html b/packages/rocketchat-ui/client/components/header/header.html index 354d0d810d0b..39da3aba4b00 100644 --- a/packages/rocketchat-ui/client/components/header/header.html +++ b/packages/rocketchat-ui/client/components/header/header.html @@ -36,8 +36,8 @@ {{#if isDirect}} -
-
{{userStatus}}
+
+
{{_ userStatus}}
{{else}} {{#if roomTopic}}{{{RocketChatMarkdown roomTopic}}}{{/if}} diff --git a/packages/rocketchat-version-check/package.js b/packages/rocketchat-version-check/package.js index a88d0e9e712f..2f76c06b8c3e 100644 --- a/packages/rocketchat-version-check/package.js +++ b/packages/rocketchat-version-check/package.js @@ -11,6 +11,7 @@ Package.onUse(function(api) { 'ecmascript', 'rocketchat:lib', 'rocketchat:logger', + 'rocketchat:cloud', 'littledata:synced-cron', ]); diff --git a/packages/rocketchat-version-check/server/functions/getNewUpdates.js b/packages/rocketchat-version-check/server/functions/getNewUpdates.js index 8426537cbd55..09ff8715e68a 100644 --- a/packages/rocketchat-version-check/server/functions/getNewUpdates.js +++ b/packages/rocketchat-version-check/server/functions/getNewUpdates.js @@ -1,6 +1,7 @@ import os from 'os'; import { HTTP } from 'meteor/http'; import { RocketChat } from 'meteor/rocketchat:lib'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { MongoInternals } from 'meteor/mongo'; // import checkUpdate from '../checkUpdate'; @@ -24,8 +25,15 @@ export default () => { deployPlatform: process.env.DEPLOY_PLATFORM || 'selfinstall', }; + const headers = {}; + const token = getWorkspaceAccessToken(); + if (token) { + headers.Authorization = `Bearer ${ token }`; + } + const result = HTTP.get('https://releases.rocket.chat/updates/check', { params: data, + headers, }); return result.data; diff --git a/server/lib/cordova.js b/server/lib/cordova.js index 2eb5a33a0c0a..8dc30c65864e 100644 --- a/server/lib/cordova.js +++ b/server/lib/cordova.js @@ -2,8 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import { TAPi18n } from 'meteor/tap:i18n'; import { SystemLogger } from 'meteor/rocketchat:logger'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { Push } from 'meteor/rocketchat:push'; + Meteor.methods({ // log() { // return console.log(...arguments); @@ -80,8 +82,14 @@ function sendPush(service, token, options, tries = 0) { token, options, }, + headers: {}, }; + const workspaceAccesstoken = getWorkspaceAccessToken(); + if (token) { + data.headers.Authorization = `Bearer ${ workspaceAccesstoken }`; + } + return HTTP.post(`${ RocketChat.settings.get('Push_gateway') }/push/${ service }/send`, data, function(error, response) { if (response && response.statusCode === 406) { console.log('removing push token', token); diff --git a/server/startup/cron.js b/server/startup/cron.js index a4b5130d6455..a5c93b6e1a5a 100644 --- a/server/startup/cron.js +++ b/server/startup/cron.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import { Logger } from 'meteor/rocketchat:logger'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { SyncedCron } from 'meteor/littledata:synced-cron'; const logger = new Logger('SyncedCron'); @@ -19,8 +20,16 @@ function generateStatistics() { if (RocketChat.settings.get('Statistics_reporting')) { try { + const headers = {}; + const token = getWorkspaceAccessToken(); + + if (token) { + headers.Authorization = `Bearer ${ token }`; + } + HTTP.post('https://collector.rocket.chat/', { data: statistics, + headers, }); } catch (error) { /* error*/ diff --git a/server/startup/migrations/v099.js b/server/startup/migrations/v099.js index 001739053fa1..cddb80db5fb0 100644 --- a/server/startup/migrations/v099.js +++ b/server/startup/migrations/v099.js @@ -201,11 +201,15 @@ RocketChat.Migrations.add({ }).then(() => { const avatarsFiles = new Mongo.Collection('avatars.files'); const avatarsChunks = new Mongo.Collection('avatars.chunks'); - avatarsFiles.rawCollection().drop(); - avatarsChunks.rawCollection().drop(); + try { + avatarsFiles.rawCollection().drop(); + avatarsChunks.rawCollection().drop(); + } catch (error) { + console.warn('Migration Error: avatars.files and avatars.chunks collections may not exist!'); + } RocketChat.models.Settings.remove({ _id: 'Accounts_AvatarStoreType' }); RocketChat.models.Settings.remove({ _id: 'Accounts_AvatarStorePath' }); - }); + }).catch((error) => console.error(error)); }, 1000); }); }, diff --git a/server/startup/migrations/v137.js b/server/startup/migrations/v137.js new file mode 100644 index 000000000000..7ebf4c7b7241 --- /dev/null +++ b/server/startup/migrations/v137.js @@ -0,0 +1,10 @@ +RocketChat.Migrations.add({ + version: 137, + up() { + const firstUser = RocketChat.models.Users.getOldest({ emails: 1 }); + const reportStats = RocketChat.settings.get('Statistics_reporting'); + + RocketChat.models.Settings.updateValueById('Organization_Email', firstUser && firstUser.emails && firstUser.emails[0].address); + RocketChat.models.Settings.updateValueById('Register_Server', reportStats); + }, +}); diff --git a/tests/end-to-end/ui/00-login.js b/tests/end-to-end/ui/00-login.js index 3511f01e9d5d..294b8c13d72a 100644 --- a/tests/end-to-end/ui/00-login.js +++ b/tests/end-to-end/ui/00-login.js @@ -141,7 +141,7 @@ describe('[Setup Wizard]', () => { describe('[Render - Final Step]', () => { it('it should render "Go to your workspace button', () => { - setupWizard.goToWorkspace.waitForVisible(15000); + setupWizard.goToWorkspace.waitForVisible(20000); setupWizard.goToWorkspace.isVisible().should.be.true; });