From 9f5fa8d483fcc6fcbbed8dd476835eb97913b93a Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Fri, 14 Apr 2017 00:30:54 +0100 Subject: [PATCH 001/102] Show full name in mentions --- packages/rocketchat-mentions/client.js | 6 ++++-- packages/rocketchat-mentions/server.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/rocketchat-mentions/client.js b/packages/rocketchat-mentions/client.js index 041eb0bfb123..81448e3c103f 100644 --- a/packages/rocketchat-mentions/client.js +++ b/packages/rocketchat-mentions/client.js @@ -17,10 +17,12 @@ function MentionsClient(message) { if (['all', 'here'].includes(username)) { return match.replace(mention, `${ mention }`); } - if (message.temp == null && _.findWhere(message.mentions, {username}) == null) { + const mentionObj = _.findWhere(message.mentions, {username}); + if (message.temp == null && mentionObj == null) { return match; } - return match.replace(mention, `${ mention }`); + const name = RocketChat.settings.get('UI_Use_Real_Name') && mentionObj && mentionObj.name; + return match.replace(mention, `${ name || mention }`); }); const msgChannelRegex = new RegExp(`(?:^|\\s|\\n)(#(${ RocketChat.settings.get('UTF8_Names_Validation') }))[:.,\s]?`, 'g'); diff --git a/packages/rocketchat-mentions/server.js b/packages/rocketchat-mentions/server.js index 66aa59c08661..06877fe6cbd8 100644 --- a/packages/rocketchat-mentions/server.js +++ b/packages/rocketchat-mentions/server.js @@ -26,7 +26,7 @@ function MentionsServer(message) { username: mention }); }); - mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true }}).fetch() : []; + mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true, name: true }}).fetch() : []; const verifiedMentions = [...mentionsAll, ...mentions]; if (verifiedMentions.length !== 0) { From 9753c716a59f998e4639d5bdab65e89d9c4d258a Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Mon, 17 Apr 2017 22:58:29 +0100 Subject: [PATCH 002/102] Use cache for mention real names --- .../rocketchat-lib/server/methods/getChannelHistory.js | 6 ++++++ packages/rocketchat-mentions/server.js | 2 +- server/methods/loadHistory.js | 6 ++++++ server/stream/messages.js | 7 +++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/methods/getChannelHistory.js b/packages/rocketchat-lib/server/methods/getChannelHistory.js index e2f40ebe748a..0f3194991f56 100644 --- a/packages/rocketchat-lib/server/methods/getChannelHistory.js +++ b/packages/rocketchat-lib/server/methods/getChannelHistory.js @@ -57,6 +57,12 @@ Meteor.methods({ const user = RocketChat.models.Users.findOneById(message.u._id); message.u.name = user && user.name; } + if (message.mentions && message.mentions.length && UI_Use_Real_Name) { + message.mentions.forEach((mention) => { + const user = RocketChat.models.Users.findOneById(mention._id); + mention.name = user && user.name; + }); + } return message; }); diff --git a/packages/rocketchat-mentions/server.js b/packages/rocketchat-mentions/server.js index 06877fe6cbd8..92b38dd1d6a0 100644 --- a/packages/rocketchat-mentions/server.js +++ b/packages/rocketchat-mentions/server.js @@ -26,7 +26,7 @@ function MentionsServer(message) { username: mention }); }); - mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true, name: true }}).fetch() : []; + mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true}}).fetch() : []; const verifiedMentions = [...mentionsAll, ...mentions]; if (verifiedMentions.length !== 0) { diff --git a/server/methods/loadHistory.js b/server/methods/loadHistory.js index 8404d9e81b8e..963eb70581ea 100644 --- a/server/methods/loadHistory.js +++ b/server/methods/loadHistory.js @@ -68,6 +68,12 @@ Meteor.methods({ const user = RocketChat.models.Users.findOneById(message.u._id); message.u.name = user && user.name; } + if (message.mentions && message.mentions.length && UI_Use_Real_Name) { + message.mentions.forEach((mention) => { + const user = RocketChat.models.Users.findOneById(mention._id); + mention.name = user && user.name; + }); + } return message; }); diff --git a/server/stream/messages.js b/server/stream/messages.js index cffeb442a7cb..96afa00f6bc7 100644 --- a/server/stream/messages.js +++ b/server/stream/messages.js @@ -51,6 +51,13 @@ Meteor.startup(function() { const user = RocketChat.models.Users.findOneById(record.u._id); record.u.name = user && user.name; } + + if (record.mentions && record.mentions.length && UI_Use_Real_Name) { + record.mentions.forEach((mention) => { + const user = RocketChat.models.Users.findOneById(mention._id); + mention.name = user && user.name; + }); + } msgStream.emitWithoutBroadcast('__my_messages__', record, {}); return msgStream.emitWithoutBroadcast(record.rid, record); } From d7a2e5cfc1d523da43de7f400ce97e14a0ffbd05 Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Mon, 17 Apr 2017 23:03:08 +0100 Subject: [PATCH 003/102] Revert rocketchat-mentions/server.js --- packages/rocketchat-mentions/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-mentions/server.js b/packages/rocketchat-mentions/server.js index 92b38dd1d6a0..66aa59c08661 100644 --- a/packages/rocketchat-mentions/server.js +++ b/packages/rocketchat-mentions/server.js @@ -26,7 +26,7 @@ function MentionsServer(message) { username: mention }); }); - mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true}}).fetch() : []; + mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true }}).fetch() : []; const verifiedMentions = [...mentionsAll, ...mentions]; if (verifiedMentions.length !== 0) { From 652e7ac2018e984ab64f4025065bf2f82a9045ce Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Tue, 18 Apr 2017 13:54:48 +0100 Subject: [PATCH 004/102] Update mention name when user changes name --- client/notifications/UsersNameChanged.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/notifications/UsersNameChanged.js b/client/notifications/UsersNameChanged.js index 77d153ba708d..68f23ea3e133 100644 --- a/client/notifications/UsersNameChanged.js +++ b/client/notifications/UsersNameChanged.js @@ -10,6 +10,18 @@ Meteor.startup(function() { multi: true }); + RocketChat.models.Messages.update({ + mentions: { + $elemMatch: { _id } + } + }, { + $set: { + 'mentions.$.name': name + } + }, { + multi: true + }); + RocketChat.models.Subscriptions.update({ name: username, t: 'd' From 8930ea911d6546d19bc5987b91cfb0048c2276e0 Mon Sep 17 00:00:00 2001 From: Alex Brazier Date: Tue, 18 Apr 2017 15:35:17 +0100 Subject: [PATCH 005/102] Replace @username with real name in notifications --- .../server/lib/sendNotificationsOnMessage.js | 144 +++++++++++------- 1 file changed, 86 insertions(+), 58 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 85ab61fb1119..eb1aa21a28fa 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -1,6 +1,81 @@ /* globals Push */ import moment from 'moment'; +/** + * Replaces @username with full name + * + * @param {string} message The message to replace + * @param {object[]} mentions Array of mentions used to make replacements + * + * @returns {string} + */ +function replaceMentionedUsernamesWithFullNames(message, mentions) { + if (!mentions || !mentions.length) { + return message; + } + mentions.forEach((mention) => { + const user = RocketChat.models.Users.findOneById(mention._id); + if (user && user.name) { + message = message.replace(`@${ mention.username }`, user.name); + } + }); + return message; +} + +/** + * Send notification to user + * + * @param {string} userId The user to notify + * @param {object} user The sender + * @param {object} room The room send from + * @param {number} duration Duration of notification + */ +function notifyUser(userId, user, message, room, duration) { + const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true; + if (UI_Use_Real_Name) { + message.msg = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); + } + let title = UI_Use_Real_Name ? user.name : `@${ user.username }`; + if (room.t !== 'd' && room.name) { + title += ` @ #${ room.name }`; + } + RocketChat.Notifications.notifyUser(userId, 'notification', { + title, + text: message.msg, + duration, + payload: { + _id: message._id, + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + } + }); +} + +/** + * Checks if a message contains a user highlight + * + * @param {string} message + * @param {array|undefined} highlights + * + * @returns {boolean} + */ +function messageContainsHighlight(message, highlights) { + if (! highlights || highlights.length === 0) { return false; } + + let has = false; + highlights.some(function(highlight) { + const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); + if (regexp.test(message.msg)) { + has = true; + return true; + } + }); + + return has; +} + RocketChat.callbacks.add('afterSaveMessage', function(message, room) { // skips this callback if the message was edited if (message.editedAt) { @@ -16,7 +91,13 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { /* Increment unread couter if direct messages */ - const settings = {}; + const settings = { + alwaysNotifyDesktopUsers: [], + dontNotifyDesktopUsers: [], + alwaysNotifyMobileUsers: [], + dontNotifyMobileUsers: [], + desktopNotificationDurations: {} + }; /** * Checks if a given user can be notified @@ -35,35 +116,6 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return (settings[types[type][0]].indexOf(id) === -1 || settings[types[type][1]].indexOf(id) !== -1); } - /** - * Checks if a message contains a user highlight - * - * @param {string} message - * @param {array|undefined} highlights - * - * @returns {boolean} - */ - function messageContainsHighlight(message, highlights) { - if (! highlights || highlights.length === 0) { return false; } - - let has = false; - highlights.some(function(highlight) { - const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); - if (regexp.test(message.msg)) { - has = true; - return true; - } - }); - - return has; - } - - settings.alwaysNotifyDesktopUsers = []; - settings.dontNotifyDesktopUsers = []; - settings.alwaysNotifyMobileUsers = []; - settings.dontNotifyMobileUsers = []; - settings.desktopNotificationDurations = {}; - const notificationPreferencesByRoom = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id); notificationPreferencesByRoom.forEach(function(subscription) { if (subscription.desktopNotifications === 'all') { @@ -127,18 +179,8 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { } if ((userOfMention != null) && canBeNotified(userOfMentionId, 'mobile')) { - RocketChat.Notifications.notifyUser(userOfMention._id, 'notification', { - title: RocketChat.settings.get('UI_Use_Real_Name') ? user.name : `@${ user.username }`, - text: message.msg, - duration: settings.desktopNotificationDurations[userOfMention._id], - payload: { - _id: message._id, - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - } - }); + const duration = settings.desktopNotificationDurations[userOfMention._id]; + notifyUser(userOfMention._id, user, message, room, duration); } if ((userOfMention != null) && canBeNotified(userOfMentionId, 'desktop')) { @@ -273,22 +315,8 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { if (userIdsToNotify.length > 0) { for (const usersOfMentionId of userIdsToNotify) { - let title = `@${ user.username }`; - if (room.name) { - title += ` @ #${ room.name }`; - } - RocketChat.Notifications.notifyUser(usersOfMentionId, 'notification', { - title, - text: message.msg, - duration: settings.desktopNotificationDurations[usersOfMentionId], - payload: { - _id: message._id, - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - } - }); + const duration = settings.desktopNotificationDurations[usersOfMentionId]; + notifyUser(usersOfMentionId, user, message, room, duration); } } From 0a4c04d14eb56487c85504e6be5b547d24b5044d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 20 Apr 2017 17:25:37 -0300 Subject: [PATCH 006/102] Add S3 storage type to avatars --- .../client/lib/FileUploadAmazonS3.js | 33 +-- .../client/lib/fileUploadHandler.js | 4 +- .../globalFileRestrictions.js | 1 + .../server/config/configFileUploadAmazonS3.js | 148 ++++++----- .../server/lib/FileUpload.js | 2 +- .../server/models/Uploads.coffee | 53 +++- .../client/avatar/prompt.coffee | 34 ++- .../rocketchat-ui/client/lib/fileUpload.js | 232 ++++++++++++++++++ packages/rocketchat-ui/package.js | 4 +- server/methods/setAvatarFromService.js | 7 + server/startup/avatar.js | 147 ++++++++--- 11 files changed, 531 insertions(+), 134 deletions(-) create mode 100644 packages/rocketchat-ui/client/lib/fileUpload.js diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index 277f6471a9aa..df9fa58e3f56 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -1,36 +1,19 @@ /* globals FileUpload, FileUploadBase, Slingshot */ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { - constructor(meta, file) { + constructor(directive, meta, file) { super(meta, file); - this.uploader = new Slingshot.Upload('rocketchat-uploads', { rid: meta.rid }); + this.uploader = new Slingshot.Upload(directive, meta); } - start() { + start(callback) { this.uploader.send(this.file, (error, downloadUrl) => { if (this.computation) { this.computation.stop(); } if (error) { - let uploading = Session.get('uploading'); - if (!Array.isArray(uploading)) { - uploading = []; - } - - const item = _.findWhere(uploading, { id: this.id }); - - if (_.isObject(item)) { - item.error = error.error; - item.percentage = 0; - } else { - uploading.push({ - error: error.error, - percentage: 0 - }); - } - - Session.set('uploading', uploading); + return callback.call(this, error); } else { const file = _.pick(this.meta, 'type', 'size', 'name', 'identify', 'description'); file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); @@ -38,13 +21,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { Meteor.call('sendFileMessage', this.meta.rid, 's3', file, () => { Meteor.setTimeout(() => { - const uploading = Session.get('uploading'); - if (uploading !== null) { - const item = _.findWhere(uploading, { - id: this.id - }); - return Session.set('uploading', _.without(uploading, item)); - } + callback.call(this, null, file); }, 2000); }); } diff --git a/packages/rocketchat-file-upload/client/lib/fileUploadHandler.js b/packages/rocketchat-file-upload/client/lib/fileUploadHandler.js index 71f5fbdf1519..f62e4bb5b93b 100644 --- a/packages/rocketchat-file-upload/client/lib/fileUploadHandler.js +++ b/packages/rocketchat-file-upload/client/lib/fileUploadHandler.js @@ -1,10 +1,10 @@ /* globals FileUpload, fileUploadHandler:true */ /* exported fileUploadHandler */ -fileUploadHandler = (meta, file) => { +fileUploadHandler = (directive, meta, file) => { const storageType = RocketChat.settings.get('FileUpload_Storage_Type'); if (FileUpload[storageType] !== undefined) { - return new FileUpload[storageType](meta, file); + return new FileUpload[storageType](directive, meta, file); } }; diff --git a/packages/rocketchat-file-upload/globalFileRestrictions.js b/packages/rocketchat-file-upload/globalFileRestrictions.js index 0568c018d8f1..3c52390c4341 100644 --- a/packages/rocketchat-file-upload/globalFileRestrictions.js +++ b/packages/rocketchat-file-upload/globalFileRestrictions.js @@ -25,5 +25,6 @@ const slingShotConfig = { allowedFileTypes: null }; +Slingshot.fileRestrictions('rocketchat-avatars', slingShotConfig); Slingshot.fileRestrictions('rocketchat-uploads', slingShotConfig); Slingshot.fileRestrictions('rocketchat-uploads-gs', slingShotConfig); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index daac0ea19932..284edb19921a 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -1,4 +1,4 @@ -/* globals Slingshot, FileUpload, AWS, SystemLogger */ +/* globals Slingshot, FileUpload, AWS */ const crypto = Npm.require('crypto'); let S3accessKey; @@ -36,8 +36,81 @@ FileUpload.addHandler('s3', { } }); -const createS3Directive = _.debounce(() => { - const directiveName = 'rocketchat-uploads'; +function createDirective(directiveName, { key, bucket, accessKey, secretKey, region, acl, cdn, bucketUrl}) { + if (Slingshot._directives[directiveName]) { + delete Slingshot._directives[directiveName]; + } + const config = { + bucket, + key, + AWSAccessKeyId: accessKey, + AWSSecretAccessKey: secretKey + }; + + if (!_.isEmpty(acl)) { + config.acl = acl; + } + + if (!_.isEmpty(cdn)) { + config.cdn = cdn; + } + + if (!_.isEmpty(region)) { + config.region = region; + } + + if (!_.isEmpty(bucketUrl)) { + config.bucketUrl = bucketUrl; + } + + try { + Slingshot.createDirective(directiveName, Slingshot.S3Storage, config); + } catch (e) { + console.error('Error configuring S3 ->', e.message); + } +} + +const configureSlingshot = _.debounce(() => { + const directives = [ + { + name: 'rocketchat-uploads', + key(file, metaContext) { + const path = `${ RocketChat.hostname }/${ metaContext.rid }/${ this.userId }/`; + + const upload = { + rid: metaContext.rid, + s3: { + bucket: RocketChat.settings.get('FileUpload_S3_Bucket'), + region: RocketChat.settings.get('FileUpload_S3_Region'), + path + } + }; + const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 's3', file, upload); + + return path + fileId; + } + }, + { + name: 'rocketchat-avatars', + key(file/*, metaContext*/) { + const path = `${ RocketChat.hostname }/avatars/`; + + const user = RocketChat.models.Users.findOneById(this.userId); + + const upload = { + username: user && user.username, + s3: { + bucket: RocketChat.settings.get('FileUpload_S3_Bucket'), + region: RocketChat.settings.get('FileUpload_S3_Region'), + path + } + }; + RocketChat.models.Uploads.insertFileInitByUsername(user.username, this.userId, 's3', file, upload); + + return path + user.username; + } + } + ]; const type = RocketChat.settings.get('FileUpload_Storage_Type'); const bucket = RocketChat.settings.get('FileUpload_S3_Bucket'); @@ -54,76 +127,41 @@ const createS3Directive = _.debounce(() => { }); if (type === 'AmazonS3' && !_.isEmpty(bucket) && !_.isEmpty(accessKey) && !_.isEmpty(secretKey)) { - if (Slingshot._directives[directiveName]) { - delete Slingshot._directives[directiveName]; - } - const config = { - bucket, - AWSAccessKeyId: accessKey, - AWSSecretAccessKey: secretKey, - key(file, metaContext) { - const path = `${ RocketChat.hostname }/${ metaContext.rid }/${ this.userId }/`; - - const upload = { s3: { - bucket, - region, - path - }}; - const fileId = RocketChat.models.Uploads.insertFileInit(metaContext.rid, this.userId, 's3', file, upload); - - return path + fileId; + directives.forEach((conf) => { + createDirective(conf.name, { key: conf.key, bucket, accessKey, secretKey, region, acl, cdn, bucketUrl}); + }); + } else { + directives.forEach((conf) => { + if (Slingshot._directives[conf.name]) { + delete Slingshot._directives[conf.name]; } - }; - - if (!_.isEmpty(acl)) { - config.acl = acl; - } - - if (!_.isEmpty(cdn)) { - config.cdn = cdn; - } - - if (!_.isEmpty(region)) { - config.region = region; - } - - if (!_.isEmpty(bucketUrl)) { - config.bucketUrl = bucketUrl; - } - - try { - Slingshot.createDirective(directiveName, Slingshot.S3Storage, config); - } catch (e) { - SystemLogger.error('Error configuring S3 ->', e.message); - } - } else if (Slingshot._directives[directiveName]) { - delete Slingshot._directives[directiveName]; + }); } }, 500); -RocketChat.settings.get('FileUpload_Storage_Type', createS3Directive); +RocketChat.settings.get('FileUpload_Storage_Type', configureSlingshot); -RocketChat.settings.get('FileUpload_S3_Bucket', createS3Directive); +RocketChat.settings.get('FileUpload_S3_Bucket', configureSlingshot); -RocketChat.settings.get('FileUpload_S3_Acl', createS3Directive); +RocketChat.settings.get('FileUpload_S3_Acl', configureSlingshot); RocketChat.settings.get('FileUpload_S3_AWSAccessKeyId', function(key, value) { S3accessKey = value; - createS3Directive(); + configureSlingshot(); }); RocketChat.settings.get('FileUpload_S3_AWSSecretAccessKey', function(key, value) { S3secretKey = value; - createS3Directive(); + configureSlingshot(); }); RocketChat.settings.get('FileUpload_S3_URLExpiryTimeSpan', function(key, value) { S3expiryTimeSpan = value; - createS3Directive(); + configureSlingshot(); }); -RocketChat.settings.get('FileUpload_S3_CDN', createS3Directive); +RocketChat.settings.get('FileUpload_S3_CDN', configureSlingshot); -RocketChat.settings.get('FileUpload_S3_Region', createS3Directive); +RocketChat.settings.get('FileUpload_S3_Region', configureSlingshot); -RocketChat.settings.get('FileUpload_S3_BucketURL', createS3Directive); +RocketChat.settings.get('FileUpload_S3_BucketURL', configureSlingshot); diff --git a/packages/rocketchat-file-upload/server/lib/FileUpload.js b/packages/rocketchat-file-upload/server/lib/FileUpload.js index cb0aadbc06c9..05ec504af3cc 100644 --- a/packages/rocketchat-file-upload/server/lib/FileUpload.js +++ b/packages/rocketchat-file-upload/server/lib/FileUpload.js @@ -16,7 +16,7 @@ FileUpload.delete = function(fileId) { this.handlers[file.store].delete(file); - return RocketChat.models.Uploads.remove(file._id); + return RocketChat.models.Uploads.deleteFile(file._id); }; FileUpload.get = function(file, req, res, next) { diff --git a/packages/rocketchat-lib/server/models/Uploads.coffee b/packages/rocketchat-lib/server/models/Uploads.coffee index 53dd79256891..2604403bbe6c 100644 --- a/packages/rocketchat-lib/server/models/Uploads.coffee +++ b/packages/rocketchat-lib/server/models/Uploads.coffee @@ -29,9 +29,8 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return @find fileQuery, fileOptions - insertFileInit: (roomId, userId, store, file, extra) -> + insertFileInit: (userId, store, file, extra) -> fileData = - rid: roomId userId: userId store: store complete: false @@ -49,6 +48,23 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return file + insertFileInitByUsername: (username, userId, store, file, extra) -> + fileData = + _id: username + userId: userId + store: store + complete: false + uploading: true + progress: 0 + extension: s.strRightBack(file.name, '.') + uploadedAt: new Date() + + _.extend(fileData, file, extra); + + file = @insertOrUpsert fileData + + return file + updateFileComplete: (fileId, userId, file) -> if not fileId return @@ -65,9 +81,40 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base update.$set = _.extend file, update.$set - if @model.direct?.insert? + if @model.direct?.update? + result = @model.direct.update filter, update + else + result = @update filter, update + + return result + + updateFileCompleteByUsername: (username, userId, url) -> + if not username + return + + filter = + username: username + userId: userId + + update = + $set: + complete: true + uploading: false + progress: 1 + url: url + + if @model.direct?.update? result = @model.direct.update filter, update else result = @update filter, update return result + + findOneByUsername: (username) -> + return @findOne username: username + + deleteFile: (fileId) -> + if @model.direct?.remove? + return @model.direct.remove { _id: fileId } + else + return @remove { _id: fileId } diff --git a/packages/rocketchat-ui-account/client/avatar/prompt.coffee b/packages/rocketchat-ui-account/client/avatar/prompt.coffee index 50f3d8838fdf..5b4849a1dcd2 100644 --- a/packages/rocketchat-ui-account/client/avatar/prompt.coffee +++ b/packages/rocketchat-ui-account/client/avatar/prompt.coffee @@ -1,4 +1,6 @@ import toastr from 'toastr' +import mime from 'mime-type/with-db' + Template.avatarPrompt.onCreated -> self = this self.suggestions = new ReactiveVar @@ -38,7 +40,7 @@ Template.avatarPrompt.helpers return '@'+Meteor.user()?.username Template.avatarPrompt.events - 'click .select-service': -> + 'click .select-service': (event, instance) -> if @service is 'initials' Meteor.call 'resetAvatar', (err) -> if err?.details?.timeToReset? @@ -60,13 +62,29 @@ Template.avatarPrompt.events else toastr.error t('Please_enter_value_for_url') else - tmpService = @service - Meteor.call 'setAvatarFromService', @blob, @contentType, @service, (err) -> - if err?.details?.timeToReset? - toastr.error t('error-too-many-requests', { seconds: parseInt(err.details.timeToReset / 1000) }) - else - toastr.success t('Avatar_changed_successfully') - RocketChat.callbacks.run('userAvatarSet', tmpService) + files = instance.find('input[type=file]').files + if not files or files.length is 0 + files = e.dataTransfer?.files or [] + + for file in files + Object.defineProperty(file, 'type', { value: mime.lookup(file.name) }) + + record = + name: files[0].name + size: files[0].size + type: files[0].type + # description: document.getElementById('file-description').value + + upload = fileUploadHandler 'rocketchat-avatars', record, files[0] + + # upload.onProgress = (progress) -> + # console.log 'progress ->', progress + + upload.start (error, result) -> + if result + Meteor.call 'saveAvatarFile', result, () => + toastr.success t('Avatar_changed_successfully') + RocketChat.callbacks.run('userAvatarSet', @service) 'click .login-with-service': (event, template) -> loginWithService = "loginWith#{_.capitalize(this)}" diff --git a/packages/rocketchat-ui/client/lib/fileUpload.js b/packages/rocketchat-ui/client/lib/fileUpload.js new file mode 100644 index 000000000000..ad91b1fc40e8 --- /dev/null +++ b/packages/rocketchat-ui/client/lib/fileUpload.js @@ -0,0 +1,232 @@ +/* globals fileUploadHandler, Handlebars, fileUpload */ +/* exported fileUpload */ + +function readAsDataURL(file, callback) { + const reader = new FileReader(); + reader.onload = ev => callback(ev.target.result, file); + + return reader.readAsDataURL(file); +} + +function getUploadPreview(file, callback) { + // If greater then 10MB don't try and show a preview + if (file.file.size > (10 * 1000000)) { + return callback(file, null); + } else if ((file.file.type.indexOf('audio') > -1) || (file.file.type.indexOf('video') > -1) || (file.file.type.indexOf('image') > -1)) { + file.type = file.file.type.split('/')[0]; + + return readAsDataURL(file.file, content => callback(file, content)); + } else { + return callback(file, null); + } +} + +function formatBytes(bytes, decimals) { + if (bytes === 0) { + return '0 Bytes'; + } + + const k = 1000; + const dm = (decimals + 1) || 3; + + const sizes = [ + 'Bytes', + 'KB', + 'MB', + 'GB', + 'TB', + 'PB' + ]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${ parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) } ${ sizes[i] }`; +} + +fileUpload = function(filesToUpload) { + const roomId = Session.get('openedRoom'); + const files = [].concat(filesToUpload); + + function consume() { + const file = files.pop(); + if ((file == null)) { + swal.close(); + return; + } + + if (!RocketChat.fileUploadIsValidContentType(file.file.type)) { + swal({ + title: t('FileUpload_MediaType_NotAccepted'), + text: file.file.type || `*.${ s.strRightBack(file.file.name, '.') }`, + type: 'error', + timer: 3000 + }); + return; + } + + if (file.file.size === 0) { + swal({ + title: t('FileUpload_File_Empty'), + type: 'error', + timer: 1000 + }); + return; + } + + return getUploadPreview(file, function(file, preview) { + let text = ''; + + if (file.type === 'audio') { + text = `\ +
+ +
+
+ + +
`; + } else if (file.type === 'video') { + text = `\ +
+ +
+
+ + +
`; + } else if (file.type === 'image') { + text = `\ +
+
+
+
+ + +
`; + } else { + const fileSize = formatBytes(file.file.size); + + text = `\ +
+
${ Handlebars._escape(file.name) } - ${ fileSize }
+
+
+ + +
`; + } + + return swal({ + title: t('Upload_file_question'), + text, + showCancelButton: true, + closeOnConfirm: false, + closeOnCancel: false, + confirmButtonText: t('Send'), + cancelButtonText: t('Cancel'), + html: true + }, function(isConfirm) { + consume(); + if (isConfirm !== true) { + return; + } + + const record = { + name: document.getElementById('file-name').value || file.name || file.file.name, + size: file.file.size, + type: file.file.type, + rid: roomId, + description: document.getElementById('file-description').value + }; + + const upload = fileUploadHandler('rocketchat-uploads', record, file.file); + + let uploading = Session.get('uploading') || []; + uploading.push({ + id: upload.id, + name: upload.getFileName(), + percentage: 0 + }); + + Session.set('uploading', uploading); + + upload.onProgress = function(progress) { + uploading = Session.get('uploading'); + + const item = _.findWhere(uploading, {id: upload.id}); + if (item != null) { + item.percentage = Math.round(progress * 100) || 0; + return Session.set('uploading', uploading); + } + }; + + upload.start(function(error, file) { + if (error) { + let uploading = Session.get('uploading'); + if (!Array.isArray(uploading)) { + uploading = []; + } + + const item = _.findWhere(uploading, { id: this.id }); + + if (_.isObject(item)) { + item.error = error.error; + item.percentage = 0; + } else { + uploading.push({ + error: error.error, + percentage: 0 + }); + } + + Session.set('uploading', uploading); + return; + } else if (file) { + const uploading = Session.get('uploading'); + if (uploading !== null) { + const item = _.findWhere(uploading, { + id: this.id + }); + return Session.set('uploading', _.without(uploading, item)); + } + } + }); + + Tracker.autorun(function(c) { + const cancel = Session.get(`uploading-cancel-${ upload.id }`); + if (cancel) { + let item; + upload.stop(); + c.stop(); + + uploading = Session.get('uploading'); + if (uploading != null) { + item = _.findWhere(uploading, {id: upload.id}); + if (item != null) { + item.percentage = 0; + } + Session.set('uploading', uploading); + } + + return Meteor.setTimeout(function() { + uploading = Session.get('uploading'); + if (uploading != null) { + item = _.findWhere(uploading, {id: upload.id}); + return Session.set('uploading', _.without(uploading, item)); + } + } + , 1000); + } + }); + }); + }); + } + + consume(); +}; diff --git a/packages/rocketchat-ui/package.js b/packages/rocketchat-ui/package.js index 0a754855308e..acd455f1fe4d 100644 --- a/packages/rocketchat-ui/package.js +++ b/packages/rocketchat-ui/package.js @@ -38,7 +38,7 @@ Package.onUse(function(api) { api.addFiles('client/lib/chatMessages.coffee', 'client'); api.addFiles('client/lib/collections.js', 'client'); api.addFiles('client/lib/customEventPolyfill.js', 'client'); - api.addFiles('client/lib/fileUpload.coffee', 'client'); + api.addFiles('client/lib/fileUpload.js', 'client'); api.addFiles('client/lib/fireEvent.js', 'client'); api.addFiles('client/lib/iframeCommands.js', 'client'); api.addFiles('client/lib/menu.js', 'client'); @@ -108,4 +108,6 @@ Package.onUse(function(api) { api.addFiles('client/views/app/videoCall/videoButtons.js', 'client'); api.addFiles('client/views/app/videoCall/videoCall.js', 'client'); api.addFiles('client/views/app/photoswipe.js', 'client'); + + api.export('fileUpload'); }); diff --git a/server/methods/setAvatarFromService.js b/server/methods/setAvatarFromService.js index 6dc13b719e2e..bdc0768552c3 100644 --- a/server/methods/setAvatarFromService.js +++ b/server/methods/setAvatarFromService.js @@ -19,6 +19,13 @@ Meteor.methods({ const user = Meteor.user(); return RocketChat.setUserAvatar(user, dataURI, contentType, service); + }, + + saveAvatarFile(file) { + const user = RocketChat.models.Users.findOneById(Meteor.userId()); + // console.log('user ->', user); + // console.log('file ->', file); + RocketChat.models.Uploads.updateFileCompleteByUsername(user.username, Meteor.userId(), file.url); } }); diff --git a/server/startup/avatar.js b/server/startup/avatar.js index 6aca22c66592..634effe061b2 100644 --- a/server/startup/avatar.js +++ b/server/startup/avatar.js @@ -35,13 +35,11 @@ Meteor.startup(function() { transformWrite }); - return WebApp.connectHandlers.use('/avatar/', Meteor.bindEnvironment(function(req, res/*, next*/) { + WebApp.connectHandlers.use('/avatar/', Meteor.bindEnvironment(function(req, res/*, next*/) { const params = { username: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')) }; - let username = params.username.replace(/\.jpg$/, '').replace(/^@/, ''); - if (_.isEmpty(params.username)) { res.writeHead(403); res.write('Forbidden'); @@ -49,24 +47,23 @@ Meteor.startup(function() { return; } - let file; + return getNewAvatar(params, req, res); + })); +}); - if (params.username[0] !== '@') { - if (Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm) { - const user = RocketChat.models.Users.findOneByUsername(username); - if (user && user.services && user.services.sandstorm && user.services.sandstorm.picture) { - res.setHeader('Location', user.services.sandstorm.picture); - res.writeHead(302); - res.end(); - return; - } - } - file = RocketChatFileAvatarInstance.getFileWithReadStream(encodeURIComponent(`${ username }.jpg`)); - } +function getNewAvatar(params, req, res) { + const match = /^\/([^?]*)/.exec(req.url); + + if (match[1]) { + let username = decodeURIComponent(match[1]); + const file = RocketChat.models.Uploads.findOneByUsername(username); + + if (file) { + res.setHeader('Content-Security-Policy', 'default-src \'none\''); - res.setHeader('Content-Disposition', 'inline'); - if (!file) { + return FileUpload.get(file, req, res); + } else { res.setHeader('Content-Type', 'image/svg+xml'); res.setHeader('Cache-Control', 'public, max-age=0'); res.setHeader('Expires', '-1'); @@ -120,35 +117,113 @@ Meteor.startup(function() { return; } + } + + res.writeHead(404); + res.end(); + return; +} + +function getOldAvatar(params, req, res) { + let username = params.username.replace(/\.jpg$/, '').replace(/^@/, ''); + let file; + if (params.username[0] !== '@') { + if (Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm) { + const user = RocketChat.models.Users.findOneByUsername(username); + if (user && user.services && user.services.sandstorm && user.services.sandstorm.picture) { + res.setHeader('Location', user.services.sandstorm.picture); + res.writeHead(302); + res.end(); + return; + } + } + + file = RocketChatFileAvatarInstance.getFileWithReadStream(encodeURIComponent(`${ username }.jpg`)); + } + + res.setHeader('Content-Disposition', 'inline'); + if (!file) { + res.setHeader('Content-Type', 'image/svg+xml'); + res.setHeader('Cache-Control', 'public, max-age=0'); + res.setHeader('Expires', '-1'); + res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT'); const reqModifiedHeader = req.headers['if-modified-since']; + if (reqModifiedHeader) { - if (reqModifiedHeader === (file.uploadDate && file.uploadDate.toUTCString())) { - res.setHeader('Last-Modified', reqModifiedHeader); + if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') { res.writeHead(304); res.end(); return; } } - res.setHeader('Cache-Control', 'public, max-age=0'); - res.setHeader('Expires', '-1'); - res.setHeader('Last-Modified', (file.uploadDate && file.uploadDate.toUTCString()) || new Date().toUTCString()); - res.setHeader('Content-Length', file.length); + const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; - if (file.contentType) { - res.setHeader('Content-Type', file.contentType); - } else { - file.readStream.once('data', function(chunk) { - const fileType = getFileType(chunk); - if (fileType) { - return res.setHeader('Content-Type', fileType.mime); - } else { - return res.setHeader('Content-Type', 'image/jpeg'); + if (RocketChat.settings.get('UI_Use_Name_Avatar')) { + const user = RocketChat.models.Users.findOneByUsername(username, { + fields: { + name: 1 } }); + + if (user && user.name) { + username = user.name; + } } - file.readStream.pipe(res); - })); -}); + let color = ''; + let initials = ''; + + if (username === '?') { + color = '#000'; + initials = username; + } else { + const position = username.length % colors.length; + + color = colors[position]; + username = username.replace(/[^A-Za-z0-9]/g, '.').replace(/\.+/g, '.').replace(/(^\.)|(\.$)/g, ''); + + const usernameParts = username.split('.'); + + initials = usernameParts.length > 1 ? _.first(usernameParts)[0] + _.last(usernameParts)[0] : username.replace(/[^A-Za-z0-9]/g, '').substr(0, 2); + initials = initials.toUpperCase(); + } + + const svg = `\n\n \n ${ initials }\n \n`; + res.write(svg); + res.end(); + + return; + } + + const reqModifiedHeader = req.headers['if-modified-since']; + if (reqModifiedHeader) { + if (reqModifiedHeader === (file.uploadDate && file.uploadDate.toUTCString())) { + res.setHeader('Last-Modified', reqModifiedHeader); + res.writeHead(304); + res.end(); + return; + } + } + + res.setHeader('Cache-Control', 'public, max-age=0'); + res.setHeader('Expires', '-1'); + res.setHeader('Last-Modified', (file.uploadDate && file.uploadDate.toUTCString()) || new Date().toUTCString()); + res.setHeader('Content-Length', file.length); + + if (file.contentType) { + res.setHeader('Content-Type', file.contentType); + } else { + file.readStream.once('data', function(chunk) { + const fileType = getFileType(chunk); + if (fileType) { + return res.setHeader('Content-Type', fileType.mime); + } else { + return res.setHeader('Content-Type', 'image/jpeg'); + } + }); + } + + file.readStream.pipe(res); +} From a2de9f51557ba1a522a5026a3e98e5d8e5c8c55f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 25 Apr 2017 09:38:03 -0300 Subject: [PATCH 007/102] Add Google Cloud Storage support to avatars --- .../client/lib/FileUploadGoogleStorage.js | 33 ++----- .../globalFileRestrictions.js | 1 + .../config/configFileUploadGoogleStorage.js | 88 ++++++++++++++----- .../client/avatar/prompt.coffee | 2 +- .../rocketchat-ui/client/lib/fileUpload.js | 2 +- 5 files changed, 73 insertions(+), 53 deletions(-) diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js index d1daa62ec07f..b1db2b767d89 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js @@ -1,36 +1,19 @@ /* globals FileUpload, FileUploadBase, Slingshot */ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileUploadBase { - constructor(meta, file) { + constructor(directive, meta, file) { super(meta, file); - this.uploader = new Slingshot.Upload('rocketchat-uploads-gs', { rid: meta.rid }); + this.uploader = new Slingshot.Upload(directive, { rid: meta.rid }); } - start() { + start(callback) { this.uploader.send(this.file, (error, downloadUrl) => { if (this.computation) { this.computation.stop(); } if (error) { - let uploading = Session.get('uploading'); - if (!Array.isArray(uploading)) { - uploading = []; - } - - const item = _.findWhere(uploading, { id: this.id }); - - if (_.isObject(item)) { - item.error = error.error; - item.percentage = 0; - } else { - uploading.push({ - error: error.error, - percentage: 0 - }); - } - - Session.set('uploading', uploading); + return callback.call(this, error); } else { const file = _.pick(this.meta, 'type', 'size', 'name', 'identify', 'description'); file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); @@ -38,13 +21,7 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileU Meteor.call('sendFileMessage', this.meta.rid, 'googleCloudStorage', file, () => { Meteor.setTimeout(() => { - const uploading = Session.get('uploading'); - if (uploading !== null) { - const item = _.findWhere(uploading, { - id: this.id - }); - return Session.set('uploading', _.without(uploading, item)); - } + callback.call(this, null, file); }, 2000); }); } diff --git a/packages/rocketchat-file-upload/globalFileRestrictions.js b/packages/rocketchat-file-upload/globalFileRestrictions.js index 3c52390c4341..8e2d5086dee2 100644 --- a/packages/rocketchat-file-upload/globalFileRestrictions.js +++ b/packages/rocketchat-file-upload/globalFileRestrictions.js @@ -28,3 +28,4 @@ const slingShotConfig = { Slingshot.fileRestrictions('rocketchat-avatars', slingShotConfig); Slingshot.fileRestrictions('rocketchat-uploads', slingShotConfig); Slingshot.fileRestrictions('rocketchat-uploads-gs', slingShotConfig); +Slingshot.fileRestrictions('rocketchat-avatars-gs', slingShotConfig); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index 9e8d5219ba60..b7ae3dfa455a 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -43,6 +43,25 @@ function generateDeleteUrl({ file }) { return `https://${ file.googleCloudStorage.bucket }.storage.googleapis.com/${ encodeURIComponent(parts.path) }?GoogleAccessId=${ parts.accessId }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`; } +function createDirective(directiveName, { key, bucket, accessId, secret }) { + if (Slingshot._directives[directiveName]) { + delete Slingshot._directives[directiveName]; + } + + const config = { + bucket, + GoogleAccessId: accessId, + GoogleSecretKey: secret, + key + }; + + try { + Slingshot.createDirective(directiveName, Slingshot.GoogleCloud, config); + } catch (e) { + console.error('Error configuring GoogleCloudStorage ->', e.message); + } +} + FileUpload.addHandler('googleCloudStorage', { get(file, req, res) { const fileUrl = generateGetURL({ file }); @@ -71,7 +90,43 @@ FileUpload.addHandler('googleCloudStorage', { }); const createGoogleStorageDirective = _.debounce(() => { - const directiveName = 'rocketchat-uploads-gs'; + const directives = [ + { + name: 'rocketchat-uploads-gs', + key: function _googleCloudStorageKey(file, metaContext) { + const path = `${ RocketChat.settings.get('uniqueID') }/${ metaContext.rid }/${ this.userId }/`; + const upload = { + rid: metaContext.rid, + googleCloudStorage: { + bucket: RocketChat.settings.get('FileUpload_GoogleStorage_Bucket'), + path + } + }; + const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'googleCloudStorage', file, upload); + + return path + fileId; + } + }, + { + name: 'rocketchat-avatars-gs', + key(file/*, metaContext*/) { + const path = `${ RocketChat.settings.get('uniqueID') }/avatars/`; + + const user = RocketChat.models.Users.findOneById(this.userId); + + const upload = { + username: user && user.username, + googleCloudStorage: { + bucket: RocketChat.settings.get('FileUpload_GoogleStorage_Bucket'), + path + } + }; + RocketChat.models.Uploads.insertFileInitByUsername(user.username, this.userId, 'googleCloudStorage', file, upload); + + return path + user.username; + } + } + ]; const type = RocketChat.settings.get('FileUpload_Storage_Type'); const bucket = RocketChat.settings.get('FileUpload_GoogleStorage_Bucket'); @@ -79,29 +134,16 @@ const createGoogleStorageDirective = _.debounce(() => { const secret = RocketChat.settings.get('FileUpload_GoogleStorage_Secret'); if (type === 'GoogleCloudStorage' && !_.isEmpty(secret) && !_.isEmpty(accessId) && !_.isEmpty(bucket)) { - if (Slingshot._directives[directiveName]) { - delete Slingshot._directives[directiveName]; - } - - const config = { - bucket, - GoogleAccessId: accessId, - GoogleSecretKey: secret, - key: function _googleCloudStorageKey(file, metaContext) { - const path = `${ RocketChat.settings.get('uniqueID') }/${ metaContext.rid }/${ this.userId }/`; - const fileId = RocketChat.models.Uploads.insertFileInit(metaContext.rid, this.userId, 'googleCloudStorage', file, { googleCloudStorage: { bucket, path }}); - - return path + fileId; - } - }; - - try { - Slingshot.createDirective(directiveName, Slingshot.GoogleCloud, config); - } catch (e) { - SystemLogger.error('Error configuring GoogleCloudStorage ->', e.message); - } + directives.forEach((conf) => { + console.log('conf.name ->', conf.name); + createDirective(conf.name, { key: conf.key, bucket, accessId, secret }); + }); } else { - delete Slingshot._directives[directiveName]; + directives.forEach((conf) => { + if (Slingshot._directives[conf.name]) { + delete Slingshot._directives[conf.name]; + } + }); } }, 500); diff --git a/packages/rocketchat-ui-account/client/avatar/prompt.coffee b/packages/rocketchat-ui-account/client/avatar/prompt.coffee index 5b4849a1dcd2..82fa805444f9 100644 --- a/packages/rocketchat-ui-account/client/avatar/prompt.coffee +++ b/packages/rocketchat-ui-account/client/avatar/prompt.coffee @@ -75,7 +75,7 @@ Template.avatarPrompt.events type: files[0].type # description: document.getElementById('file-description').value - upload = fileUploadHandler 'rocketchat-avatars', record, files[0] + upload = fileUploadHandler 'rocketchat-avatars-gs', record, files[0] # upload.onProgress = (progress) -> # console.log 'progress ->', progress diff --git a/packages/rocketchat-ui/client/lib/fileUpload.js b/packages/rocketchat-ui/client/lib/fileUpload.js index ad91b1fc40e8..d7ef1590a437 100644 --- a/packages/rocketchat-ui/client/lib/fileUpload.js +++ b/packages/rocketchat-ui/client/lib/fileUpload.js @@ -145,7 +145,7 @@ fileUpload = function(filesToUpload) { description: document.getElementById('file-description').value }; - const upload = fileUploadHandler('rocketchat-uploads', record, file.file); + const upload = fileUploadHandler('rocketchat-uploads-gs', record, file.file); let uploading = Session.get('uploading') || []; uploading.push({ From d40d639d47af31f554a9c1cf1c340a8880ce9214 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 27 Apr 2017 18:14:33 -0300 Subject: [PATCH 008/102] Add GridFS support to avatars --- lib/fileUpload.js | 73 +++++++++++++++++++ .../client/lib/FileUploadGridFS.js | 36 +++------ .../client/avatar/prompt.coffee | 2 +- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/lib/fileUpload.js b/lib/fileUpload.js index fbb006f15b7e..8f26103319de 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -72,6 +72,79 @@ if (UploadFS) { return true; } }); + + Meteor.fileStoreAvatar = new UploadFS.store.GridFS({ + collection: RocketChat.models.Uploads.model, + name: 'rocketchat_uploads_avatar', + collectionName: 'rocketchat_uploads', + // filter: new UploadFS.Filter({ + // onCheck: FileUpload.validateFileUpload + // }), + beforeSave() { + console.log('beforeSave ->', arguments); + }, + beforeWrite() { + console.log('beforeWrite ->', arguments); + return { + username: 'testando' + }; + }, + transformWrite(readStream, writeStream, fileId, file) { + if (RocketChatFile.enabled === false || !/^image\/.+/.test(file.type)) { + return readStream.pipe(writeStream); + } + + let stream = undefined; + + const identify = function(err, data) { + if (err) { + return stream.pipe(writeStream); + } + + file.identify = { + format: data.format, + size: data.size + }; + + if (data.Orientation && !['', 'Unknown', 'Undefined'].includes(data.Orientation)) { + RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); + } else { + stream.pipe(writeStream); + } + }; + + stream = RocketChatFile.gm(readStream).identify(identify).stream(); + }, + + onRead(fileId, file, req, res) { + if (RocketChat.settings.get('FileUpload_ProtectFiles')) { + let uid; + let token; + + if (req && req.headers && req.headers.cookie) { + const rawCookies = req.headers.cookie; + + if (rawCookies) { + uid = cookie.get('rc_uid', rawCookies) ; + token = cookie.get('rc_token', rawCookies); + } + } + + if (!uid) { + uid = req.query.rc_uid; + token = req.query.rc_token; + } + + if (!uid || !token || !RocketChat.models.Users.findOneByIdAndLoginToken(uid, token)) { + res.writeHead(403); + return false; + } + } + + res.setHeader('content-disposition', `attachment; filename="${ encodeURIComponent(file.name) }"`); + return true; + } + }); }; Meteor.startup(function() { diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js index ac14bb7fafd0..a952090d87d6 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js @@ -1,38 +1,24 @@ /* globals FileUploadBase, UploadFS, FileUpload:true */ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { - constructor(meta, file) { + constructor(directive, meta, file) { super(meta, file); + this.store = directive === 'avatar' ? Meteor.fileStoreAvatar : Meteor.fileStore; + } + + start(callback) { this.handler = new UploadFS.Uploader({ - store: Meteor.fileStore, - data: file, - file: meta, + store: this.store, + data: this.file, + file: this.meta, onError: (err) => { - const uploading = Session.get('uploading'); - if (uploading != null) { - const item = _.findWhere(uploading, { - id: this.id - }); - if (item != null) { - item.error = err.reason; - item.percentage = 0; - } - return Session.set('uploading', uploading); - } + return callback(err); }, onComplete: (fileData) => { const file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); - file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); - Meteor.call('sendFileMessage', this.meta.rid, null, file, () => { Meteor.setTimeout(() => { - const uploading = Session.get('uploading'); - if (uploading != null) { - const item = _.findWhere(uploading, { - id: this.id - }); - return Session.set('uploading', _.without(uploading, item)); - } + return callback(null, file); }, 2000); }); } @@ -41,9 +27,7 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { this.handler.onProgress = (file, progress) => { this.onProgress(progress); }; - } - start() { return this.handler.start(); } diff --git a/packages/rocketchat-ui-account/client/avatar/prompt.coffee b/packages/rocketchat-ui-account/client/avatar/prompt.coffee index 82fa805444f9..17f255c3cbd0 100644 --- a/packages/rocketchat-ui-account/client/avatar/prompt.coffee +++ b/packages/rocketchat-ui-account/client/avatar/prompt.coffee @@ -75,7 +75,7 @@ Template.avatarPrompt.events type: files[0].type # description: document.getElementById('file-description').value - upload = fileUploadHandler 'rocketchat-avatars-gs', record, files[0] + upload = fileUploadHandler 'avatar', record, files[0] # upload.onProgress = (progress) -> # console.log 'progress ->', progress From 4785fd479a3dfb87625c8cedc3b9dfabc7b9bfc6 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 28 Apr 2017 10:20:56 -0300 Subject: [PATCH 009/102] Better handling for GridFS avatars --- lib/fileUpload.js | 21 +- .../client/lib/FileUploadGridFS.js | 4 + .../server/config/configFileUploadAmazonS3.js | 2 +- .../config/configFileUploadGoogleStorage.js | 2 +- .../server/config/configFileUploadGridFS.js | 28 +++ .../server/models/Uploads.coffee | 23 +- server/methods/setAvatarFromService.js | 33 ++- server/startup/avatar.js | 219 +++++------------- 8 files changed, 153 insertions(+), 179 deletions(-) diff --git a/lib/fileUpload.js b/lib/fileUpload.js index 8f26103319de..f466da9ceb2d 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -80,15 +80,6 @@ if (UploadFS) { // filter: new UploadFS.Filter({ // onCheck: FileUpload.validateFileUpload // }), - beforeSave() { - console.log('beforeSave ->', arguments); - }, - beforeWrite() { - console.log('beforeWrite ->', arguments); - return { - username: 'testando' - }; - }, transformWrite(readStream, writeStream, fileId, file) { if (RocketChatFile.enabled === false || !/^image\/.+/.test(file.type)) { return readStream.pipe(writeStream); @@ -116,6 +107,18 @@ if (UploadFS) { stream = RocketChatFile.gm(readStream).identify(identify).stream(); }, + onFinishUpload(file) { + // update file record to match user's username + const user = RocketChat.models.Users.findOneById(file.userId); + const oldAvatar = RocketChat.models.Uploads.findOneByName(`${ user.username }.avatar`); + if (oldAvatar) { + Meteor.fileStoreAvatar.delete(oldAvatar._id); + RocketChat.models.Uploads.deleteFile(oldAvatar._id); + } + RocketChat.models.Uploads.updateFileNameById(file._id, `${ user.username }.avatar`); + // console.log('upload finished ->', file); + }, + onRead(fileId, file, req, res) { if (RocketChat.settings.get('FileUpload_ProtectFiles')) { let uid; diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js index a952090d87d6..d5236b1c971c 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js @@ -2,6 +2,7 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); + this.directive = directive; this.store = directive === 'avatar' ? Meteor.fileStoreAvatar : Meteor.fileStore; } @@ -15,6 +16,9 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { }, onComplete: (fileData) => { const file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); + if (this.directive === 'avatar') { + return callback(null, file); + } file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); Meteor.call('sendFileMessage', this.meta.rid, null, file, () => { Meteor.setTimeout(() => { diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index 284edb19921a..95d1dcd84347 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -105,7 +105,7 @@ const configureSlingshot = _.debounce(() => { path } }; - RocketChat.models.Uploads.insertFileInitByUsername(user.username, this.userId, 's3', file, upload); + RocketChat.models.Uploads.insertAvatarFileInit(user.username, this.userId, 's3', file, upload); return path + user.username; } diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index b7ae3dfa455a..b2d1c6b9a826 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -121,7 +121,7 @@ const createGoogleStorageDirective = _.debounce(() => { path } }; - RocketChat.models.Uploads.insertFileInitByUsername(user.username, this.userId, 'googleCloudStorage', file, upload); + RocketChat.models.Uploads.insertAvatarFileInit(user.username, this.userId, 'googleCloudStorage', file, upload); return path + user.username; } diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js index 21ac212db8ed..fad40155182d 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js @@ -139,3 +139,31 @@ FileUpload.addHandler('rocketchat_uploads', { return Meteor.fileStore.delete(file._id); } }); + +FileUpload.addHandler('rocketchat_uploads_avatar', { + get(file, req, res) { + const reqModifiedHeader = req.headers['if-modified-since']; + if (reqModifiedHeader) { + if (reqModifiedHeader === (file.uploadedAt && file.uploadedAt.toUTCString())) { + res.setHeader('Last-Modified', reqModifiedHeader); + res.writeHead(304); + res.end(); + return; + } + } + + file = FileUpload.addExtensionTo(file); + const headers = { + 'Cache-Control': 'public, max-age=0', + 'Expires': '-1', + 'Content-Disposition': 'inline', + 'Last-Modified': file.uploadedAt.toUTCString(), + 'Content-Type': file.type, + 'Content-Length': file.size + }; + return readFromGridFS(file.store, file._id, file, headers, req, res); + }, + delete(file) { + return Meteor.fileStore.delete(file._id); + } +}); diff --git a/packages/rocketchat-lib/server/models/Uploads.coffee b/packages/rocketchat-lib/server/models/Uploads.coffee index 2604403bbe6c..6fc165a190c1 100644 --- a/packages/rocketchat-lib/server/models/Uploads.coffee +++ b/packages/rocketchat-lib/server/models/Uploads.coffee @@ -48,9 +48,9 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return file - insertFileInitByUsername: (username, userId, store, file, extra) -> + insertAvatarFileInit: (name, userId, store, file, extra) -> fileData = - _id: username + name: "#{name}.avatar" userId: userId store: store complete: false @@ -88,12 +88,13 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return result - updateFileCompleteByUsername: (username, userId, url) -> - if not username + # @TODO deprecated + updateFileCompleteByNameAndUserId: (name, userId, url) -> + if not name return filter = - username: username + name: name userId: userId update = @@ -110,11 +111,19 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return result - findOneByUsername: (username) -> - return @findOne username: username + findOneByName: (name) -> + return @findOne name: name deleteFile: (fileId) -> if @model.direct?.remove? return @model.direct.remove { _id: fileId } else return @remove { _id: fileId } + + updateFileNameById: (fileId, name) -> + filter = _id: fileId + update = $set: name: name + if @model.direct?.update? + return @model.direct.update filter, update + else + return @update filter, update diff --git a/server/methods/setAvatarFromService.js b/server/methods/setAvatarFromService.js index bdc0768552c3..8781e1c45022 100644 --- a/server/methods/setAvatarFromService.js +++ b/server/methods/setAvatarFromService.js @@ -22,10 +22,37 @@ Meteor.methods({ }, saveAvatarFile(file) { + check(file, Match.ObjectIncluding({ + _id: String + })); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'setAvatarFromService' + }); + } + + if (!RocketChat.settings.get('Accounts_AllowUserAvatarChange')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'setAvatarFromService' + }); + } + const user = RocketChat.models.Users.findOneById(Meteor.userId()); - // console.log('user ->', user); - // console.log('file ->', file); - RocketChat.models.Uploads.updateFileCompleteByUsername(user.username, Meteor.userId(), file.url); + const fileSaved = RocketChat.models.Uploads.findOneById(file._id); + + if (fileSaved.userId !== user._id) { + // this file is not user's avatar + throw new Meteor.Error('invalid-avatar'); + } + + // just returns if file already complete (case for GridFS) + if (fileSaved.complete) { + return true; + } + + RocketChat.models.Uploads.updateFileCompleteByNameAndUserId(`${ user.username }.avatar`, Meteor.userId(), file.url); + return true; } }); diff --git a/server/startup/avatar.js b/server/startup/avatar.js index 634effe061b2..cf31fcc2366b 100644 --- a/server/startup/avatar.js +++ b/server/startup/avatar.js @@ -1,5 +1,4 @@ -import getFileType from 'file-type'; - +/* globals FileUpload */ Meteor.startup(function() { let storeType = 'GridFS'; @@ -47,183 +46,87 @@ Meteor.startup(function() { return; } - return getNewAvatar(params, req, res); - })); -}); - - -function getNewAvatar(params, req, res) { - const match = /^\/([^?]*)/.exec(req.url); - - if (match[1]) { - let username = decodeURIComponent(match[1]); - const file = RocketChat.models.Uploads.findOneByUsername(username); - - if (file) { - res.setHeader('Content-Security-Policy', 'default-src \'none\''); + const match = /^\/([^?]*)/.exec(req.url); - return FileUpload.get(file, req, res); - } else { - res.setHeader('Content-Type', 'image/svg+xml'); - res.setHeader('Cache-Control', 'public, max-age=0'); - res.setHeader('Expires', '-1'); - res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT'); + if (match[1]) { + let username = decodeURIComponent(match[1]); + let file; - const reqModifiedHeader = req.headers['if-modified-since']; - - if (reqModifiedHeader) { - if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') { - res.writeHead(304); - res.end(); - return; - } - } - - const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; - - if (RocketChat.settings.get('UI_Use_Name_Avatar')) { - const user = RocketChat.models.Users.findOneByUsername(username, { - fields: { - name: 1 + if (username[0] !== '@') { + if (Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm) { + const user = RocketChat.models.Users.findOneByUsername(username); + if (user && user.services && user.services.sandstorm && user.services.sandstorm.picture) { + res.setHeader('Location', user.services.sandstorm.picture); + res.writeHead(302); + res.end(); + return; } - }); - - if (user && user.name) { - username = user.name; } + file = RocketChat.models.Uploads.findOneByName(`${ username }.avatar`); } - let color = ''; - let initials = ''; + if (file) { + res.setHeader('Content-Security-Policy', 'default-src \'none\''); - if (username === '?') { - color = '#000'; - initials = username; + return FileUpload.get(file, req, res); } else { - const position = username.length % colors.length; - - color = colors[position]; - username = username.replace(/[^A-Za-z0-9]/g, '.').replace(/\.+/g, '.').replace(/(^\.)|(\.$)/g, ''); + res.setHeader('Content-Type', 'image/svg+xml'); + res.setHeader('Cache-Control', 'public, max-age=0'); + res.setHeader('Expires', '-1'); + res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT'); + + const reqModifiedHeader = req.headers['if-modified-since']; + + if (reqModifiedHeader) { + if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') { + res.writeHead(304); + res.end(); + return; + } + } - const usernameParts = username.split('.'); + const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; - initials = usernameParts.length > 1 ? _.first(usernameParts)[0] + _.last(usernameParts)[0] : username.replace(/[^A-Za-z0-9]/g, '').substr(0, 2); - initials = initials.toUpperCase(); - } + if (RocketChat.settings.get('UI_Use_Name_Avatar')) { + const user = RocketChat.models.Users.findOneByUsername(username, { + fields: { + name: 1 + } + }); - const svg = `\n\n \n ${ initials }\n \n`; - res.write(svg); - res.end(); + if (user && user.name) { + username = user.name; + } + } - return; - } - } + let color = ''; + let initials = ''; - res.writeHead(404); - res.end(); - return; -} - -function getOldAvatar(params, req, res) { - let username = params.username.replace(/\.jpg$/, '').replace(/^@/, ''); - let file; - if (params.username[0] !== '@') { - if (Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm) { - const user = RocketChat.models.Users.findOneByUsername(username); - if (user && user.services && user.services.sandstorm && user.services.sandstorm.picture) { - res.setHeader('Location', user.services.sandstorm.picture); - res.writeHead(302); - res.end(); - return; - } - } + if (username === '?') { + color = '#000'; + initials = username; + } else { + const position = username.length % colors.length; - file = RocketChatFileAvatarInstance.getFileWithReadStream(encodeURIComponent(`${ username }.jpg`)); - } + color = colors[position]; + username = username.replace(/[^A-Za-z0-9]/g, '.').replace(/\.+/g, '.').replace(/(^\.)|(\.$)/g, ''); - res.setHeader('Content-Disposition', 'inline'); - if (!file) { - res.setHeader('Content-Type', 'image/svg+xml'); - res.setHeader('Cache-Control', 'public, max-age=0'); - res.setHeader('Expires', '-1'); - res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT'); + const usernameParts = username.split('.'); - const reqModifiedHeader = req.headers['if-modified-since']; + initials = usernameParts.length > 1 ? _.first(usernameParts)[0] + _.last(usernameParts)[0] : username.replace(/[^A-Za-z0-9]/g, '').substr(0, 2); + initials = initials.toUpperCase(); + } - if (reqModifiedHeader) { - if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') { - res.writeHead(304); + const svg = `\n\n \n ${ initials }\n \n`; + res.write(svg); res.end(); - return; - } - } - - const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; - - if (RocketChat.settings.get('UI_Use_Name_Avatar')) { - const user = RocketChat.models.Users.findOneByUsername(username, { - fields: { - name: 1 - } - }); - if (user && user.name) { - username = user.name; + return; } } - let color = ''; - let initials = ''; - - if (username === '?') { - color = '#000'; - initials = username; - } else { - const position = username.length % colors.length; - - color = colors[position]; - username = username.replace(/[^A-Za-z0-9]/g, '.').replace(/\.+/g, '.').replace(/(^\.)|(\.$)/g, ''); - - const usernameParts = username.split('.'); - - initials = usernameParts.length > 1 ? _.first(usernameParts)[0] + _.last(usernameParts)[0] : username.replace(/[^A-Za-z0-9]/g, '').substr(0, 2); - initials = initials.toUpperCase(); - } - - const svg = `\n\n \n ${ initials }\n \n`; - res.write(svg); + res.writeHead(404); res.end(); - return; - } - - const reqModifiedHeader = req.headers['if-modified-since']; - if (reqModifiedHeader) { - if (reqModifiedHeader === (file.uploadDate && file.uploadDate.toUTCString())) { - res.setHeader('Last-Modified', reqModifiedHeader); - res.writeHead(304); - res.end(); - return; - } - } - - res.setHeader('Cache-Control', 'public, max-age=0'); - res.setHeader('Expires', '-1'); - res.setHeader('Last-Modified', (file.uploadDate && file.uploadDate.toUTCString()) || new Date().toUTCString()); - res.setHeader('Content-Length', file.length); - - if (file.contentType) { - res.setHeader('Content-Type', file.contentType); - } else { - file.readStream.once('data', function(chunk) { - const fileType = getFileType(chunk); - if (fileType) { - return res.setHeader('Content-Type', fileType.mime); - } else { - return res.setHeader('Content-Type', 'image/jpeg'); - } - }); - } - - file.readStream.pipe(res); -} + })); +}); From b7fc7b68daa0b0c9bc81778f5aca60165a5c389d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 28 Apr 2017 17:46:05 -0300 Subject: [PATCH 010/102] Use new collection for avatars only --- lib/fileUpload.js | 10 +- .../client/lib/FileUploadAmazonS3.js | 11 +- .../client/lib/FileUploadGoogleStorage.js | 11 +- .../server/config/configFileUploadAmazonS3.js | 3 +- .../config/configFileUploadGoogleStorage.js | 3 +- .../rocketchat-lib/client/models/Avatars.js | 6 + packages/rocketchat-lib/package.js | 2 + .../rocketchat-lib/server/models/Avatars.js | 105 ++++++++++++++++++ .../server/models/Uploads.coffee | 51 --------- server/methods/setAvatarFromService.js | 9 +- server/startup/avatar.js | 2 +- 11 files changed, 150 insertions(+), 63 deletions(-) create mode 100644 packages/rocketchat-lib/client/models/Avatars.js create mode 100644 packages/rocketchat-lib/server/models/Avatars.js diff --git a/lib/fileUpload.js b/lib/fileUpload.js index f466da9ceb2d..93ae93979284 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -74,9 +74,9 @@ if (UploadFS) { }); Meteor.fileStoreAvatar = new UploadFS.store.GridFS({ - collection: RocketChat.models.Uploads.model, + collection: RocketChat.models.Avatars.model, name: 'rocketchat_uploads_avatar', - collectionName: 'rocketchat_uploads', + collectionName: 'rocketchat_avatars', // filter: new UploadFS.Filter({ // onCheck: FileUpload.validateFileUpload // }), @@ -110,12 +110,12 @@ if (UploadFS) { onFinishUpload(file) { // update file record to match user's username const user = RocketChat.models.Users.findOneById(file.userId); - const oldAvatar = RocketChat.models.Uploads.findOneByName(`${ user.username }.avatar`); + const oldAvatar = RocketChat.models.Avatars.findOneByName(`${ user.username }.avatar`); if (oldAvatar) { Meteor.fileStoreAvatar.delete(oldAvatar._id); - RocketChat.models.Uploads.deleteFile(oldAvatar._id); + RocketChat.models.Avatars.deleteFile(oldAvatar._id); } - RocketChat.models.Uploads.updateFileNameById(file._id, `${ user.username }.avatar`); + RocketChat.models.Avatars.updateFileNameById(file._id, `${ user.username }.avatar`); // console.log('upload finished ->', file); }, diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index df9fa58e3f56..7d8250b7ddfa 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -3,7 +3,12 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); - this.uploader = new Slingshot.Upload(directive, meta); + this.directive = directive; + const directives = { + 'upload': 'rocketchat-uploads', + 'avatar': 'rocketchat-avatars' + }; + this.uploader = new Slingshot.Upload(directives[directive], meta); } start(callback) { @@ -19,6 +24,10 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; + if (this.directive === 'avatar') { + return callback(null, file); + } + Meteor.call('sendFileMessage', this.meta.rid, 's3', file, () => { Meteor.setTimeout(() => { callback.call(this, null, file); diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js index b1db2b767d89..1ac7279e4bf6 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js @@ -3,7 +3,12 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); - this.uploader = new Slingshot.Upload(directive, { rid: meta.rid }); + this.directive = directive; + const directives = { + 'upload': 'rocketchat-uploads-gs', + 'avatar': 'rocketchat-avatars-gs' + }; + this.uploader = new Slingshot.Upload(directives[directive], { rid: meta.rid }); } start(callback) { @@ -19,6 +24,10 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileU file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; + if (this.directive === 'avatar') { + return callback(null, file); + } + Meteor.call('sendFileMessage', this.meta.rid, 'googleCloudStorage', file, () => { Meteor.setTimeout(() => { callback.call(this, null, file); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index 95d1dcd84347..a1844ba22e06 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -105,7 +105,8 @@ const configureSlingshot = _.debounce(() => { path } }; - RocketChat.models.Uploads.insertAvatarFileInit(user.username, this.userId, 's3', file, upload); + delete file.name; + RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 's3', file, upload); return path + user.username; } diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index b2d1c6b9a826..e7a2b7b4fc8e 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -121,7 +121,8 @@ const createGoogleStorageDirective = _.debounce(() => { path } }; - RocketChat.models.Uploads.insertAvatarFileInit(user.username, this.userId, 'googleCloudStorage', file, upload); + delete file.name; + RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'googleCloudStorage', file, upload); return path + user.username; } diff --git a/packages/rocketchat-lib/client/models/Avatars.js b/packages/rocketchat-lib/client/models/Avatars.js new file mode 100644 index 000000000000..bd8804e746c8 --- /dev/null +++ b/packages/rocketchat-lib/client/models/Avatars.js @@ -0,0 +1,6 @@ +RocketChat.models.Avatars = new class extends RocketChat.models._Base { + constructor() { + super(); + this._initModel('avatars'); + } +}; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 8902fce8f259..9ec626890d5e 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -104,6 +104,7 @@ Package.onUse(function(api) { // SERVER MODELS api.addFiles('server/models/_Base.js', 'server'); + api.addFiles('server/models/Avatars.js', 'server'); api.addFiles('server/models/Messages.coffee', 'server'); api.addFiles('server/models/Reports.js', 'server'); api.addFiles('server/models/Rooms.coffee', 'server'); @@ -195,6 +196,7 @@ Package.onUse(function(api) { // CLIENT MODELS api.addFiles('client/models/_Base.coffee', 'client'); + api.addFiles('client/models/Avatars.js', 'client'); api.addFiles('client/models/Uploads.coffee', 'client'); // CLIENT VIEWS diff --git a/packages/rocketchat-lib/server/models/Avatars.js b/packages/rocketchat-lib/server/models/Avatars.js new file mode 100644 index 000000000000..43c7b6be6af6 --- /dev/null +++ b/packages/rocketchat-lib/server/models/Avatars.js @@ -0,0 +1,105 @@ +RocketChat.models.Avatars = new class extends RocketChat.models._Base { + constructor() { + super('avatars'); + + 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 }); + } + } +}; diff --git a/packages/rocketchat-lib/server/models/Uploads.coffee b/packages/rocketchat-lib/server/models/Uploads.coffee index 6fc165a190c1..ff5edf5fd8d3 100644 --- a/packages/rocketchat-lib/server/models/Uploads.coffee +++ b/packages/rocketchat-lib/server/models/Uploads.coffee @@ -48,23 +48,6 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return file - insertAvatarFileInit: (name, userId, store, file, extra) -> - fileData = - name: "#{name}.avatar" - userId: userId - store: store - complete: false - uploading: true - progress: 0 - extension: s.strRightBack(file.name, '.') - uploadedAt: new Date() - - _.extend(fileData, file, extra); - - file = @insertOrUpsert fileData - - return file - updateFileComplete: (fileId, userId, file) -> if not fileId return @@ -88,42 +71,8 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base return result - # @TODO deprecated - updateFileCompleteByNameAndUserId: (name, userId, url) -> - if not name - return - - filter = - name: name - userId: userId - - update = - $set: - complete: true - uploading: false - progress: 1 - url: url - - if @model.direct?.update? - result = @model.direct.update filter, update - else - result = @update filter, update - - return result - - findOneByName: (name) -> - return @findOne name: name - deleteFile: (fileId) -> if @model.direct?.remove? return @model.direct.remove { _id: fileId } else return @remove { _id: fileId } - - updateFileNameById: (fileId, name) -> - filter = _id: fileId - update = $set: name: name - if @model.direct?.update? - return @model.direct.update filter, update - else - return @update filter, update diff --git a/server/methods/setAvatarFromService.js b/server/methods/setAvatarFromService.js index 8781e1c45022..6f974d1a35cc 100644 --- a/server/methods/setAvatarFromService.js +++ b/server/methods/setAvatarFromService.js @@ -39,7 +39,11 @@ Meteor.methods({ } const user = RocketChat.models.Users.findOneById(Meteor.userId()); - const fileSaved = RocketChat.models.Uploads.findOneById(file._id); + const fileSaved = RocketChat.models.Avatars.findOneById(file._id); + + if (!fileSaved) { + return; + } if (fileSaved.userId !== user._id) { // this file is not user's avatar @@ -51,7 +55,8 @@ Meteor.methods({ return true; } - RocketChat.models.Uploads.updateFileCompleteByNameAndUserId(`${ user.username }.avatar`, Meteor.userId(), file.url); + RocketChat.models.Avatars.updateFileCompleteByNameAndUserId(user.username, user._id, file.url); + return true; } }); diff --git a/server/startup/avatar.js b/server/startup/avatar.js index cf31fcc2366b..ae69381aff5e 100644 --- a/server/startup/avatar.js +++ b/server/startup/avatar.js @@ -62,7 +62,7 @@ Meteor.startup(function() { return; } } - file = RocketChat.models.Uploads.findOneByName(`${ username }.avatar`); + file = RocketChat.models.Avatars.findOneByName(username); } if (file) { From ce812e804b46a290836eba9777b3f0bd42804fb6 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 1 May 2017 12:17:15 -0300 Subject: [PATCH 011/102] Delete converted file --- .../client/lib/fileUpload.coffee | 187 ------------------ 1 file changed, 187 deletions(-) delete mode 100644 packages/rocketchat-ui/client/lib/fileUpload.coffee diff --git a/packages/rocketchat-ui/client/lib/fileUpload.coffee b/packages/rocketchat-ui/client/lib/fileUpload.coffee deleted file mode 100644 index 55507fe06189..000000000000 --- a/packages/rocketchat-ui/client/lib/fileUpload.coffee +++ /dev/null @@ -1,187 +0,0 @@ -readAsDataURL = (file, callback) -> - reader = new FileReader() - reader.onload = (ev) -> - callback ev.target.result, file - - reader.readAsDataURL file - -getUploadPreview = (file, callback) -> - # If greater then 10MB don't try and show a preview - if file.file.size > 10 * 1000000 - callback(file, null) - else - if file.file.type.indexOf('audio') > -1 or file.file.type.indexOf('video') > -1 or file.file.type.indexOf('image') > -1 - file.type = file.file.type.split('/')[0] - - readAsDataURL file.file, (content) -> - callback(file, content) - else - callback(file, null) - -formatBytes = (bytes, decimals) -> - if bytes == 0 - return '0 Bytes' - - k = 1000 - dm = decimals + 1 or 3 - - sizes = [ - 'Bytes' - 'KB' - 'MB' - 'GB' - 'TB' - 'PB' - ] - - i = Math.floor(Math.log(bytes) / Math.log(k)) - - parseFloat((bytes / k ** i).toFixed(dm)) + ' ' + sizes[i] - -readAsArrayBuffer = (file, callback) -> - reader = new FileReader() - reader.onload = (ev) -> - callback ev.target.result, file - - reader.readAsArrayBuffer file - - -@fileUpload = (files) -> - roomId = Session.get('openedRoom') - files = [].concat files - - consume = -> - file = files.pop() - if not file? - swal.close() - return - - if not RocketChat.fileUploadIsValidContentType file.file.type - swal - title: t('FileUpload_MediaType_NotAccepted') - text: file.file.type || "*.#{s.strRightBack(file.file.name, '.')}" - type: 'error' - timer: 3000 - return - - if file.file.size is 0 - swal - title: t('FileUpload_File_Empty') - type: 'error' - timer: 1000 - return - - getUploadPreview file, (file, preview) -> - text = '' - - if file.type is 'audio' - text = """ -
- -
-
- - -
- """ - else if file.type is 'video' - text = """ -
- -
-
- - -
- """ - else if file.type is 'image' - text = """ -
-
-
-
- - -
- """ - else - fileSize = formatBytes(file.file.size) - - text = """ -
-
#{Handlebars._escape(file.name)} - #{fileSize}
-
-
- - -
- """ - - swal - title: t('Upload_file_question') - text: text - showCancelButton: true - closeOnConfirm: false - closeOnCancel: false - confirmButtonText: t('Send') - cancelButtonText: t('Cancel') - html: true - , (isConfirm) -> - consume() - if isConfirm isnt true - return - - record = - name: document.getElementById('file-name').value or file.name or file.file.name - size: file.file.size - type: file.file.type - rid: roomId - description: document.getElementById('file-description').value - - upload = fileUploadHandler record, file.file - - uploading = Session.get('uploading') or [] - uploading.push - id: upload.id - name: upload.getFileName() - percentage: 0 - - Session.set 'uploading', uploading - - upload.onProgress = (progress) -> - uploading = Session.get('uploading') - - item = _.findWhere(uploading, {id: upload.id}) - if item? - item.percentage = Math.round(progress * 100) or 0 - Session.set 'uploading', uploading - - upload.start() - - Tracker.autorun (c) -> - cancel = Session.get "uploading-cancel-#{upload.id}" - if cancel - upload.stop() - c.stop() - - uploading = Session.get 'uploading' - if uploading? - item = _.findWhere(uploading, {id: upload.id}) - if item? - item.percentage = 0 - Session.set 'uploading', uploading - - Meteor.setTimeout -> - uploading = Session.get 'uploading' - if uploading? - item = _.findWhere(uploading, {id: upload.id}) - Session.set 'uploading', _.without(uploading, item) - , 1000 - - consume() From 1c72057feb6d2b02c3b2b02de99a56c3a6e992c7 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 1 May 2017 12:18:05 -0300 Subject: [PATCH 012/102] Avatar: Add support to FileSystem and various fixes --- lib/fileUpload.js | 31 +-- .../client/lib/FileUploadAmazonS3.js | 11 +- .../client/lib/FileUploadFileSystem.js | 50 ++--- .../client/lib/FileUploadGoogleStorage.js | 11 +- .../client/lib/FileUploadGridFS.js | 10 +- .../config/configFileUploadFileSystem.js | 125 +++++++++--- .../server/lib/FileUpload.js | 10 + .../.npm/package/npm-shrinkwrap.json | 188 +++--------------- .../rocketchat-ui/client/lib/fileUpload.js | 27 ++- 9 files changed, 175 insertions(+), 288 deletions(-) diff --git a/lib/fileUpload.js b/lib/fileUpload.js index 93ae93979284..782015a73cd5 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -80,42 +80,17 @@ if (UploadFS) { // filter: new UploadFS.Filter({ // onCheck: FileUpload.validateFileUpload // }), - transformWrite(readStream, writeStream, fileId, file) { - if (RocketChatFile.enabled === false || !/^image\/.+/.test(file.type)) { - return readStream.pipe(writeStream); - } - - let stream = undefined; - - const identify = function(err, data) { - if (err) { - return stream.pipe(writeStream); - } - - file.identify = { - format: data.format, - size: data.size - }; - - if (data.Orientation && !['', 'Unknown', 'Undefined'].includes(data.Orientation)) { - RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); - } else { - stream.pipe(writeStream); - } - }; - - stream = RocketChatFile.gm(readStream).identify(identify).stream(); - }, + transformWrite: FileUpload.avatarTransformWrite, onFinishUpload(file) { // update file record to match user's username const user = RocketChat.models.Users.findOneById(file.userId); - const oldAvatar = RocketChat.models.Avatars.findOneByName(`${ user.username }.avatar`); + const oldAvatar = RocketChat.models.Avatars.findOneByName(user.username); if (oldAvatar) { Meteor.fileStoreAvatar.delete(oldAvatar._id); RocketChat.models.Avatars.deleteFile(oldAvatar._id); } - RocketChat.models.Avatars.updateFileNameById(file._id, `${ user.username }.avatar`); + RocketChat.models.Avatars.updateFileNameById(file._id, user.username); // console.log('upload finished ->', file); }, diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index 7d8250b7ddfa..0c6f3efd9054 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -3,7 +3,6 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); - this.directive = directive; const directives = { 'upload': 'rocketchat-uploads', 'avatar': 'rocketchat-avatars' @@ -24,15 +23,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; - if (this.directive === 'avatar') { - return callback(null, file); - } - - Meteor.call('sendFileMessage', this.meta.rid, 's3', file, () => { - Meteor.setTimeout(() => { - callback.call(this, null, file); - }, 2000); - }); + return callback(null, file, 's3'); } }); diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js index d19c5c03141f..025a3cf78103 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js @@ -1,4 +1,4 @@ -/* globals FileUploadBase, UploadFS, FileUpload:true, FileSystemStore:true */ +/* globals FileUploadBase, UploadFS, FileUpload:true, FileSystemStore:true, FileSystemStoreAvatar:true */ FileSystemStore = new UploadFS.store.Local({ collection: RocketChat.models.Uploads.model, @@ -8,51 +8,41 @@ FileSystemStore = new UploadFS.store.Local({ }) }); +FileSystemStoreAvatar = new UploadFS.store.Local({ + collection: RocketChat.models.Avatars.model, + name: 'fileSystemAvatar', + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }) +}); + FileUpload.FileSystem = class FileUploadFileSystem extends FileUploadBase { - constructor(meta, file) { + constructor(directive, meta, file) { super(meta, file); + console.log('filesystem', {directive, meta, file}); + this.store = directive === 'avatar' ? FileSystemStoreAvatar : FileSystemStore; + } + + start(callback) { this.handler = new UploadFS.Uploader({ - store: FileSystemStore, - data: file, - file: meta, + store: this.store, + data: this.file, + file: this.meta, onError: (err) => { - const uploading = Session.get('uploading'); - if (uploading != null) { - const item = _.findWhere(uploading, { - id: this.id - }); - if (item != null) { - item.error = err.reason; - item.percentage = 0; - } - return Session.set('uploading', uploading); - } + return callback(err); }, onComplete: (fileData) => { const file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); - - Meteor.call('sendFileMessage', this.meta.rid, null, file, () => { - Meteor.setTimeout(() => { - const uploading = Session.get('uploading'); - if (uploading != null) { - const item = _.findWhere(uploading, { - id: this.id - }); - return Session.set('uploading', _.without(uploading, item)); - } - }, 2000); - }); + return callback(null, file, 'fs'); } }); this.handler.onProgress = (file, progress) => { this.onProgress(progress); }; - } - start() { return this.handler.start(); } diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js index 1ac7279e4bf6..b4b047c42b8b 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js @@ -3,7 +3,6 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); - this.directive = directive; const directives = { 'upload': 'rocketchat-uploads-gs', 'avatar': 'rocketchat-avatars-gs' @@ -24,15 +23,7 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileU file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; - if (this.directive === 'avatar') { - return callback(null, file); - } - - Meteor.call('sendFileMessage', this.meta.rid, 'googleCloudStorage', file, () => { - Meteor.setTimeout(() => { - callback.call(this, null, file); - }, 2000); - }); + return callback(null, file, 'googleCloudStorage'); } }); diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js index d5236b1c971c..6b8c3755b99e 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js @@ -2,7 +2,6 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { constructor(directive, meta, file) { super(meta, file); - this.directive = directive; this.store = directive === 'avatar' ? Meteor.fileStoreAvatar : Meteor.fileStore; } @@ -16,15 +15,8 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { }, onComplete: (fileData) => { const file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); - if (this.directive === 'avatar') { - return callback(null, file); - } file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); - Meteor.call('sendFileMessage', this.meta.rid, null, file, () => { - Meteor.setTimeout(() => { - return callback(null, file); - }, 2000); - }); + return callback(null, file, 'gridfs'); } }); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js index f97534577abf..0ba17089bfd0 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js @@ -1,47 +1,70 @@ -/* globals FileSystemStore:true, FileUpload, UploadFS, RocketChatFile */ +/* globals FileSystemStore:true, FileUpload, UploadFS, RocketChatFile, FileSystemStoreAvatar */ -const storeName = 'fileSystem'; +const transformWrite = function(readStream, writeStream, fileId, file) { + if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) { + return readStream.pipe(writeStream); + } + + let stream = undefined; + + const identify = function(err, data) { + if (err != null) { + return stream.pipe(writeStream); + } + + file.identify = { + format: data.format, + size: data.size + }; + + if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) { + return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); + } else { + return stream.pipe(writeStream); + } + }; + + stream = RocketChatFile.gm(readStream).identify(identify).stream(); + return; +}; FileSystemStore = null; +FileSystemStoreAvatar = null; const createFileSystemStore = _.debounce(function() { const stores = UploadFS.getStores(); - if (stores[storeName]) { - delete stores[storeName]; - } + delete stores.fileSystem; + delete stores.fileSystemAvatar; + FileSystemStore = new UploadFS.store.Local({ - collection: RocketChat.models.Uploads.model, - name: storeName, path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', + collection: RocketChat.models.Uploads.model, filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload }), - transformWrite(readStream, writeStream, fileId, file) { - if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) { - return readStream.pipe(writeStream); - } - - let stream = undefined; - - const identify = function(err, data) { - if (err != null) { - return stream.pipe(writeStream); - } - - file.identify = { - format: data.format, - size: data.size - }; + name: 'fileSystem', + transformWrite + }); - if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) { - return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); - } else { - return stream.pipe(writeStream); + FileSystemStoreAvatar = new UploadFS.store.Local({ + path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', + collection: RocketChat.models.Avatars.model, + name: 'fileSystemAvatar', + transformWrite: FileUpload.avatarTransformWrite, + onFinishUpload(file) { + // update file record to match user's username + const user = RocketChat.models.Users.findOneById(file.userId); + const oldAvatar = RocketChat.models.Avatars.findOneByName(user.username); + if (oldAvatar) { + try { + FileSystemStoreAvatar.delete(oldAvatar._id); + RocketChat.models.Avatars.deleteFile(oldAvatar._id); + } catch (e) { + console.error(e); } - }; - - stream = RocketChatFile.gm(readStream).identify(identify).stream(); - return; + } + RocketChat.models.Avatars.updateFileNameById(file._id, user.username); + // console.log('upload finished ->', file); } }); }, 500); @@ -50,7 +73,7 @@ RocketChat.settings.get('FileUpload_FileSystemPath', createFileSystemStore); const fs = Npm.require('fs'); -FileUpload.addHandler(storeName, { +FileUpload.addHandler('fileSystem', { get(file, req, res) { const filePath = FileSystemStore.getFilePath(file._id, file); @@ -77,3 +100,41 @@ FileUpload.addHandler(storeName, { return FileSystemStore.delete(file._id); } }); + +FileUpload.addHandler('fileSystemAvatar', { + get(file, req, res) { + const reqModifiedHeader = req.headers['if-modified-since']; + if (reqModifiedHeader) { + if (reqModifiedHeader === (file.uploadedAt && file.uploadedAt.toUTCString())) { + res.setHeader('Last-Modified', reqModifiedHeader); + res.writeHead(304); + res.end(); + return; + } + } + + const filePath = FileSystemStoreAvatar.getFilePath(file._id, file); + + try { + const stat = Meteor.wrapAsync(fs.stat)(filePath); + + if (stat && stat.isFile()) { + file = FileUpload.addExtensionTo(file); + res.setHeader('Content-Disposition', 'inline'); + res.setHeader('Last-Modified', file.uploadedAt.toUTCString()); + res.setHeader('Content-Type', file.type); + res.setHeader('Content-Length', file.size); + + FileSystemStoreAvatar.getReadStream(file._id, file).pipe(res); + } + } catch (e) { + res.writeHead(404); + res.end(); + return; + } + }, + + delete(file) { + return FileSystemStoreAvatar.delete(file._id); + } +}); diff --git a/packages/rocketchat-file-upload/server/lib/FileUpload.js b/packages/rocketchat-file-upload/server/lib/FileUpload.js index 05ec504af3cc..7e207eef10f7 100644 --- a/packages/rocketchat-file-upload/server/lib/FileUpload.js +++ b/packages/rocketchat-file-upload/server/lib/FileUpload.js @@ -41,3 +41,13 @@ FileUpload.addExtensionTo = function(file) { return file; }; + +FileUpload.avatarTransformWrite = function(readStream, writeStream, fileId, file) { + if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) { + return readStream.pipe(writeStream); + } + const height = RocketChat.settings.get('Accounts_AvatarSize'); + const width = height; + return RocketChatFile.gm(readStream).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream); +}; + diff --git a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json index a34613259049..c931564c628f 100644 --- a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json @@ -1,8 +1,8 @@ { "dependencies": { "ajv": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.7.tgz", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "from": "ajv@>=4.9.1 <5.0.0" }, "ansi-regex": { @@ -30,11 +30,6 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "from": "arrify@>=1.0.1 <2.0.0" }, - "ascli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", - "from": "ascli@>=1.0.0 <2.0.0" - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -46,8 +41,8 @@ "from": "assert-plus@>=0.2.0 <0.3.0" }, "async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz", "from": "async@>=2.1.2 <3.0.0" }, "asynckit": { @@ -65,11 +60,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", "from": "aws4@>=1.2.1 <2.0.0" }, - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "from": "balanced-match@>=0.4.1 <0.5.0" - }, "base64url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", @@ -85,11 +75,6 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "from": "boom@>=2.0.0 <3.0.0" }, - "brace-expansion": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "from": "brace-expansion@>=1.0.0 <2.0.0" - }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -100,16 +85,6 @@ "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", "from": "buffer-shims@>=1.0.0 <1.1.0" }, - "bytebuffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", - "from": "bytebuffer@>=5.0.0 <6.0.0" - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "from": "camelcase@>=2.0.1 <3.0.0" - }, "capture-stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", @@ -125,26 +100,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "from": "chalk@>=1.1.1 <2.0.0" }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "from": "cliui@>=3.0.3 <4.0.0" - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "from": "co@>=4.6.0 <5.0.0" }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "from": "code-point-at@>=1.0.0 <2.0.0" - }, - "colour": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", - "from": "colour@>=0.7.1 <0.8.0" - }, "combined-stream": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", @@ -155,11 +115,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "from": "commander@>=2.9.0 <3.0.0" }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "from": "concat-map@0.0.1" - }, "concat-stream": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", @@ -192,11 +147,6 @@ } } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "from": "decamelize@>=1.1.1 <2.0.0" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -233,8 +183,8 @@ "from": "escape-string-regexp@>=1.0.2 <2.0.0" }, "extend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "from": "extend@>=3.0.0 <4.0.0" }, "extsprintf": { @@ -252,11 +202,6 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "from": "form-data@>=2.1.1 <2.2.0" }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "from": "fs.realpath@>=1.0.0 <2.0.0" - }, "generate-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", @@ -279,11 +224,6 @@ } } }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "from": "glob@>=7.0.5 <8.0.0" - }, "google-auth-library": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.10.0.tgz", @@ -322,8 +262,8 @@ "from": "graceful-readlink@>=1.0.0" }, "grpc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.2.4.tgz", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.3.0.tgz", "from": "grpc@>=1.1.0 <2.0.0", "dependencies": { "node-pre-gyp": { @@ -378,8 +318,8 @@ "from": "npmlog@>=4.0.2 <5.0.0", "dependencies": { "are-we-there-yet": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "from": "are-we-there-yet@>=1.1.2 <1.2.0", "dependencies": { "delegates": { @@ -390,7 +330,7 @@ "readable-stream": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", - "from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0", + "from": "readable-stream@>=2.0.6 <3.0.0", "dependencies": { "buffer-shims": { "version": "1.0.0", @@ -437,8 +377,8 @@ "from": "console-control-strings@>=1.1.0 <1.2.0" }, "gauge": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "from": "gauge@>=2.7.1 <2.8.0", "dependencies": { "aproba": { @@ -598,8 +538,8 @@ "from": "har-validator@>=4.2.1 <4.3.0", "dependencies": { "ajv": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.6.tgz", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.7.tgz", "from": "ajv@>=4.9.1 <5.0.0", "dependencies": { "co": { @@ -693,8 +633,8 @@ } }, "sshpk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.11.0.tgz", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", "from": "sshpk@>=1.7.0 <2.0.0", "dependencies": { "asn1": { @@ -723,8 +663,8 @@ "from": "ecc-jsbn@>=0.1.1 <0.2.0" }, "getpass": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "from": "getpass@>=0.1.1 <0.2.0" }, "jodid25519": { @@ -939,14 +879,14 @@ "from": "tar-pack@>=3.4.0 <4.0.0", "dependencies": { "debug": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.3.tgz", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.5.tgz", "from": "debug@>=2.2.0 <3.0.0", "dependencies": { "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "from": "ms@0.7.2" + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "from": "ms@0.7.3" } } }, @@ -1006,7 +946,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "from": "once@>=1.0.0 <2.0.0", + "from": "once@>=1.3.3 <2.0.0", "dependencies": { "wrappy": { "version": "1.0.2", @@ -1103,31 +1043,16 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "from": "http-signature@>=1.1.0 <1.2.0" }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "from": "inflight@>=1.0.4 <2.0.0" - }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "from": "inherits@>=2.0.3 <3.0.0" }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "from": "invert-kv@>=1.0.0 <2.0.0" - }, "is": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz", "from": "is@>=3.0.1 <4.0.0" }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0" - }, "is-my-json-valid": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", @@ -1215,11 +1140,6 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", "from": "jws@>=3.1.4 <4.0.0" }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "from": "lcid@>=1.0.0 <2.0.0" - }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -1238,7 +1158,7 @@ "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "from": "long@>=3.0.0 <4.0.0" + "from": "long@>=3.2.0 <4.0.0" }, "methmeth": { "version": "1.1.0", @@ -1260,11 +1180,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "from": "mime-types@>=2.1.7 <2.2.0" }, - "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", - "from": "minimatch@>=3.0.2 <4.0.0" - }, "modelo": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.0.tgz", @@ -1280,11 +1195,6 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", "from": "node-forge@>=0.7.1 <0.8.0" }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "from": "number-is-nan@>=1.0.0 <2.0.0" - }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -1300,21 +1210,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "from": "once@>=1.3.0 <1.4.0" }, - "optjs": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", - "from": "optjs@>=3.2.2 <3.3.0" - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "from": "os-locale@>=1.4.0 <2.0.0" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "from": "path-is-absolute@>=1.0.0 <2.0.0" - }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", @@ -1341,9 +1236,9 @@ "from": "propprop@>=0.3.1 <0.4.0" }, "protobufjs": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.2.tgz", - "from": "protobufjs@>=5.0.0 <6.0.0" + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.7.3.tgz", + "from": "protobufjs@>=6.7.0 <7.0.0" }, "punycode": { "version": "1.4.1", @@ -1451,11 +1346,6 @@ "resolved": "https://registry.npmjs.org/string-format-obj/-/string-format-obj-1.1.0.tgz", "from": "string-format-obj@>=1.1.0 <2.0.0" }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "from": "string-width@>=1.0.1 <2.0.0" - }, "string_decoder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz", @@ -1521,16 +1411,6 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "from": "verror@1.3.6" }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "from": "window-size@>=0.1.4 <0.2.0" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "from": "wrap-ansi@>=2.0.0 <3.0.0" - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1540,16 +1420,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "from": "xtend@>=4.0.0 <5.0.0" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "from": "y18n@>=3.2.0 <4.0.0" - }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "from": "yargs@>=3.10.0 <4.0.0" } } } diff --git a/packages/rocketchat-ui/client/lib/fileUpload.js b/packages/rocketchat-ui/client/lib/fileUpload.js index d7ef1590a437..20d2007951c0 100644 --- a/packages/rocketchat-ui/client/lib/fileUpload.js +++ b/packages/rocketchat-ui/client/lib/fileUpload.js @@ -145,7 +145,7 @@ fileUpload = function(filesToUpload) { description: document.getElementById('file-description').value }; - const upload = fileUploadHandler('rocketchat-uploads-gs', record, file.file); + const upload = fileUploadHandler('upload', record, file.file); let uploading = Session.get('uploading') || []; uploading.push({ @@ -166,7 +166,7 @@ fileUpload = function(filesToUpload) { } }; - upload.start(function(error, file) { + upload.start(function(error, file, storage) { if (error) { let uploading = Session.get('uploading'); if (!Array.isArray(uploading)) { @@ -187,14 +187,21 @@ fileUpload = function(filesToUpload) { Session.set('uploading', uploading); return; - } else if (file) { - const uploading = Session.get('uploading'); - if (uploading !== null) { - const item = _.findWhere(uploading, { - id: this.id - }); - return Session.set('uploading', _.without(uploading, item)); - } + } + + + if (file) { + Meteor.call('sendFileMessage', roomId, storage, file, () => { + Meteor.setTimeout(() => { + const uploading = Session.get('uploading'); + if (uploading !== null) { + const item = _.findWhere(uploading, { + id: this.id + }); + return Session.set('uploading', _.without(uploading, item)); + } + }, 2000); + }); } }); From 536fba74d7129cc4149f56cfb4408622b858f369 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 1 May 2017 18:31:09 -0300 Subject: [PATCH 013/102] Avatar: Refactor some code, allow set avatar from server for FS and GridFS --- lib/fileUpload.js | 4 +- .../client/lib/FileUploadFileSystem.js | 4 +- .../server/config/configFileUploadAmazonS3.js | 8 +- .../config/configFileUploadFileSystem.js | 168 +++++++++--------- .../config/configFileUploadGoogleStorage.js | 12 +- .../server/config/configFileUploadGridFS.js | 39 ++-- .../server/lib/FileUpload.js | 126 +++++++++---- .../server/functions/deleteMessage.js | 2 +- .../server/functions/deleteUser.js | 3 +- .../server/functions/setUserAvatar.js | 15 +- server/methods/deleteFileMessage.js | 2 +- server/methods/resetAvatar.js | 2 +- server/startup/initialData.js | 19 +- 13 files changed, 250 insertions(+), 154 deletions(-) diff --git a/lib/fileUpload.js b/lib/fileUpload.js index 782015a73cd5..f5eafb6e0985 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -11,7 +11,7 @@ if (UploadFS) { Meteor.fileStore = new UploadFS.store.GridFS({ collection: RocketChat.models.Uploads.model, - name: 'rocketchat_uploads', + name: 'GridFS:Uploads', collectionName: 'rocketchat_uploads', filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload @@ -75,7 +75,7 @@ if (UploadFS) { Meteor.fileStoreAvatar = new UploadFS.store.GridFS({ collection: RocketChat.models.Avatars.model, - name: 'rocketchat_uploads_avatar', + name: 'GridFS:Avatars', collectionName: 'rocketchat_avatars', // filter: new UploadFS.Filter({ // onCheck: FileUpload.validateFileUpload diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js index 025a3cf78103..10bb44cf74fb 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js @@ -2,7 +2,7 @@ FileSystemStore = new UploadFS.store.Local({ collection: RocketChat.models.Uploads.model, - name: 'fileSystem', + name: 'FileSystem:Uploads', filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload }) @@ -10,7 +10,7 @@ FileSystemStore = new UploadFS.store.Local({ FileSystemStoreAvatar = new UploadFS.store.Local({ collection: RocketChat.models.Avatars.model, - name: 'fileSystemAvatar', + name: 'FileSystem:Avatars', filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload }) diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index a1844ba22e06..fe18ecaacebe 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -1,5 +1,6 @@ /* globals Slingshot, FileUpload, AWS */ -const crypto = Npm.require('crypto'); +import crypto from 'crypto'; +import { FileUploadClass } from '../lib/FileUpload'; let S3accessKey; let S3secretKey; @@ -16,7 +17,9 @@ const generateURL = function(file) { return `${ file.url }?AWSAccessKeyId=${ encodeURIComponent(S3accessKey) }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`; }; -FileUpload.addHandler('s3', { +new FileUploadClass({ + name: 'S3:Uploads', + get(file, req, res) { const fileUrl = generateURL(file); @@ -26,6 +29,7 @@ FileUpload.addHandler('s3', { } res.end(); }, + delete(file) { const s3 = new AWS.S3(); const request = s3.deleteObject({ diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js index 0ba17089bfd0..9ffdb26a7a9c 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js @@ -1,81 +1,20 @@ -/* globals FileSystemStore:true, FileUpload, UploadFS, RocketChatFile, FileSystemStoreAvatar */ +/* globals FileUpload, UploadFS, RocketChatFile */ -const transformWrite = function(readStream, writeStream, fileId, file) { - if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) { - return readStream.pipe(writeStream); - } +import fs from 'fs'; +import { FileUploadClass } from '../lib/FileUpload'; - let stream = undefined; - - const identify = function(err, data) { - if (err != null) { - return stream.pipe(writeStream); - } - - file.identify = { - format: data.format, - size: data.size - }; +const insert = function(file, stream, cb) { + const fileId = this.store.create(file); - if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) { - return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); - } else { - return stream.pipe(writeStream); - } - }; - - stream = RocketChatFile.gm(readStream).identify(identify).stream(); - return; + this.store.write(stream, fileId, cb); }; -FileSystemStore = null; -FileSystemStoreAvatar = null; +const FileSystemUploads = new FileUploadClass({ + name: 'FileSystem:Uploads', + // store setted bellow -const createFileSystemStore = _.debounce(function() { - const stores = UploadFS.getStores(); - delete stores.fileSystem; - delete stores.fileSystemAvatar; - - FileSystemStore = new UploadFS.store.Local({ - path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', - collection: RocketChat.models.Uploads.model, - filter: new UploadFS.Filter({ - onCheck: FileUpload.validateFileUpload - }), - name: 'fileSystem', - transformWrite - }); - - FileSystemStoreAvatar = new UploadFS.store.Local({ - path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', - collection: RocketChat.models.Avatars.model, - name: 'fileSystemAvatar', - transformWrite: FileUpload.avatarTransformWrite, - onFinishUpload(file) { - // update file record to match user's username - const user = RocketChat.models.Users.findOneById(file.userId); - const oldAvatar = RocketChat.models.Avatars.findOneByName(user.username); - if (oldAvatar) { - try { - FileSystemStoreAvatar.delete(oldAvatar._id); - RocketChat.models.Avatars.deleteFile(oldAvatar._id); - } catch (e) { - console.error(e); - } - } - RocketChat.models.Avatars.updateFileNameById(file._id, user.username); - // console.log('upload finished ->', file); - } - }); -}, 500); - -RocketChat.settings.get('FileUpload_FileSystemPath', createFileSystemStore); - -const fs = Npm.require('fs'); - -FileUpload.addHandler('fileSystem', { get(file, req, res) { - const filePath = FileSystemStore.getFilePath(file._id, file); + const filePath = this.store.getFilePath(file._id, file); try { const stat = Meteor.wrapAsync(fs.stat)(filePath); @@ -87,7 +26,7 @@ FileUpload.addHandler('fileSystem', { res.setHeader('Content-Type', file.type); res.setHeader('Content-Length', file.size); - FileSystemStore.getReadStream(file._id, file).pipe(res); + this.store.getReadStream(file._id, file).pipe(res); } } catch (e) { res.writeHead(404); @@ -96,12 +35,13 @@ FileUpload.addHandler('fileSystem', { } }, - delete(file) { - return FileSystemStore.delete(file._id); - } + insert }); -FileUpload.addHandler('fileSystemAvatar', { +const FileSystemAvatars = new FileUploadClass({ + name: 'FileSystem:Avatars', + // store setted bellow + get(file, req, res) { const reqModifiedHeader = req.headers['if-modified-since']; if (reqModifiedHeader) { @@ -113,7 +53,7 @@ FileUpload.addHandler('fileSystemAvatar', { } } - const filePath = FileSystemStoreAvatar.getFilePath(file._id, file); + const filePath = this.store.getFilePath(file._id, file); try { const stat = Meteor.wrapAsync(fs.stat)(filePath); @@ -125,7 +65,7 @@ FileUpload.addHandler('fileSystemAvatar', { res.setHeader('Content-Type', file.type); res.setHeader('Content-Length', file.size); - FileSystemStoreAvatar.getReadStream(file._id, file).pipe(res); + this.store.getReadStream(file._id, file).pipe(res); } } catch (e) { res.writeHead(404); @@ -134,7 +74,73 @@ FileUpload.addHandler('fileSystemAvatar', { } }, - delete(file) { - return FileSystemStoreAvatar.delete(file._id); - } + insert }); + + +const transformWrite = function(readStream, writeStream, fileId, file) { + if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) { + return readStream.pipe(writeStream); + } + + let stream = undefined; + + const identify = function(err, data) { + if (err != null) { + return stream.pipe(writeStream); + } + + file.identify = { + format: data.format, + size: data.size + }; + + if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) { + return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); + } else { + return stream.pipe(writeStream); + } + }; + + stream = RocketChatFile.gm(readStream).identify(identify).stream(); + return; +}; + +const createFileSystemStore = _.debounce(function() { + const stores = UploadFS.getStores(); + delete stores['FileSystem:Uploads']; + delete stores['FileSystem:Avatars']; + + FileSystemUploads.store = new UploadFS.store.Local({ + path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', + collection: FileSystemUploads.model.model, + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }), + name: FileSystemUploads.name, + transformWrite + }); + + FileSystemAvatars.store = new UploadFS.store.Local({ + path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', + collection: FileSystemAvatars.model.model, + name: FileSystemAvatars.name, + transformWrite: FileUpload.avatarTransformWrite, + onFinishUpload(file) { + // update file record to match user's username + const user = RocketChat.models.Users.findOneById(file.userId); + const oldAvatar = FileSystemAvatars.model.findOneByName(user.username); + if (oldAvatar) { + try { + FileSystemAvatars.deleteById(oldAvatar._id); + } catch (e) { + console.error(e); + } + } + FileSystemAvatars.model.updateFileNameById(file._id, user.username); + // console.log('upload finished ->', file); + } + }); +}, 500); + +RocketChat.settings.get('FileUpload_FileSystemPath', createFileSystemStore); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index e7a2b7b4fc8e..69e0b514ba0c 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -1,6 +1,7 @@ -/* globals FileUpload, Slingshot, SystemLogger */ +/* globals FileUpload, Slingshot */ -const crypto = Npm.require('crypto'); +import crypto from 'crypto'; +import { FileUploadClass } from '../lib/FileUpload'; function generateUrlParts({ file }) { const accessId = RocketChat.settings.get('FileUpload_GoogleStorage_AccessId'); @@ -62,7 +63,9 @@ function createDirective(directiveName, { key, bucket, accessId, secret }) { } } -FileUpload.addHandler('googleCloudStorage', { +new FileUploadClass({ + name: 'googleCloudStorage', + get(file, req, res) { const fileUrl = generateGetURL({ file }); @@ -72,12 +75,15 @@ FileUpload.addHandler('googleCloudStorage', { } res.end(); }, + delete(file) { if (!file || !file.googleCloudStorage) { console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, the file and googleCloudStorage properties are not defined.'); return; } + // RocketChat.models.Uploads.deleteFile(file._id); + const url = generateDeleteUrl({ file }); if (_.isEmpty(url)) { diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js index fad40155182d..4e7553f642e2 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js @@ -1,7 +1,10 @@ /* globals FileUpload, UploadFS */ -const stream = Npm.require('stream'); -const zlib = Npm.require('zlib'); -const util = Npm.require('util'); +import stream from 'stream'; +import zlib from 'zlib'; +import util from 'util'; + +import { FileUploadClass } from '../lib/FileUpload'; + const logger = new Logger('FileUpload'); function ExtractRange(options) { @@ -124,7 +127,18 @@ const readFromGridFS = function(storeName, fileId, file, headers, req, res) { } }; -FileUpload.addHandler('rocketchat_uploads', { +const insert = function(file, stream, cb) { + const fileId = this.store.create(file); + + this.store.write(stream, fileId, cb); +}; + +new FileUploadClass({ + name: 'GridFS:Uploads', + getStore() { + return Meteor.fileStore; + }, + get(file, req, res) { file = FileUpload.addExtensionTo(file); const headers = { @@ -135,12 +149,16 @@ FileUpload.addHandler('rocketchat_uploads', { }; return readFromGridFS(file.store, file._id, file, headers, req, res); }, - delete(file) { - return Meteor.fileStore.delete(file._id); - } + + insert }); -FileUpload.addHandler('rocketchat_uploads_avatar', { +new FileUploadClass({ + name: 'GridFS:Avatars', + getStore() { + return Meteor.fileStoreAvatar + }, + get(file, req, res) { const reqModifiedHeader = req.headers['if-modified-since']; if (reqModifiedHeader) { @@ -163,7 +181,6 @@ FileUpload.addHandler('rocketchat_uploads_avatar', { }; return readFromGridFS(file.store, file._id, file, headers, req, res); }, - delete(file) { - return Meteor.fileStore.delete(file._id); - } + + insert }); diff --git a/packages/rocketchat-file-upload/server/lib/FileUpload.js b/packages/rocketchat-file-upload/server/lib/FileUpload.js index 7e207eef10f7..f606d0ad0d4e 100644 --- a/packages/rocketchat-file-upload/server/lib/FileUpload.js +++ b/packages/rocketchat-file-upload/server/lib/FileUpload.js @@ -1,53 +1,109 @@ -/* globals FileUpload:true */ import mime from 'mime-type/with-db'; -FileUpload.handlers = {}; +Object.assign(FileUpload, { + handlers: {}, -FileUpload.addHandler = function(store, handler) { - this.handlers[store] = handler; -}; + avatarTransformWrite(readStream, writeStream/*, fileId, file*/) { + if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) { + return readStream.pipe(writeStream); + } + const height = RocketChat.settings.get('Accounts_AvatarSize'); + const width = height; + return RocketChatFile.gm(readStream).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream); + }, -FileUpload.delete = function(fileId) { - const file = RocketChat.models.Uploads.findOneById(fileId); + addExtensionTo(file) { + if (mime.lookup(file.name) === file.type) { + return file; + } - if (!file) { - return; + const ext = mime.extension(file.type); + if (ext && false === new RegExp(`\.${ ext }$`, 'i').test(file.name)) { + file.name = `${ file.name }.${ ext }`; + } + + return file; + }, + + getStore(modelName) { + const storageType = RocketChat.settings.get('FileUpload_Storage_Type'); + const handlerName = `${ storageType }:${ modelName }`; + + if (this.handlers[handlerName] == null) { + console.error(`Upload handler "${ handlerName }" does not exists`); + } + + return this.handlers[handlerName]; + }, + + get(file, req, res, next) { + if (file.store && this.handlers && this.handlers[file.store] && this.handlers[file.store].get) { + this.handlers[file.store].get(file, req, res, next); + } else { + res.writeHead(404); + res.end(); + return; + } } +}); + - this.handlers[file.store].delete(file); +export class FileUploadClass { + constructor({ name, model, store, get, insert, getStore }) { + this.name = name; + this.model = model || this.getModelFromName(); + this._store = store; + this.get = get; + this.insert = insert; - return RocketChat.models.Uploads.deleteFile(file._id); -}; + if (getStore) { + this.getStore = getStore; + } -FileUpload.get = function(file, req, res, next) { - if (file.store && this.handlers && this.handlers[file.store] && this.handlers[file.store].get) { - this.handlers[file.store].get.call(this, file, req, res, next); - } else { - res.writeHead(404); - res.end(); - return; + FileUpload.handlers[name] = this; } -}; -FileUpload.addExtensionTo = function(file) { - if (mime.lookup(file.name) === file.type) { - return file; + getStore() { + return this._store; } - const ext = mime.extension(file.type); - if (ext && false === new RegExp(`\.${ ext }$`, 'i').test(file.name)) { - file.name = `${ file.name }.${ ext }`; + get store() { + return this.getStore(); } - return file; -}; + set store(store) { + this._store = store; + } -FileUpload.avatarTransformWrite = function(readStream, writeStream, fileId, file) { - if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) { - return readStream.pipe(writeStream); + getModelFromName() { + return RocketChat.models[this.name.split(':')[1]]; } - const height = RocketChat.settings.get('Accounts_AvatarSize'); - const width = height; - return RocketChatFile.gm(readStream).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream); -}; + delete(fileId) { + if (this.store && this.store.delete) { + this.store.delete(fileId); + } + + return this.model.deleteFile(fileId); + } + + deleteById(fileId) { + const file = this.model.findOneById(fileId); + + if (!file) { + return; + } + + return this.delete(file._id); + } + + deleteByName(fileName) { + const file = this.model.findOneByName(fileName); + + if (!file) { + return; + } + + return this.delete(file._id); + } +} diff --git a/packages/rocketchat-lib/server/functions/deleteMessage.js b/packages/rocketchat-lib/server/functions/deleteMessage.js index 7a190964be11..50fd5999864e 100644 --- a/packages/rocketchat-lib/server/functions/deleteMessage.js +++ b/packages/rocketchat-lib/server/functions/deleteMessage.js @@ -21,7 +21,7 @@ RocketChat.deleteMessage = function(message, user) { } if (message.file && message.file._id) { - FileUpload.delete(message.file._id); + FileUpload.getStore('Uploads').deleteById(message.file._id); } Meteor.defer(function() { diff --git a/packages/rocketchat-lib/server/functions/deleteUser.js b/packages/rocketchat-lib/server/functions/deleteUser.js index a139389b064d..ab7ec6719b94 100644 --- a/packages/rocketchat-lib/server/functions/deleteUser.js +++ b/packages/rocketchat-lib/server/functions/deleteUser.js @@ -1,4 +1,3 @@ -/* globals RocketChat */ RocketChat.deleteUser = function(userId) { const user = RocketChat.models.Users.findOneById(userId); @@ -22,7 +21,7 @@ RocketChat.deleteUser = function(userId) { // removes user's avatar if (user.avatarOrigin === 'upload' || user.avatarOrigin === 'url') { - RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ user.username }.jpg`)); + FileUpload.getStore('Avatars').deleteByName(user.username); } RocketChat.models.Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. diff --git a/packages/rocketchat-lib/server/functions/setUserAvatar.js b/packages/rocketchat-lib/server/functions/setUserAvatar.js index e80a8c7cdd70..40289080a947 100644 --- a/packages/rocketchat-lib/server/functions/setUserAvatar.js +++ b/packages/rocketchat-lib/server/functions/setUserAvatar.js @@ -40,13 +40,18 @@ RocketChat.setUserAvatar = function(user, dataURI, contentType, service) { } const rs = RocketChatFile.bufferToStream(new Buffer(image, encoding)); - RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ user.username }.jpg`)); - const ws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${ user.username }.jpg`), contentType); - ws.on('end', Meteor.bindEnvironment(function() { + const fileStore = FileUpload.getStore('Avatars'); + fileStore.deleteByName(user.username); + + const file = { + userId: user._id, + type: contentType + }; + + fileStore.insert(file, rs, () => { Meteor.setTimeout(function() { RocketChat.models.Users.setAvatarOrigin(user._id, service); RocketChat.Notifications.notifyLogged('updateAvatar', {username: user.username}); }, 500); - })); - rs.pipe(ws); + }); }; diff --git a/server/methods/deleteFileMessage.js b/server/methods/deleteFileMessage.js index ce10e596c459..9ffc1fe7f8a6 100644 --- a/server/methods/deleteFileMessage.js +++ b/server/methods/deleteFileMessage.js @@ -9,6 +9,6 @@ Meteor.methods({ return Meteor.call('deleteMessage', msg); } - return FileUpload.delete(fileID); + return FileUpload.getStore('Uploads').deleteById(fileID); } }); diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js index 8b5d1fed2191..ea551e50850f 100644 --- a/server/methods/resetAvatar.js +++ b/server/methods/resetAvatar.js @@ -13,7 +13,7 @@ Meteor.methods({ } const user = Meteor.user(); - RocketChatFileAvatarInstance.deleteFile(`${ user.username }.jpg`); + FileUpload.getStore('Avatars').deleteByName(user.username); RocketChat.models.Users.unsetAvatarOrigin(user._id); RocketChat.Notifications.notifyLogged('updateAvatar', { username: user.username diff --git a/server/startup/initialData.js b/server/startup/initialData.js index 298d76c19119..b8f9acdcaf37 100644 --- a/server/startup/initialData.js +++ b/server/startup/initialData.js @@ -21,16 +21,19 @@ Meteor.startup(function() { RocketChat.authz.addUserRoles('rocket.cat', 'bot'); const rs = RocketChatFile.bufferToStream(new Buffer(Assets.getBinary('avatars/rocketcat.png'), 'utf8')); + const fileStore = FileUpload.getStore('Avatars'); + fileStore.deleteByName('rocket.cat'); - RocketChatFileAvatarInstance.deleteFile('rocket.cat.jpg'); - - const ws = RocketChatFileAvatarInstance.createWriteStream('rocket.cat.jpg', 'image/png'); - - ws.on('end', Meteor.bindEnvironment(function() { - return RocketChat.models.Users.setAvatarOrigin('rocket.cat', 'local'); - })); + const file = { + userId: 'rocket.cat', + type: 'image/png' + }; - rs.pipe(ws); + Meteor.runAsUser('rocket.cat', () => { + fileStore.insert(file, rs, () => { + return RocketChat.models.Users.setAvatarOrigin('rocket.cat', 'local'); + }); + }); } if (process.env.ADMIN_PASS) { From 001dd3a72721c2fa654e5c50cd64de6d3a35691d Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 May 2017 09:19:21 -0300 Subject: [PATCH 014/102] Add FileUpload to .eslintrc --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index bbae28a07d60..2c1846603505 100644 --- a/.eslintrc +++ b/.eslintrc @@ -105,6 +105,7 @@ "EJSON" : false, "Email" : false, "FlowRouter" : false, + "FileUpload" : false, "HTTP" : false, "getNextAgent" : false, "handleError" : false, From 8360b869b875ff0b31ede1ee602f40ed86a028b0 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 May 2017 09:20:14 -0300 Subject: [PATCH 015/102] Fix ESLint error --- .../server/config/configFileUploadGridFS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js index 4e7553f642e2..e26b7cc7d6b4 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js @@ -156,7 +156,7 @@ new FileUploadClass({ new FileUploadClass({ name: 'GridFS:Avatars', getStore() { - return Meteor.fileStoreAvatar + return Meteor.fileStoreAvatar; }, get(file, req, res) { From 06d966cd8bf049b9c6abbbd088dc93b09e341fe0 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 May 2017 10:09:02 -0300 Subject: [PATCH 016/102] Remove RocketChatFileAvatarInstance --- packages/rocketchat-ldap/server/sync.js | 16 ++++++--- .../server/functions/setUsername.coffee | 10 ++---- server/startup/avatar.js | 34 ------------------- server/startup/migrations/v002.js | 14 +++++--- 4 files changed, 23 insertions(+), 51 deletions(-) diff --git a/packages/rocketchat-ldap/server/sync.js b/packages/rocketchat-ldap/server/sync.js index df3ec4bf828b..cdac0c769176 100644 --- a/packages/rocketchat-ldap/server/sync.js +++ b/packages/rocketchat-ldap/server/sync.js @@ -143,16 +143,22 @@ syncUserData = function syncUserData(user, ldapUser) { const avatar = ldapUser.raw.thumbnailPhoto || ldapUser.raw.jpegPhoto; if (avatar) { logger.info('Syncing user avatar'); + const rs = RocketChatFile.bufferToStream(avatar); - RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ user.username }.jpg`)); - const ws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${ user.username }.jpg`), 'image/jpeg'); - ws.on('end', Meteor.bindEnvironment(function() { + const fileStore = FileUpload.getStore('Avatars'); + fileStore.deleteByName(user.username); + + const file = { + userId: user._id, + type: 'image/jpeg' + }; + + fileStore.insert(file, rs, () => { Meteor.setTimeout(function() { RocketChat.models.Users.setAvatarOrigin(user._id, 'ldap'); RocketChat.Notifications.notifyLogged('updateAvatar', {username: user.username}); }, 500); - })); - rs.pipe(ws); + }); } } }; diff --git a/packages/rocketchat-lib/server/functions/setUsername.coffee b/packages/rocketchat-lib/server/functions/setUsername.coffee index a3afa4a49e9e..f07e63275384 100644 --- a/packages/rocketchat-lib/server/functions/setUsername.coffee +++ b/packages/rocketchat-lib/server/functions/setUsername.coffee @@ -61,13 +61,9 @@ RocketChat._setUsername = (userId, username) -> RocketChat.models.Subscriptions.setUserUsernameByUserId user._id, username RocketChat.models.Subscriptions.setNameForDirectRoomsWithOldName previousUsername, username - rs = RocketChatFileAvatarInstance.getFileWithReadStream(encodeURIComponent("#{previousUsername}.jpg")) - if rs? - RocketChatFileAvatarInstance.deleteFile encodeURIComponent("#{username}.jpg") - ws = RocketChatFileAvatarInstance.createWriteStream encodeURIComponent("#{username}.jpg"), rs.contentType - ws.on 'end', Meteor.bindEnvironment -> - RocketChatFileAvatarInstance.deleteFile encodeURIComponent("#{previousUsername}.jpg") - rs.readStream.pipe(ws) + fileStore = FileUpload.getStore('Avatars') + file = fileStore.model.findOneByName(previousUsername) + fileStore.model.updateFileNameById(file._id, username) # Set new username RocketChat.models.Users.setUsername user._id, username diff --git a/server/startup/avatar.js b/server/startup/avatar.js index ae69381aff5e..0fa118f685e1 100644 --- a/server/startup/avatar.js +++ b/server/startup/avatar.js @@ -1,39 +1,5 @@ /* globals FileUpload */ Meteor.startup(function() { - let storeType = 'GridFS'; - - if (RocketChat.settings.get('Accounts_AvatarStoreType')) { - storeType = RocketChat.settings.get('Accounts_AvatarStoreType'); - } - - const RocketChatStore = RocketChatFile[storeType]; - - if (!RocketChatStore) { - throw new Error(`Invalid RocketChatStore type [${ storeType }]`); - } - - console.log((`Using ${ storeType } for Avatar storage`).green); - - function transformWrite(file, readStream, writeStream) { - if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) { - return readStream.pipe(writeStream); - } - const height = RocketChat.settings.get('Accounts_AvatarSize'); - const width = height; - return RocketChatFile.gm(readStream, file.fileName).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream); - } - - let path = '~/uploads'; - if (RocketChat.settings.get('Accounts_AvatarStorePath') && RocketChat.settings.get('Accounts_AvatarStorePath').trim() !== '') { - path = RocketChat.settings.get('Accounts_AvatarStorePath'); - } - - this.RocketChatFileAvatarInstance = new RocketChatStore({ - name: 'avatars', - absolutePath: path, - transformWrite - }); - WebApp.connectHandlers.use('/avatar/', Meteor.bindEnvironment(function(req, res/*, next*/) { const params = { username: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')) diff --git a/server/startup/migrations/v002.js b/server/startup/migrations/v002.js index ad2d589a3cb8..e67336ed640d 100644 --- a/server/startup/migrations/v002.js +++ b/server/startup/migrations/v002.js @@ -26,13 +26,17 @@ RocketChat.Migrations.add({ const {image, contentType} = RocketChatFile.dataURIParse(dataURI); const rs = RocketChatFile.bufferToStream(new Buffer(image, 'base64')); - const ws = RocketChatFileAvatarInstance.createWriteStream(`${ user.username }.jpg`, contentType); + const fileStore = FileUpload.getStore('Avatars'); + fileStore.deleteByName(user.username); - ws.on('end', Meteor.bindEnvironment(function() { - return RocketChat.models.Users.setAvatarOrigin(user._id, service); - })); + const file = { + userId: user._id, + type: contentType + }; - return rs.pipe(ws); + fileStore.insert(file, rs, () => { + return RocketChat.models.Users.setAvatarOrigin(user._id, service); + }); }); } }); From 014241c352eaf0e694e0f869b76f63653cd31d20 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 May 2017 12:39:30 -0300 Subject: [PATCH 017/102] Make Amazon S3 work again --- .../client/lib/FileUploadAmazonS3.js | 3 +- .../server/config/configFileUploadAmazonS3.js | 48 +++++++++++-------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index 0c6f3efd9054..6472ad7bbea1 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -7,6 +7,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { 'upload': 'rocketchat-uploads', 'avatar': 'rocketchat-avatars' }; + this.directive = directive; this.uploader = new Slingshot.Upload(directives[directive], meta); } @@ -23,7 +24,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; - return callback(null, file, 's3'); + return callback(null, file, this.directive === 'avatar' ? 'S3:Avatars' : 'S3:Uploads'); } }); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index fe18ecaacebe..c4f3e13e0cfa 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -17,27 +17,37 @@ const generateURL = function(file) { return `${ file.url }?AWSAccessKeyId=${ encodeURIComponent(S3accessKey) }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`; }; +const getFile = function(file, req, res) { + const fileUrl = generateURL(file); + + if (fileUrl) { + res.setHeader('Location', fileUrl); + res.writeHead(302); + } + res.end(); +}; + +const deleteFile = function(file) { + const s3 = new AWS.S3(); + const request = s3.deleteObject({ + Bucket: file.s3.bucket, + Key: file.s3.path + file._id + }); + request.send(); +}; + new FileUploadClass({ name: 'S3:Uploads', - get(file, req, res) { - const fileUrl = generateURL(file); + get: getFile, + delete: deleteFile +}); - if (fileUrl) { - res.setHeader('Location', fileUrl); - res.writeHead(302); - } - res.end(); - }, - - delete(file) { - const s3 = new AWS.S3(); - const request = s3.deleteObject({ - Bucket: file.s3.bucket, - Key: file.s3.path + file._id - }); - request.send(); - } +new FileUploadClass({ + name: 'S3:Avatars', + + get: getFile, + delete: deleteFile }); function createDirective(directiveName, { key, bucket, accessKey, secretKey, region, acl, cdn, bucketUrl}) { @@ -89,7 +99,7 @@ const configureSlingshot = _.debounce(() => { path } }; - const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 's3', file, upload); + const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'S3:Uploads', file, upload); return path + fileId; } @@ -110,7 +120,7 @@ const configureSlingshot = _.debounce(() => { } }; delete file.name; - RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 's3', file, upload); + RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'S3:Avatars', file, upload); return path + user.username; } From 451b055a70e375214cf723f6ef1d7392113b42a5 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 May 2017 19:51:45 -0300 Subject: [PATCH 018/102] Fix upload for Google Cloud Storage --- .../client/lib/FileUploadAmazonS3.js | 2 +- .../client/lib/FileUploadGoogleStorage.js | 2 +- .../server/config/configFileUploadAmazonS3.js | 8 +-- .../config/configFileUploadGoogleStorage.js | 60 +++++++++++-------- .../rocketchat-ui/client/lib/fileUpload.js | 2 +- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index 6472ad7bbea1..678830d2bf53 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -24,7 +24,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; - return callback(null, file, this.directive === 'avatar' ? 'S3:Avatars' : 'S3:Uploads'); + return callback(null, file, this.directive === 'avatar' ? 'AmazonS3:Avatars' : 'AmazonS3:Uploads'); } }); diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js index b4b047c42b8b..a5aa21485b28 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGoogleStorage.js @@ -23,7 +23,7 @@ FileUpload.GoogleCloudStorage = class FileUploadGoogleCloudStorage extends FileU file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; - return callback(null, file, 'googleCloudStorage'); + return callback(null, file, 'GoogleCloudStorage:Uploads'); } }); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index c4f3e13e0cfa..b954f95e710b 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -37,14 +37,14 @@ const deleteFile = function(file) { }; new FileUploadClass({ - name: 'S3:Uploads', + name: 'AmazonS3:Uploads', get: getFile, delete: deleteFile }); new FileUploadClass({ - name: 'S3:Avatars', + name: 'AmazonS3:Avatars', get: getFile, delete: deleteFile @@ -99,7 +99,7 @@ const configureSlingshot = _.debounce(() => { path } }; - const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'S3:Uploads', file, upload); + const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'AmazonS3:Uploads', file, upload); return path + fileId; } @@ -120,7 +120,7 @@ const configureSlingshot = _.debounce(() => { } }; delete file.name; - RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'S3:Avatars', file, upload); + RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'AmazonS3:Avatars', file, upload); return path + user.username; } diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index 69e0b514ba0c..c9cd841aacb8 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -63,36 +63,46 @@ function createDirective(directiveName, { key, bucket, accessId, secret }) { } } -new FileUploadClass({ - name: 'googleCloudStorage', +const getFile = function(file, req, res) { + const fileUrl = generateGetURL({ file }); - get(file, req, res) { - const fileUrl = generateGetURL({ file }); + if (fileUrl) { + res.setHeader('Location', fileUrl); + res.writeHead(302); + } + res.end(); +}; - if (fileUrl) { - res.setHeader('Location', fileUrl); - res.writeHead(302); - } - res.end(); - }, +const deleteFile = function(file) { + if (!file || !file.googleCloudStorage) { + console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, the file and googleCloudStorage properties are not defined.'); + return; + } - delete(file) { - if (!file || !file.googleCloudStorage) { - console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, the file and googleCloudStorage properties are not defined.'); - return; - } + // RocketChat.models.Uploads.deleteFile(file._id); + + const url = generateDeleteUrl({ file }); + + if (_.isEmpty(url)) { + console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, failed to generate a delete url.'); + return; + } - // RocketChat.models.Uploads.deleteFile(file._id); + HTTP.call('DELETE', url); +}; - const url = generateDeleteUrl({ file }); +new FileUploadClass({ + name: 'GoogleCloudStorage:Uploads', - if (_.isEmpty(url)) { - console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, failed to generate a delete url.'); - return; - } + get: getFile, + delete: deleteFile +}); - HTTP.call('DELETE', url); - } +new FileUploadClass({ + name: 'GoogleCloudStorage:Avatars', + + get: getFile, + delete: deleteFile }); const createGoogleStorageDirective = _.debounce(() => { @@ -108,7 +118,7 @@ const createGoogleStorageDirective = _.debounce(() => { path } }; - const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'googleCloudStorage', file, upload); + const fileId = RocketChat.models.Uploads.insertFileInit(this.userId, 'GoogleCloudStorage:Uploads', file, upload); return path + fileId; } @@ -128,7 +138,7 @@ const createGoogleStorageDirective = _.debounce(() => { } }; delete file.name; - RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'googleCloudStorage', file, upload); + RocketChat.models.Avatars.insertAvatarFileInit(user.username, this.userId, 'GoogleCloudStorage:Avatars', file, upload); return path + user.username; } diff --git a/packages/rocketchat-ui/client/lib/fileUpload.js b/packages/rocketchat-ui/client/lib/fileUpload.js index 20d2007951c0..5f8cb982fab4 100644 --- a/packages/rocketchat-ui/client/lib/fileUpload.js +++ b/packages/rocketchat-ui/client/lib/fileUpload.js @@ -196,7 +196,7 @@ fileUpload = function(filesToUpload) { const uploading = Session.get('uploading'); if (uploading !== null) { const item = _.findWhere(uploading, { - id: this.id + id: upload.id }); return Session.set('uploading', _.without(uploading, item)); } From 398d2a7e48bb0282d4786f9ef14be431db297702 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Wed, 17 May 2017 18:47:11 -0300 Subject: [PATCH 019/102] wip --- .../admin/client/collection.js | 1 + .../admin/client/route.js | 22 +++++ .../admin/client/startup.js | 10 ++ .../admin/client/views/oauthApp.js | 98 +++++++++++++++++++ .../admin/client/views/oauthApps.js | 18 ++++ .../oauth/client/oauth2-client.js | 59 +++++++++++ .../server/models/OAuthApps.js | 17 ++++ 7 files changed, 225 insertions(+) create mode 100644 packages/rocketchat-oauth2-server-config/admin/client/collection.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/client/route.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/client/startup.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/client/views/oauthApps.js create mode 100644 packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.js create mode 100644 packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js diff --git a/packages/rocketchat-oauth2-server-config/admin/client/collection.js b/packages/rocketchat-oauth2-server-config/admin/client/collection.js new file mode 100644 index 000000000000..f196d7dc17eb --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/client/collection.js @@ -0,0 +1 @@ +this.ChatOAuthApps = new Mongo.Collection('rocketchat_oauth_apps'); diff --git a/packages/rocketchat-oauth2-server-config/admin/client/route.js b/packages/rocketchat-oauth2-server-config/admin/client/route.js new file mode 100644 index 000000000000..7b0b7f469f2c --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/client/route.js @@ -0,0 +1,22 @@ +FlowRouter.route('/admin/oauth-apps', { + name: 'admin-oauth-apps', + action() { + return BlazeLayout.render('main', { + center: 'pageSettingsContainer', + pageTitle: t('OAuth_Applications'), + pageTemplate: 'oauthApps' + }); + } +}); + +FlowRouter.route('/admin/oauth-app/:id?', { + name: 'admin-oauth-app', + action(params) { + return BlazeLayout.render('main', { + center: 'pageSettingsContainer', + pageTitle: t('OAuth_Application'), + pageTemplate: 'oauthApp', + params + }); + } +}); diff --git a/packages/rocketchat-oauth2-server-config/admin/client/startup.js b/packages/rocketchat-oauth2-server-config/admin/client/startup.js new file mode 100644 index 000000000000..28fa6d3bfbbc --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/client/startup.js @@ -0,0 +1,10 @@ +RocketChat.AdminBox.addOption({ + href: 'admin-oauth-apps', + i18nLabel: 'OAuth Apps', + permissionGranted() { + return RocketChat.authz.hasAllPermission('manage-oauth-apps'); + } +}); + +// --- +// generated by coffee-script 1.9.2 diff --git a/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.js b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.js new file mode 100644 index 000000000000..fb6a95d2cf2a --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.js @@ -0,0 +1,98 @@ +/* globals ChatOAuthApps */ +import toastr from 'toastr'; + +Template.oauthApp.onCreated(function() { + this.subscribe('oauthApps'); + this.record = new ReactiveVar({ + active: true + }); +}); + +Template.oauthApp.helpers({ + hasPermission() { + return RocketChat.authz.hasAllPermission('manage-oauth-apps'); + }, + data() { + let params; + let data; + const instance = Template.instance(); + if (typeof instance.data.params === 'function') { + params = instance.data.params(); + } + if (params && params.id) { + data = ChatOAuthApps.findOne({ _id: params.id }); + if (data) { + data.authorization_url = Meteor.absoluteUrl('oauth/authorize'); + data.access_token_url = Meteor.absoluteUrl('oauth/token'); + Template.instance().record.set(data); + return data; + } + } + return Template.instance().record.curValue; + } +}); + +Template.oauthApp.events({ + 'click .submit > .delete'() { + const params = Template.instance().data.params(); + swal({ + title: t('Are_you_sure'), + text: t('You_will_not_be_able_to_recover'), + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes_delete_it'), + cancelButtonText: t('Cancel'), + closeOnConfirm: false, + html: false + }, function() { + Meteor.call('deleteOAuthApp', params.id, function() { + swal({ + title: t('Deleted'), + text: t('Your_entry_has_been_deleted'), + type: 'success', + timer: 1000, + showConfirmButton: false + }); + FlowRouter.go('admin-oauth-apps'); + }); + }); + }, + 'click .submit > .save'() { + let params; + const instance = Template.instance(); + const name = $('[name=name]').val().trim(); + const active = $('[name=active]:checked').val().trim() === '1'; + const redirectUri = $('[name=redirectUri]').val().trim(); + if (name === '') { + return toastr.error(TAPi18n.__('The_application_name_is_required')); + } + if (redirectUri === '') { + return toastr.error(TAPi18n.__('The_redirectUri_is_required')); + } + const app = { + name, + active, + redirectUri + }; + if (typeof instance.data.params === 'function') { + params = instance.data.params(); + } + if (params && params.id) { + Meteor.call('updateOAuthApp', params.id, app, function(err) { + if (err != null) { + return handleError(err); + } + toastr.success(TAPi18n.__('Application_updated')); + }); + } else { + Meteor.call('addOAuthApp', app, function(err, data) { + if (err != null) { + return handleError(err); + } + toastr.success(TAPi18n.__('Application_added')); + FlowRouter.go('admin-oauth-app', { id: data._id }); + }); + } + } +}); diff --git a/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApps.js b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApps.js new file mode 100644 index 000000000000..ddc3ce6c1539 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApps.js @@ -0,0 +1,18 @@ +/* globals ChatOAuthApps */ +import moment from 'moment'; + +Template.oauthApps.onCreated(function() { + this.subscribe('oauthApps'); +}); + +Template.oauthApps.helpers({ + hasPermission() { + return RocketChat.authz.hasAllPermission('manage-oauth-apps'); + }, + applications() { + return ChatOAuthApps.find(); + }, + dateFormated(date) { + return moment(date).format('L LT'); + } +}); diff --git a/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.js b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.js new file mode 100644 index 000000000000..a2eb6ebc3e20 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.js @@ -0,0 +1,59 @@ +// @ChatOAuthApps = new Mongo.Collection 'rocketchat_oauth_apps' +/*globals ChatOAuthApps */ +FlowRouter.route('/oauth/authorize', { + action(params, queryParams) { + BlazeLayout.render('main', { + center: 'authorize', + modal: true, + client_id: queryParams.client_id, + redirect_uri: queryParams.redirect_uri, + response_type: queryParams.response_type, + state: queryParams.state + }); + } +}); + +FlowRouter.route('/oauth/error/:error', { + action(params) { + BlazeLayout.render('main', { + center: 'oauth404', + modal: true, + error: params.error + }); + } +}); + +Template.authorize.onCreated(function() { + this.subscribe('authorizedOAuth'); + this.subscribe('oauthClient', this.data.client_id()); +}); + +Template.authorize.helpers({ + getToken() { + return localStorage.getItem('Meteor.loginToken'); + }, + getClient() { + return ChatOAuthApps.findOne(); + } +}); + +Template.authorize.events({ + 'click #logout-oauth'() { + return Meteor.logout(); + }, + 'click #cancel-oauth'() { + return window.close(); + } +}); + +Template.authorize.onRendered(function() { + this.autorun((c) => { + const user = Meteor.user(); + if (user && user.oauth && user.oauth.authorizedClients) { + if (user.oauth.authorizedClients.indexOf(this.data.client_id() > -1)) { + c.stop(); + $('button[type=submit]').click(); + } + } + }); +}); diff --git a/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js b/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js new file mode 100644 index 000000000000..682b00a23427 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js @@ -0,0 +1,17 @@ +RocketChat.models.OAuthApps = new class extends RocketChat.models._Base { + constructor() { + super('oauth_apps'); + } +}; + + + + + // # FIND + // # findByRole: (role, options) -> + // # query = + // # roles: role + + // # return @find query, options + + // # CREATE From a54ff14d335a9cf6f889ed35c4a76105a4d5803b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 18 May 2017 11:04:01 -0300 Subject: [PATCH 020/102] Parse HTML on admin setting's descriptions Closes #6992 --- packages/rocketchat-markdown/markdown.js | 1 + packages/rocketchat-ui-admin/client/admin.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/rocketchat-markdown/markdown.js b/packages/rocketchat-markdown/markdown.js index 5c426439fe42..5f0269f99fac 100644 --- a/packages/rocketchat-markdown/markdown.js +++ b/packages/rocketchat-markdown/markdown.js @@ -90,4 +90,5 @@ RocketChat.callbacks.add('renderMessage', MarkdownMessage, RocketChat.callbacks. if (Meteor.isClient) { Blaze.registerHelper('RocketChatMarkdown', text => Markdown.parse(text)); + Blaze.registerHelper('RocketChatMarkdownUnescape', text => Markdown.parseNotEscaped(text)); } diff --git a/packages/rocketchat-ui-admin/client/admin.html b/packages/rocketchat-ui-admin/client/admin.html index e7f3cbd8bd24..725b6405b344 100644 --- a/packages/rocketchat-ui-admin/client/admin.html +++ b/packages/rocketchat-ui-admin/client/admin.html @@ -186,7 +186,7 @@

{{/if}} {{#if description}} -
{{{RocketChatMarkdown description}}}
+
{{{RocketChatMarkdownUnescape description}}}
{{/if}} {{#if alert}}
{{{_ alert}}}
From 979a6a66b3838fc105574c3a58591d8b5e8a81ff Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 18 May 2017 13:43:31 -0300 Subject: [PATCH 021/102] converted --- .../client/admin/adminImport.js | 33 +++ .../client/admin/adminImportPrepare.js | 195 ++++++++++++++ .../client/admin/adminImportProgress.js | 51 ++++ packages/rocketchat-importer/lib/_importer.js | 3 + .../rocketchat-importer/lib/importTool.js | 13 + .../server/classes/ImporterBase.js | 248 ++++++++++++++++++ .../server/classes/ImporterProgress.js | 13 + .../server/classes/ImporterProgressStep.js | 18 ++ .../server/classes/ImporterSelection.js | 15 ++ .../classes/ImporterSelectionChannel.js | 20 ++ .../server/classes/ImporterSelectionUser.js | 22 ++ .../server/methods/getImportProgress.js | 17 ++ .../server/methods/getSelectionData.js | 23 ++ .../server/methods/restartImport.js | 22 ++ .../server/methods/setupImporter.js | 25 ++ .../server/methods/startImport.js | 22 ++ .../server/models/Imports.js | 6 + .../server/models/RawImports.js | 6 + .../server/startup/setImportsToInvalid.js | 9 + 19 files changed, 761 insertions(+) create mode 100644 packages/rocketchat-importer/client/admin/adminImport.js create mode 100644 packages/rocketchat-importer/client/admin/adminImportPrepare.js create mode 100644 packages/rocketchat-importer/client/admin/adminImportProgress.js create mode 100644 packages/rocketchat-importer/lib/_importer.js create mode 100644 packages/rocketchat-importer/lib/importTool.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterBase.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterProgress.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterProgressStep.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterSelection.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js create mode 100644 packages/rocketchat-importer/server/classes/ImporterSelectionUser.js create mode 100644 packages/rocketchat-importer/server/methods/getImportProgress.js create mode 100644 packages/rocketchat-importer/server/methods/getSelectionData.js create mode 100644 packages/rocketchat-importer/server/methods/restartImport.js create mode 100644 packages/rocketchat-importer/server/methods/setupImporter.js create mode 100644 packages/rocketchat-importer/server/methods/startImport.js create mode 100644 packages/rocketchat-importer/server/models/Imports.js create mode 100644 packages/rocketchat-importer/server/models/RawImports.js create mode 100644 packages/rocketchat-importer/server/startup/setImportsToInvalid.js diff --git a/packages/rocketchat-importer/client/admin/adminImport.js b/packages/rocketchat-importer/client/admin/adminImport.js new file mode 100644 index 000000000000..9bfd1e34a4c7 --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImport.js @@ -0,0 +1,33 @@ +/* globals Importer */ +Template.adminImport.helpers({ + isAdmin() { + return RocketChat.authz.hasRole(Meteor.userId(), 'admin'); + }, + isImporters() { + return Object.keys(Importer.Importers).length > 0; + }, + getDescription(importer) { + return TAPi18n.__('Importer_From_Description', { from: importer.name }); + }, + importers() { + const importers = []; + _.each(Importer.Importers, function(importer, key) { + importer.key = key; + return importers.push(importer); + }); + return importers; + } +}); + +Template.adminImport.events({ + 'click .start-import'() { + const importer = this; + return Meteor.call('setupImporter', importer.key, function(error) { + if (error) { + console.log(t('importer_setup_error'), importer.key, error); + return handleError(error); + } + return FlowRouter.go(`/admin/import/prepare/${ importer.key }`); + }); + } +}); diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.js b/packages/rocketchat-importer/client/admin/adminImportPrepare.js new file mode 100644 index 000000000000..fe7e00ee045f --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImportPrepare.js @@ -0,0 +1,195 @@ +/* globals Importer */ +import toastr from 'toastr'; +Template.adminImportPrepare.helpers({ + isAdmin() { + return RocketChat.authz.hasRole(Meteor.userId(), 'admin'); + }, + importer() { + const importerKey = FlowRouter.getParam('importer'); + let importer = undefined; + _.each(Importer.Importers, function(i, key) { + i.key = key; + if (key === importerKey) { + return importer = i; + } + }); + + return importer; + }, + isLoaded() { + return Template.instance().loaded.get(); + }, + isPreparing() { + return Template.instance().preparing.get(); + }, + users() { + return Template.instance().users.get(); + }, + channels() { + return Template.instance().channels.get(); + } +}); + +Template.adminImportPrepare.events({ + 'change .import-file-input'(event, template) { + const importer = this; + if (!importer.key) { return; } + + const e = event.originalEvent || event; + let { files } = e.target; + if (!files || (files.length === 0)) { + files = (e.dataTransfer != null ? e.dataTransfer.files : undefined) || []; + } + + return Array.from(files).map((blob) => { + template.preparing.set(true); + + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => { + Meteor.call('prepareImport', importer.key, reader.result, blob.type, blob.name, function(error, data) { + if (error) { + toastr.error(t('Invalid_Import_File_Type')); + template.preparing.set(false); + return; + } + + if (!data) { + console.warn(`The importer ${ importer.key } is not set up correctly, as it did not return any data.`); + toastr.error(t('Importer_not_setup')); + template.preparing.set(false); + return; + } + + if (data.step) { + console.warn('Invalid file, contains `data.step`.', data); + toastr.error(t('Invalid_Export_File', importer.key)); + template.preparing.set(false); + return; + } + + template.users.set(data.users); + template.channels.set(data.channels); + template.loaded.set(true); + template.preparing.set(false); + }); + }; + }); + }, + + 'click .button.start'(event, template) { + const btn = this; + $(btn).prop('disabled', true); + // const importer = this; + for (const user of Array.from(template.users.get())) { + user.do_import = $(`[name=${ user.user_id }]`).is(':checked'); + } + + for (const channel of Array.from(template.channels.get())) { + channel.do_import = $(`[name=${ channel.channel_id }]`).is(':checked'); + } + + return Meteor.call('startImport', FlowRouter.getParam('importer'), { users: template.users.get(), channels: template.channels.get() }, function(error) { + if (error) { + console.warn('Error on starting the import:', error); + return handleError(error); + } else { + return FlowRouter.go(`/admin/import/progress/${ FlowRouter.getParam('importer') }`); + } + }); + }, + + 'click .button.restart'(event, template) { + Meteor.call('restartImport', FlowRouter.getParam('importer'), function(error) { + if (error) { + console.warn('Error while restarting the import:', error); + handleError(error); + return; + } + + template.users.set([]); + template.channels.set([]); + template.loaded.set(false); + }); + }, + + 'click .button.uncheck-deleted-users'(event, template) { + Array.from(template.users.get()).filter((user) => user.is_deleted).map((user) => + $(`[name=${ user.user_id }]`).attr('checked', false)); + }, + + 'click .button.uncheck-archived-channels'(event, template) { + Array.from(template.channels.get()).filter((channel) => channel.is_archived).map((channel) => + $(`[name=${ channel.channel_id }]`).attr('checked', false)); + } +}); + + +Template.adminImportPrepare.onCreated(function() { + const instance = this; + this.preparing = new ReactiveVar(true); + this.loaded = new ReactiveVar(false); + this.users = new ReactiveVar([]); + this.channels = new ReactiveVar([]); + + function loadSelection(progress) { + if ((progress != null ? progress.step : undefined)) { + switch (progress.step) { + //When the import is running, take the user to the progress page + case 'importer_importing_started': case 'importer_importing_users': case 'importer_importing_channels': case 'importer_importing_messages': case 'importer_finishing': + return FlowRouter.go(`/admin/import/progress/${ FlowRouter.getParam('importer') }`); + // when the import is done, restart it (new instance) + case 'importer_user_selection': + return Meteor.call('getSelectionData', FlowRouter.getParam('importer'), function(error, data) { + if (error) { + handleError(error); + } + instance.users.set(data.users); + instance.channels.set(data.channels); + instance.loaded.set(true); + return instance.preparing.set(false); + }); + case 'importer_new': + return instance.preparing.set(false); + default: + return Meteor.call('restartImport', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + handleError(error); + } + return loadSelection(progress); + }); + } + } else { + return console.warn('Invalid progress information.', progress); + } + } + + // Load the initial progress to determine what we need to do + if (FlowRouter.getParam('importer')) { + return Meteor.call('getImportProgress', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + console.warn('Error while getting the import progress:', error); + handleError(error); + return; + } + + // if the progress isnt defined, that means there currently isn't an instance + // of the importer, so we need to create it + if (progress === undefined) { + return Meteor.call('setupImporter', FlowRouter.getParam('importer'), function(err, data) { + if (err) { + handleError(err); + } + instance.preparing.set(false); + return loadSelection(data); + }); + } else { + // Otherwise, we might need to do something based upon the current step + // of the import + return loadSelection(progress); + } + }); + } else { + return FlowRouter.go('/admin/import'); + } +}); diff --git a/packages/rocketchat-importer/client/admin/adminImportProgress.js b/packages/rocketchat-importer/client/admin/adminImportProgress.js new file mode 100644 index 000000000000..4d22c3a46a72 --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImportProgress.js @@ -0,0 +1,51 @@ +import toastr from 'toastr'; +Template.adminImportProgress.helpers({ + step() { + return Template.instance().step.get(); + }, + completed() { + return Template.instance().completed.get(); + }, + total() { + return Template.instance().total.get(); + } +}); + +Template.adminImportProgress.onCreated(function() { + const instance = this; + this.step = new ReactiveVar(t('Loading...')); + this.completed = new ReactiveVar(0); + this.total = new ReactiveVar(0); + this.updateProgress = function() { + if (FlowRouter.getParam('importer') !== '') { + return Meteor.call('getImportProgress', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + console.warn('Error on getting the import progress:', error); + handleError(error); + return; + } + + if (progress) { + if (progress.step === 'importer_done') { + toastr.success(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + return FlowRouter.go('/admin/import'); + } else if (progress.step === 'importer_import_failed') { + toastr.error(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + return FlowRouter.go(`/admin/import/prepare/${ FlowRouter.getParam('importer') }`); + } else { + instance.step.set(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + instance.completed.set(progress.count.completed); + instance.total.set(progress.count.total); + return setTimeout(() => instance.updateProgress() + , 100); + } + } else { + toastr.warning(t('Importer_not_in_progress')); + return FlowRouter.go(`/admin/import/prepare/${ FlowRouter.getParam('importer') }`); + } + }); + } + }; + + return instance.updateProgress(); +}); diff --git a/packages/rocketchat-importer/lib/_importer.js b/packages/rocketchat-importer/lib/_importer.js new file mode 100644 index 000000000000..8e7c3f9971cf --- /dev/null +++ b/packages/rocketchat-importer/lib/_importer.js @@ -0,0 +1,3 @@ +/* globals Importer */ +Importer = {}; +export default Importer; diff --git a/packages/rocketchat-importer/lib/importTool.js b/packages/rocketchat-importer/lib/importTool.js new file mode 100644 index 000000000000..94315ab36bd5 --- /dev/null +++ b/packages/rocketchat-importer/lib/importTool.js @@ -0,0 +1,13 @@ +/* globals Importer */ +Importer.Importers = {}; + +Importer.addImporter = function(name, importer, options) { + if (Importer.Importers[name] == null) { + return Importer.Importers[name] = { + name: options.name, + importer, + mimeType: options.mimeType, + warnings: options.warnings + }; + } +}; diff --git a/packages/rocketchat-importer/server/classes/ImporterBase.js b/packages/rocketchat-importer/server/classes/ImporterBase.js new file mode 100644 index 000000000000..9e3f98c4011c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterBase.js @@ -0,0 +1,248 @@ +/* globals Importer */ +// Base class for all Importers. +// +// @example How to subclass an importer +// class ExampleImporter extends RocketChat.importTool._baseImporter +// constructor: -> +// super('Name of Importer', 'Description of the importer, use i18n string.', new RegExp('application\/.*?zip')) +// prepare: (uploadedFileData, uploadedFileContentType, uploadedFileName) => +// super +// startImport: (selectedUsersAndChannels) => +// super +// getProgress: => +// #return the progress report, tbd what is expected +// @version 1.0.0 +Importer.Base = class Base { + static getBSONSize(object) { + // The max BSON object size we can store in MongoDB is 16777216 bytes + // but for some reason the mongo instanace which comes with meteor + // errors out for anything close to that size. So, we are rounding it + // down to 8000000 bytes. + const { BSON } = require('bson').native(); + const bson = new BSON(); + return bson.calculateObjectSize(object); + } + + static getBSONSafeArraysFromAnArray(theArray) { + const BSONSize = Importer.Base.getBSONSize(theArray); + const maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Importer.Base.MaxBSONSize))); + const safeArrays = []; + let i = 0; + while (i < theArray.length) { + safeArrays.push(theArray.slice(i, (i += maxSize))); + } + return safeArrays; + } + + // Constructs a new importer, adding an empty collection, AdmZip property, and empty users & channels + // + // @param [String] name the name of the Importer + // @param [String] description the i18n string which describes the importer + // @param [String] mimeType the of the expected file type + // + constructor(name, description, mimeType) { + this.MaxBSONSize = 8000000; + this.http = Npm.require('http'); + this.https = Npm.require('https'); + + + this.prepare = this.prepare.bind(this); + this.startImport = this.startImport.bind(this); + this.getSelection = this.getSelection.bind(this); + this.getProgress = this.getProgress.bind(this); + this.updateProgress = this.updateProgress.bind(this); + this.addCountToTotal = this.addCountToTotal.bind(this); + this.addCountCompleted = this.addCountCompleted.bind(this); + this.updateRecord = this.updateRecord.bind(this); + this.uploadFile = this.uploadFile.bind(this); + this.name = name; + this.description = description; + this.mimeType = mimeType; + this.logger = new Logger(`${ this.name } Importer`, {}); + this.progress = new Importer.Progress(this.name); + this.collection = Importer.RawImports; + this.AdmZip = Npm.require('adm-zip'); + this.getFileType = Npm.require('file-type'); + const importId = Importer.Imports.insert({ 'type': this.name, 'ts': Date.now(), 'status': this.progress.step, 'valid': true, 'user': Meteor.user()._id }); + this.importRecord = Importer.Imports.findOne(importId); + this.users = {}; + this.channels = {}; + this.messages = {}; + } + + // Takes the uploaded file and extracts the users, channels, and messages from it. + // + // @param [String] dataURI a base64 string of the uploaded file + // @param [String] sentContentType the file type + // @param [String] fileName the name of the uploaded file + // + // @return [Importer.Selection] Contains two properties which are arrays of objects, `channels` and `users`. + // + prepare(dataURI, sentContentType, fileName) { + const fileType = this.getFileType(new Buffer(dataURI.split(',')[1], 'base64')); + this.logger.debug('Uploaded file information is:', fileType); + this.logger.debug('Expected file type is:', this.mimeType); + + if (!fileType || (fileType.mime !== this.mimeType)) { + this.logger.warn(`Invalid file uploaded for the ${ this.name } importer.`); + throw new Meteor.Error('error-invalid-file-uploaded', `Invalid file uploaded to import ${ this.name } data from.`, { step: 'prepare' }); + } + + this.updateProgress(Importer.ProgressStep.PREPARING_STARTED); + return this.updateRecord({ 'file': fileName }); + } + + // Starts the import process. The implementing method should defer as soon as the selection is set, so the user who started the process + // doesn't end up with a "locked" ui while meteor waits for a response. The returned object should be the progress. + // + // @param [Importer.Selection] selectedUsersAndChannels an object with `channels` and `users` which contains information about which users and channels to import + // + // @return [Importer.Progress] the progress of the import + // + startImport(importSelection) { + if (importSelection === undefined) { + throw new Error(`No selected users and channel data provided to the ${ this.name } importer.`); //TODO: Make translatable + } else if (importSelection.users === undefined) { + throw new Error(`Users in the selected data wasn't found, it must but at least an empty array for the ${ this.name } importer.`); //TODO: Make translatable + } else if (importSelection.channels === undefined) { + throw new Error(`Channels in the selected data wasn't found, it must but at least an empty array for the ${ this.name } importer.`); //TODO: Make translatable + } + + return this.updateProgress(Importer.ProgressStep.IMPORTING_STARTED); + } + + // Gets the Importer.Selection object for the import. + // + // @return [Importer.Selection] the users and channels selection + getSelection() { + throw new Error(`Invalid 'getSelection' called on ${ this.name }, it must be overridden and super can not be called.`); + } + + // Gets the progress of this importer. + // + // @return [Importer.Progress] the progress of the import + // + getProgress() { + return this.progress; + } + + // Updates the progress step of this importer. + // + // @return [Importer.Progress] the progress of the import + // + updateProgress(step) { + this.progress.step = step; + + this.logger.debug(`${ this.name } is now at ${ step }.`); + this.updateRecord({ 'status': this.progress.step }); + + return this.progress; + } + + // Adds the passed in value to the total amount of items needed to complete. + // + // @return [Importer.Progress] the progress of the import + // + addCountToTotal(count) { + this.progress.count.total = this.progress.count.total + count; + this.updateRecord({ 'count.total': this.progress.count.total }); + + return this.progress; + } + + // Adds the passed in value to the total amount of items completed. + // + // @return [Importer.Progress] the progress of the import + // + addCountCompleted(count) { + this.progress.count.completed = this.progress.count.completed + count; + + //Only update the database every 500 records + //Or the completed is greater than or equal to the total amount + if (((this.progress.count.completed % 500) === 0) || (this.progress.count.completed >= this.progress.count.total)) { + this.updateRecord({ 'count.completed': this.progress.count.completed }); + } + + return this.progress; + } + + // Updates the import record with the given fields being `set` + // + // @return [Importer.Imports] the import record object + // + updateRecord(fields) { + Importer.Imports.update({ _id: this.importRecord._id }, { $set: fields }); + this.importRecord = Importer.Imports.findOne(this.importRecord._id); + + return this.importRecord; + } + + // Uploads the file to the storage. + // + // @param [Object] details an object with details about the upload. name, size, type, and rid + // @param [String] fileUrl url of the file to download/import + // @param [Object] user the Rocket.Chat user + // @param [Object] room the Rocket.Chat room + // @param [Date] timeStamp the timestamp the file was uploaded + // + uploadFile(details, fileUrl, user, room, timeStamp) { + this.logger.debug(`Uploading the file ${ details.name } from ${ fileUrl }.`); + const requestModule = /https/i.test(fileUrl) ? Importer.Base.https : Importer.Base.http; + + return requestModule.get(fileUrl, Meteor.bindEnvironment(function(stream) { + const fileId = Meteor.fileStore.create(details); + if (fileId) { + return Meteor.fileStore.write(stream, fileId, function(err, file) { + if (err) { + throw new Error(err); + } else { + const url = file.url.replace(Meteor.absoluteUrl(), '/'); + + const attachment = { + title: `File Uploaded: ${ file.name }`, + title_link: url + }; + + if (/^image\/.+/.test(file.type)) { + attachment.image_url = url; + attachment.image_type = file.type; + attachment.image_size = file.size; + attachment.image_dimensions = file.identify != null ? file.identify.size : undefined; + } + + if (/^audio\/.+/.test(file.type)) { + attachment.audio_url = url; + attachment.audio_type = file.type; + attachment.audio_size = file.size; + } + + if (/^video\/.+/.test(file.type)) { + attachment.video_url = url; + attachment.video_type = file.type; + attachment.video_size = file.size; + } + + const msg = { + rid: details.rid, + ts: timeStamp, + msg: '', + file: { + _id: file._id + }, + groupable: false, + attachments: [attachment] + }; + + if ((details.message_id != null) && (typeof details.message_id === 'string')) { + msg['_id'] = details.message_id; + } + + return RocketChat.sendMessage(user, msg, room, true); + } + }); + } else { + return this.logger.error(`Failed to create the store for ${ fileUrl }!!!`); + } + })); + } +}; diff --git a/packages/rocketchat-importer/server/classes/ImporterProgress.js b/packages/rocketchat-importer/server/classes/ImporterProgress.js new file mode 100644 index 000000000000..f1cf9e370677 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterProgress.js @@ -0,0 +1,13 @@ +/* globals Importer */ +// Class for all the progress of the importers to use. +Importer.Progress = (Importer.Progress = class Progress { + // Constructs a new progress object. + // + // @param [String] name the name of the Importer + // + constructor(name) { + this.name = name; + this.step = Importer.ProgressStep.NEW; + this.count = { completed: 0, total: 0 }; + } +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterProgressStep.js b/packages/rocketchat-importer/server/classes/ImporterProgressStep.js new file mode 100644 index 000000000000..542383ce100c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterProgressStep.js @@ -0,0 +1,18 @@ +/* globals Importer */ +// "ENUM" of the import step, the value is the translation string +Importer.ProgressStep = Object.freeze({ + NEW: 'importer_new', + PREPARING_STARTED: 'importer_preparing_started', + PREPARING_USERS: 'importer_preparing_users', + PREPARING_CHANNELS: 'importer_preparing_channels', + PREPARING_MESSAGES: 'importer_preparing_messages', + USER_SELECTION: 'importer_user_selection', + IMPORTING_STARTED: 'importer_importing_started', + IMPORTING_USERS: 'importer_importing_users', + IMPORTING_CHANNELS: 'importer_importing_channels', + IMPORTING_MESSAGES: 'importer_importing_messages', + FINISHING: 'importer_finishing', + DONE: 'importer_done', + ERROR: 'importer_import_failed', + CANCELLED: 'importer_import_cancelled' +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterSelection.js b/packages/rocketchat-importer/server/classes/ImporterSelection.js new file mode 100644 index 000000000000..070af9a70be6 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelection.js @@ -0,0 +1,15 @@ +/* globals Importer */ +// Class for all the selection of users and channels for the importers +Importer.Selection = (Importer.Selection = class Selection { + // Constructs a new importer selection object. + // + // @param [String] name the name of the Importer + // @param [Array] users the array of users + // @param [Array] channels the array of channels + // + constructor(name, users, channels) { + this.name = name; + this.users = users; + this.channels = channels; + } +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js new file mode 100644 index 000000000000..60262cb0e99c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js @@ -0,0 +1,20 @@ +/* globals Importer */ +// Class for the selection channels for ImporterSelection +Importer.SelectionChannel = (Importer.SelectionChannel = class SelectionChannel { + // Constructs a new selection channel. + // + // @param [String] channel_id the unique identifier of the channel + // @param [String] name the name of the channel + // @param [Boolean] is_archived whether the channel was archived or not + // @param [Boolean] do_import whether we will be importing the channel or not + // @param [Boolean] is_private whether the channel is private or public + // + constructor(channel_id, name, is_archived, do_import, is_private) { + this.channel_id = channel_id; + this.name = name; + this.is_archived = is_archived; + this.do_import = do_import; + this.is_private = is_private; + } +}); + //TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js new file mode 100644 index 000000000000..1e1afffda545 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js @@ -0,0 +1,22 @@ +/* globals Importer */ +// Class for the selection users for ImporterSelection +Importer.SelectionUser = (Importer.SelectionUser = class SelectionUser { + // Constructs a new selection user. + // + // @param [String] user_id the unique user identifier + // @param [String] username the user's username + // @param [String] email the user's email + // @param [Boolean] is_deleted whether the user was deleted or not + // @param [Boolean] is_bot whether the user is a bot or not + // @param [Boolean] do_import whether we are going to import this user or not + // + constructor(user_id, username, email, is_deleted, is_bot, do_import) { + this.user_id = user_id; + this.username = username; + this.email = email; + this.is_deleted = is_deleted; + this.is_bot = is_bot; + this.do_import = do_import; + } +}); + //TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/methods/getImportProgress.js b/packages/rocketchat-importer/server/methods/getImportProgress.js new file mode 100644 index 000000000000..f3ca0c279351 --- /dev/null +++ b/packages/rocketchat-importer/server/methods/getImportProgress.js @@ -0,0 +1,17 @@ +/* globals Importer */ +Meteor.methods({ + getImportProgress(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getImportProgress' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] != null) { + return (Importer.Importers[name].importerInstance != null ? Importer.Importers[name].importerInstance.getProgress() : undefined); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getImportProgress' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/getSelectionData.js b/packages/rocketchat-importer/server/methods/getSelectionData.js new file mode 100644 index 000000000000..a8e2a2f57c11 --- /dev/null +++ b/packages/rocketchat-importer/server/methods/getSelectionData.js @@ -0,0 +1,23 @@ +/* globals Importer */ +Meteor.methods({ + getSelectionData(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getSelectionData' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if ((Importer.Importers[name] != null ? Importer.Importers[name].importerInstance : undefined) != null) { + const progress = Importer.Importers[name].importerInstance.getProgress(); + switch (progress.step) { + case Importer.ProgressStep.USER_SELECTION: + return Importer.Importers[name].importerInstance.getSelection(); + default: + return false; + } + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getSelectionData' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/restartImport.js b/packages/rocketchat-importer/server/methods/restartImport.js new file mode 100644 index 000000000000..c4a69df03ddb --- /dev/null +++ b/packages/rocketchat-importer/server/methods/restartImport.js @@ -0,0 +1,22 @@ +/* globals Importer*/ +Meteor.methods({ + restartImport(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'restartImport' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] != null) { + const importer = Importer.Importers[name]; + importer.importerInstance.updateProgress(Importer.ProgressStep.CANCELLED); + importer.importerInstance.updateRecord({ valid: false }); + importer.importerInstance = undefined; + importer.importerInstance = new importer.importer(importer.name, importer.description, importer.mimeType); // eslint-disable-line new-cap + return importer.importerInstance.getProgress(); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'restartImport' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/setupImporter.js b/packages/rocketchat-importer/server/methods/setupImporter.js new file mode 100644 index 000000000000..38cd360efa1f --- /dev/null +++ b/packages/rocketchat-importer/server/methods/setupImporter.js @@ -0,0 +1,25 @@ +/* globals Importer */ +Meteor.methods({ + setupImporter(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setupImporter' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if ((Importer.Importers[name] != null ? Importer.Importers[name].importer : undefined) != null) { + const importer = Importer.Importers[name]; + // If they currently have progress, get it and return the progress. + if (importer.importerInstance) { + return importer.importerInstance.getProgress(); + } else { + importer.importerInstance = new importer.importer(importer.name, importer.description, importer.mimeType); //eslint-disable-line new-cap + return importer.importerInstance.getProgress(); + } + } else { + console.warn(`Tried to setup ${ name } as an importer.`); + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'setupImporter' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/startImport.js b/packages/rocketchat-importer/server/methods/startImport.js new file mode 100644 index 000000000000..352193e28a2f --- /dev/null +++ b/packages/rocketchat-importer/server/methods/startImport.js @@ -0,0 +1,22 @@ +/* globals Importer */ +Meteor.methods({ + startImport(name, input) { + // Takes name and object with users / channels selected to import + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'startImport' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] && Importer.Importers[name].importerInstance) { + const usersSelection = input.users.map(user => new Importer.SelectionUser(user.user_id, user.username, user.email, user.is_deleted, user.is_bot, user.do_import)); + const channelsSelection = input.channels.map(channel => new Importer.SelectionChannel(channel.channel_id, channel.name, channel.is_archived, channel.do_import)); + + const selection = new Importer.Selection(name, usersSelection, channelsSelection); + return Importer.Importers[name].importerInstance.startImport(selection); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'startImport' }); + } + }}); diff --git a/packages/rocketchat-importer/server/models/Imports.js b/packages/rocketchat-importer/server/models/Imports.js new file mode 100644 index 000000000000..f68dc3e1d246 --- /dev/null +++ b/packages/rocketchat-importer/server/models/Imports.js @@ -0,0 +1,6 @@ +/* globals Importer */ +Importer.Imports = new (Importer.Imports = class Imports extends RocketChat.models._Base { + constructor() { + super('import'); + } +}); diff --git a/packages/rocketchat-importer/server/models/RawImports.js b/packages/rocketchat-importer/server/models/RawImports.js new file mode 100644 index 000000000000..70eae7f76c06 --- /dev/null +++ b/packages/rocketchat-importer/server/models/RawImports.js @@ -0,0 +1,6 @@ +/* globals Importer */ +Importer.RawImports = new (Importer.RawImports = class RawImports extends RocketChat.models._Base { + constructor() { + super('raw_imports'); + } +}); diff --git a/packages/rocketchat-importer/server/startup/setImportsToInvalid.js b/packages/rocketchat-importer/server/startup/setImportsToInvalid.js new file mode 100644 index 000000000000..2424ef40a007 --- /dev/null +++ b/packages/rocketchat-importer/server/startup/setImportsToInvalid.js @@ -0,0 +1,9 @@ +/* globals Importer */ +Meteor.startup(function() { + // Make sure all imports are marked as invalid, data clean up since you can't + // restart an import at the moment. + Importer.Imports.update({ valid: { $ne: false } }, { $set: { valid: false } }, { multi: true }); + + // Clean up all the raw import data, since you can't restart an import at the moment + return Importer.Imports.find({ valid: { $ne: true }}).forEach(item => Importer.RawImports.remove({ 'import': item._id, 'importer': item.type })); +}); From d9117c71970a2dc243009cdb55b5d84b9d6e9477 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 18 May 2017 13:56:27 -0300 Subject: [PATCH 022/102] Convert oauth-server-config to js --- .../admin/client/startup.js | 3 - .../admin/server/methods/addOAuthApp.js | 22 +++++ .../admin/server/methods/deleteOAuthApp.js | 13 +++ .../admin/server/methods/updateOAuthApp.js | 34 ++++++++ .../admin/server/publications/oauthApps.js | 9 ++ .../oauth/server/default-services.js | 15 ++++ .../oauth/server/oauth2-server.js | 83 +++++++++++++++++++ .../package.js | 26 +++--- .../server/models/OAuthApps.js | 12 +-- 9 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 packages/rocketchat-oauth2-server-config/admin/server/methods/addOAuthApp.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/server/methods/deleteOAuthApp.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/server/methods/updateOAuthApp.js create mode 100644 packages/rocketchat-oauth2-server-config/admin/server/publications/oauthApps.js create mode 100644 packages/rocketchat-oauth2-server-config/oauth/server/default-services.js create mode 100644 packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js diff --git a/packages/rocketchat-oauth2-server-config/admin/client/startup.js b/packages/rocketchat-oauth2-server-config/admin/client/startup.js index 28fa6d3bfbbc..27cee4bd5597 100644 --- a/packages/rocketchat-oauth2-server-config/admin/client/startup.js +++ b/packages/rocketchat-oauth2-server-config/admin/client/startup.js @@ -5,6 +5,3 @@ RocketChat.AdminBox.addOption({ return RocketChat.authz.hasAllPermission('manage-oauth-apps'); } }); - -// --- -// generated by coffee-script 1.9.2 diff --git a/packages/rocketchat-oauth2-server-config/admin/server/methods/addOAuthApp.js b/packages/rocketchat-oauth2-server-config/admin/server/methods/addOAuthApp.js new file mode 100644 index 000000000000..80ddecef58fc --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/server/methods/addOAuthApp.js @@ -0,0 +1,22 @@ +Meteor.methods({ + addOAuthApp(application) { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-oauth-apps')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'addOAuthApp' }); + } + if (!_.isString(application.name) || application.name.trim() === '') { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { method: 'addOAuthApp' }); + } + if (!_.isString(application.redirectUri) || application.redirectUri.trim() === '') { + throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { method: 'addOAuthApp' }); + } + if (!_.isBoolean(application.active)) { + throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { method: 'addOAuthApp' }); + } + application.clientId = Random.id(); + application.clientSecret = Random.secret(); + application._createdAt = new Date; + application._createdBy = RocketChat.models.Users.findOne(this.userId, { fields: { username: 1 } }); + application._id = RocketChat.models.OAuthApps.insert(application); + return application; + } +}); diff --git a/packages/rocketchat-oauth2-server-config/admin/server/methods/deleteOAuthApp.js b/packages/rocketchat-oauth2-server-config/admin/server/methods/deleteOAuthApp.js new file mode 100644 index 000000000000..1426496ed407 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/server/methods/deleteOAuthApp.js @@ -0,0 +1,13 @@ +Meteor.methods({ + deleteOAuthApp(applicationId) { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-oauth-apps')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteOAuthApp' }); + } + const application = RocketChat.models.OAuthApps.findOne(applicationId); + if (application == null) { + throw new Meteor.Error('error-application-not-found', 'Application not found', { method: 'deleteOAuthApp' }); + } + RocketChat.models.OAuthApps.remove({ _id: applicationId }); + return true; + } +}); diff --git a/packages/rocketchat-oauth2-server-config/admin/server/methods/updateOAuthApp.js b/packages/rocketchat-oauth2-server-config/admin/server/methods/updateOAuthApp.js new file mode 100644 index 000000000000..78a742b726f0 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/server/methods/updateOAuthApp.js @@ -0,0 +1,34 @@ +Meteor.methods({ + updateOAuthApp(applicationId, application) { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-oauth-apps')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'updateOAuthApp' }); + } + if (!_.isString(application.name) || application.name.trim() === '') { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { method: 'updateOAuthApp' }); + } + if (!_.isString(application.redirectUri) || application.redirectUri.trim() === '') { + throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { method: 'updateOAuthApp' }); + } + if (!_.isBoolean(application.active)) { + throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { method: 'updateOAuthApp' }); + } + const currentApplication = RocketChat.models.OAuthApps.findOne(applicationId); + if (currentApplication == null) { + throw new Meteor.Error('error-application-not-found', 'Application not found', { method: 'updateOAuthApp' }); + } + RocketChat.models.OAuthApps.update(applicationId, { + $set: { + name: application.name, + active: application.active, + redirectUri: application.redirectUri, + _updatedAt: new Date, + _updatedBy: RocketChat.models.Users.findOne(this.userId, { + fields: { + username: 1 + } + }) + } + }); + return RocketChat.models.OAuthApps.findOne(applicationId); + } +}); diff --git a/packages/rocketchat-oauth2-server-config/admin/server/publications/oauthApps.js b/packages/rocketchat-oauth2-server-config/admin/server/publications/oauthApps.js new file mode 100644 index 000000000000..33cc5f5ff955 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/admin/server/publications/oauthApps.js @@ -0,0 +1,9 @@ +Meteor.publish('oauthApps', function() { + if (!this.userId) { + return this.ready(); + } + if (!RocketChat.authz.hasPermission(this.userId, 'manage-oauth-apps')) { + this.error(Meteor.Error('error-not-allowed', 'Not allowed', { publish: 'oauthApps' })); + } + return RocketChat.models.OAuthApps.find(); +}); diff --git a/packages/rocketchat-oauth2-server-config/oauth/server/default-services.js b/packages/rocketchat-oauth2-server-config/oauth/server/default-services.js new file mode 100644 index 000000000000..2a5ab569110e --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/oauth/server/default-services.js @@ -0,0 +1,15 @@ +if (!RocketChat.models.OAuthApps.findOne('zapier')) { + RocketChat.models.OAuthApps.insert({ + _id: 'zapier', + name: 'Zapier', + active: true, + clientId: 'zapier', + clientSecret: 'RTK6TlndaCIolhQhZ7_KHIGOKj41RnlaOq_o-7JKwLr', + redirectUri: 'https://zapier.com/dashboard/auth/oauth/return/App32270API/', + _createdAt: new Date, + _createdBy: { + _id: 'system', + username: 'system' + } + }); +} diff --git a/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js b/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js new file mode 100644 index 000000000000..a93f9ae4baf0 --- /dev/null +++ b/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js @@ -0,0 +1,83 @@ +/*global OAuth2Server */ + +const oauth2server = new OAuth2Server({ + accessTokensCollectionName: 'rocketchat_oauth_access_tokens', + refreshTokensCollectionName: 'rocketchat_oauth_refresh_tokens', + authCodesCollectionName: 'rocketchat_oauth_auth_codes', + clientsCollection: RocketChat.models.OAuthApps.model, + debug: true +}); + +WebApp.connectHandlers.use(oauth2server.app); + +oauth2server.routes.get('/oauth/userinfo', function(req, res) { + if (req.headers.authorization == null) { + return res.sendStatus(401).send('No token'); + } + const accessToken = req.headers.authorization.replace('Bearer ', ''); + const token = oauth2server.oauth.model.AccessTokens.findOne({ + accessToken + }); + if (token == null) { + return res.sendStatus(401).send('Invalid Token'); + } + const user = RocketChat.models.Users.findOneById(token.userId); + if (user == null) { + return res.sendStatus(401).send('Invalid Token'); + } + return res.send({ + sub: user._id, + name: user.name, + email: user.emails[0].address, + email_verified: user.emails[0].verified, + department: '', + birthdate: '', + preffered_username: user.username, + updated_at: user._updatedAt, + picture: `${ Meteor.absoluteUrl() }avatar/${ user.username }` + }); +}); + +Meteor.publish('oauthClient', function(clientId) { + if (!this.userId) { + return this.ready(); + } + return RocketChat.models.OAuthApps.find({ + clientId, + active: true + }, { + fields: { + name: 1 + } + }); +}); + +RocketChat.API.v1.addAuthMethod(function() { + let headerToken = this.request.headers['authorization']; + const getToken = this.request.query.access_token; + if (headerToken != null) { + const matches = headerToken.match(/Bearer\s(\S+)/); + if (matches) { + headerToken = matches[1]; + } else { + headerToken = undefined; + } + } + const bearerToken = headerToken || getToken; + if (bearerToken == null) { + return; + } + const getAccessToken = Meteor.wrapAsync(oauth2server.oauth.model.getAccessToken, oauth2server.oauth.model); + const accessToken = getAccessToken(bearerToken); + if (accessToken == null) { + return; + } + if ((accessToken.expires != null) && accessToken.expires !== 0 && accessToken.expires < new Date()) { + return; + } + const user = RocketChat.models.Users.findOne(accessToken.userId); + if (user == null) { + return; + } + return { user: _.omit(user, '$loki') }; +}); diff --git a/packages/rocketchat-oauth2-server-config/package.js b/packages/rocketchat-oauth2-server-config/package.js index 1123513df500..83934c528c7b 100644 --- a/packages/rocketchat-oauth2-server-config/package.js +++ b/packages/rocketchat-oauth2-server-config/package.js @@ -20,33 +20,33 @@ Package.onUse(function(api) { //// General // // Server - api.addFiles('server/models/OAuthApps.coffee', 'server'); + api.addFiles('server/models/OAuthApps.js', 'server'); //// OAuth // // Server - api.addFiles('oauth/server/oauth2-server.coffee', 'server'); - api.addFiles('oauth/server/default-services.coffee', 'server'); + api.addFiles('oauth/server/oauth2-server.js', 'server'); + api.addFiles('oauth/server/default-services.js', 'server'); api.addFiles('oauth/client/stylesheets/oauth2.less', 'client'); // Client api.addFiles('oauth/client/oauth2-client.html', 'client'); - api.addFiles('oauth/client/oauth2-client.coffee', 'client'); + api.addFiles('oauth/client/oauth2-client.js', 'client'); //// Admin // // Client - api.addFiles('admin/client/startup.coffee', 'client'); - api.addFiles('admin/client/collection.coffee', 'client'); - api.addFiles('admin/client/route.coffee', 'client'); + api.addFiles('admin/client/startup.js', 'client'); + api.addFiles('admin/client/collection.js', 'client'); + api.addFiles('admin/client/route.js', 'client'); api.addFiles('admin/client/views/oauthApp.html', 'client'); - api.addFiles('admin/client/views/oauthApp.coffee', 'client'); + api.addFiles('admin/client/views/oauthApp.js', 'client'); api.addFiles('admin/client/views/oauthApps.html', 'client'); - api.addFiles('admin/client/views/oauthApps.coffee', 'client'); + api.addFiles('admin/client/views/oauthApps.js', 'client'); // Server - api.addFiles('admin/server/publications/oauthApps.coffee', 'server'); - api.addFiles('admin/server/methods/addOAuthApp.coffee', 'server'); - api.addFiles('admin/server/methods/updateOAuthApp.coffee', 'server'); - api.addFiles('admin/server/methods/deleteOAuthApp.coffee', 'server'); + api.addFiles('admin/server/publications/oauthApps.js', 'server'); + api.addFiles('admin/server/methods/addOAuthApp.js', 'server'); + api.addFiles('admin/server/methods/updateOAuthApp.js', 'server'); + api.addFiles('admin/server/methods/deleteOAuthApp.js', 'server'); }); diff --git a/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js b/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js index 682b00a23427..46c70d55f9eb 100644 --- a/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js +++ b/packages/rocketchat-oauth2-server-config/server/models/OAuthApps.js @@ -7,11 +7,11 @@ RocketChat.models.OAuthApps = new class extends RocketChat.models._Base { - // # FIND - // # findByRole: (role, options) -> - // # query = - // # roles: role + // FIND + // findByRole: (role, options) -> + // query = + // roles: role - // # return @find query, options + // return @find query, options - // # CREATE + // CREATE From c09c957093e6f9875aa781efcd1a26277903704a Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 18 May 2017 16:15:39 -0300 Subject: [PATCH 023/102] convert irc-to-js --- packages/rocketchat-irc/server/server.js | 455 +++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 packages/rocketchat-irc/server/server.js diff --git a/packages/rocketchat-irc/server/server.js b/packages/rocketchat-irc/server/server.js new file mode 100644 index 000000000000..6e085df64956 --- /dev/null +++ b/packages/rocketchat-irc/server/server.js @@ -0,0 +1,455 @@ +////// +// Assign values + +//Package availability +const IRC_AVAILABILITY = RocketChat.settings.get('IRC_Enabled'); + +// Cache prep +const net = Npm.require('net'); +const Lru = Npm.require('lru-cache'); +const MESSAGE_CACHE_SIZE = RocketChat.settings.get('IRC_Message_Cache_Size'); +const ircReceiveMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line +const ircSendMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line + +// IRC server +const IRC_PORT = RocketChat.settings.get('IRC_Port'); +const IRC_HOST = RocketChat.settings.get('IRC_Host'); + +const ircClientMap = {}; + +////// +// Core functionality + +const bind = function(f) { + const g = Meteor.bindEnvironment((self, ...args) => f.apply(self, args)); + return function(...args) { g(this, ...args); }; +}; + +const async = (f, ...args) => Meteor.wrapAsync(f)(...args); + +class IrcClient { + constructor(loginReq) { + this.loginReq = loginReq; + + this.user = this.loginReq.user; + ircClientMap[this.user._id] = this; + this.ircPort = IRC_PORT; + this.ircHost = IRC_HOST; + this.msgBuf = []; + + this.isConnected = false; + this.isDistroyed = false; + this.socket = new net.Socket; + this.socket.setNoDelay; + this.socket.setEncoding('utf-8'); + this.socket.setKeepAlive(true); + this.onConnect = bind(this.onConnect); + this.onClose = bind(this.onClose); + this.onTimeout = bind(this.onTimeout); + this.onError = bind(this.onError); + this.onReceiveRawMessage = bind(this.onReceiveRawMessage); + this.socket.on('data', this.onReceiveRawMessage); + this.socket.on('close', this.onClose); + this.socket.on('timeout', this.onTimeout); + this.socket.on('error', this.onError); + + this.isJoiningRoom = false; + this.receiveMemberListBuf = {}; + this.pendingJoinRoomBuf = []; + + this.successLoginMessageRegex = /RocketChat.settings.get('IRC_RegEx_successLogin');/; + this.failedLoginMessageRegex = /RocketChat.settings.get('IRC_RegEx_failedLogin');/; + this.receiveMessageRegex = /RocketChat.settings.get('IRC_RegEx_receiveMessage');/; + this.receiveMemberListRegex = /RocketChat.settings.get('IRC_RegEx_receiveMemberList');/; + this.endMemberListRegex = /RocketChat.settings.get('IRC_RegEx_endMemberList');/; + this.addMemberToRoomRegex = /RocketChat.settings.get('IRC_RegEx_addMemberToRoom');/; + this.removeMemberFromRoomRegex = /RocketChat.settings.get('IRC_RegEx_removeMemberFromRoom');/; + this.quitMemberRegex = /RocketChat.settings.get('IRC_RegEx_quitMember');/; + } + + connect(loginCb) { + this.loginCb = loginCb; + this.socket.connect(this.ircPort, this.ircHost, this.onConnect); + this.initRoomList(); + } + + disconnect() { + this.isDistroyed = true; + this.socket.destroy(); + } + + onConnect() { + console.log('[irc] onConnect -> '.yellow, this.user.username, 'connect success.'); + this.socket.write(`NICK ${ this.user.username }\r\n`); + this.socket.write(`USER ${ this.user.username } 0 * :${ this.user.name }\r\n`); + // message order could not make sure here + this.isConnected = true; + const messageBuf = this.msgBuf; + messageBuf.forEach(msg => { + this.socket.write(msg); + }); + } + + onClose() { + console.log('[irc] onClose -> '.yellow, this.user.username, 'connection close.'); + this.isConnected = false; + if (this.isDistroyed) { + delete ircClientMap[this.user._id]; + } else { + this.connect(); + } + } + + onTimeout() { + console.log('[irc] onTimeout -> '.yellow, this.user.username, 'connection timeout.', arguments); + } + + onError() { + console.log('[irc] onError -> '.yellow, this.user.username, 'connection error.', arguments); + } + + onReceiveRawMessage(data) { + data = data.toString().split('\n'); + + data.forEach(line => { + line = line.trim(); + console.log(`[${ this.ircHost }:${ this.ircPort }]:`, line); + + // Send heartbeat package to irc server + if (line.indexOf('PING') === 0) { + this.socket.write(line.replace('PING :', 'PONG ')); + return; + } + let matchResult = this.receiveMessageRegex.exec(line); + if (matchResult) { + this.onReceiveMessage(matchResult[1], matchResult[2], matchResult[3]); + return; + } + matchResult = this.receiveMemberListRegex.exec(line); + if (matchResult) { + this.onReceiveMemberList(matchResult[1], matchResult[2].split(' ')); + return; + } + matchResult = this.endMemberListRegex.exec(line); + if (matchResult) { + this.onEndMemberList(matchResult[1]); + return; + } + matchResult = this.addMemberToRoomRegex.exec(line); + if (matchResult) { + this.onAddMemberToRoom(matchResult[1], matchResult[2]); + return; + } + matchResult = this.removeMemberFromRoomRegex.exec(line); + if (matchResult) { + this.onRemoveMemberFromRoom(matchResult[1], matchResult[2]); + return; + } + matchResult = this.quitMemberRegex.exec(line); + if (matchResult) { + this.onQuitMember(matchResult[1]); + return; + } + matchResult = this.successLoginMessageRegex.exec(line); + if (matchResult) { + this.onSuccessLoginMessage(); + return; + } + matchResult = this.failedLoginMessageRegex.exec(line); + if (matchResult) { + this.onFailedLoginMessage(); + return; + } + }); + } + + onSuccessLoginMessage() { + console.log('[irc] onSuccessLoginMessage -> '.yellow); + if (this.loginCb) { + this.loginCb(null, this.loginReq); + } + } + + onFailedLoginMessage() { + console.log('[irc] onFailedLoginMessage -> '.yellow); + this.loginReq.allowed = false; + this.disconnect(); + if (this.loginCb) { + this.loginCb(null, this.loginReq); + } + } + + onReceiveMessage(source, target, content) { + let room; + const now = new Date; + const timestamp = now.getTime(); + let cacheKey = [source, target, content].join(','); + console.log('[irc] ircSendMessageCache.get -> '.yellow, 'key:', cacheKey, 'value:', ircSendMessageCache.get(cacheKey), 'ts:', timestamp - 1000); + if (ircSendMessageCache.get(cacheKey) > (timestamp - 1000)) { + return; + } else { + ircSendMessageCache.set(cacheKey, timestamp); + } + console.log('[irc] onReceiveMessage -> '.yellow, 'source:', source, 'target:', target, 'content:', content); + source = this.createUserWhenNotExist(source); + if (target[0] === '#') { + room = RocketChat.models.Rooms.findOneByName(target.substring(1)); + } else { + room = this.createDirectRoomWhenNotExist(source, this.user); + } + const message = { + msg: content, + ts: now + }; + cacheKey = `${ source.username }${ timestamp }`; + ircReceiveMessageCache.set(cacheKey, true); + console.log('[irc] ircReceiveMessageCache.set -> '.yellow, 'key:', cacheKey); + RocketChat.sendMessage(source, message, room); + } + + onReceiveMemberList(roomName, members) { + this.receiveMemberListBuf[roomName] = this.receiveMemberListBuf[roomName].concat(members); + } + + onEndMemberList(roomName) { + const newMembers = this.receiveMemberListBuf[roomName]; + console.log('[irc] onEndMemberList -> '.yellow, 'room:', roomName, 'members:', newMembers.join(',')); + const room = RocketChat.models.Rooms.findOneByNameAndType(roomName, 'c'); + if (!room) { + return; + } + const oldMembers = room.usernames; + const appendMembers = _.difference(newMembers, oldMembers); + const removeMembers = _.difference(oldMembers, newMembers); + appendMembers.forEach(member => this.createUserWhenNotExist(member)); + + RocketChat.models.Rooms.removeUsernamesById(room._id, removeMembers); + RocketChat.models.Rooms.addUsernamesById(room._id, appendMembers); + + this.isJoiningRoom = false; + roomName = this.pendingJoinRoomBuf.shift(); + if (roomName) { + this.joinRoom({ + t: 'c', + name: roomName + }); + } + } + + sendRawMessage(msg) { + console.log('[irc] sendRawMessage -> '.yellow, msg.slice(0, -2)); + if (this.isConnected) { + this.socket.write(msg); + } else { + this.msgBuf.push(msg); + } + } + + sendMessage(room, message) { + console.log('[irc] sendMessage -> '.yellow, 'userName:', message.u.username); + let target = ''; + if (room.t === 'c') { + target = `#${ room.name }`; + } else if (room.t === 'd') { + const usernames = room.usernames; + usernames.forEach(name => { + if (message.u.username !== name) { + target = name; + return; + } + }); + } + const cacheKey = [this.user.username, target, message.msg].join(','); + console.log('[irc] ircSendMessageCache.set -> '.yellow, 'key:', cacheKey, 'ts:', message.ts.getTime()); + ircSendMessageCache.set(cacheKey, message.ts.getTime()); + const msg = `PRIVMSG ${ target } :${ message.msg }\r\n`; + this.sendRawMessage(msg); + } + + initRoomList() { + const roomsCursor = RocketChat.models.Rooms.findByTypeContainingUsername('c', this.user.username, { fields: { name: 1, t: 1 }}); + const rooms = roomsCursor.fetch(); + + rooms.forEach(room => { + this.joinRoom(room); + }); + } + + joinRoom(room) { + let msg; + if (room.t !== 'c' || room.name === 'general') { + return; + } + if (this.isJoiningRoom) { + this.pendingJoinRoomBuf.push(room.name); + } else { + console.log('[irc] joinRoom -> '.yellow, 'roomName:', room.name, 'pendingJoinRoomBuf:', this.pendingJoinRoomBuf.join(',')); + msg = `JOIN #${ room.name }\r\n`; + this.receiveMemberListBuf[room.name] = []; + this.sendRawMessage(msg); + this.isJoiningRoom = true; + } + } + + leaveRoom(room) { + if (room.t !== 'c') { + return; + } + const msg = `PART #${ room.name }\r\n`; + this.sendRawMessage(msg); + } + + getMemberList(room) { + if (room.t !== 'c') { + return; + } + const msg = `NAMES #${ room.name }\r\n`; + this.receiveMemberListBuf[room.name] = []; + this.sendRawMessage(msg); + } + + onAddMemberToRoom(member, roomName) { + if (this.user.username === member) { + return; + } + console.log('[irc] onAddMemberToRoom -> '.yellow, 'roomName:', roomName, 'member:', member); + this.createUserWhenNotExist(member); + RocketChat.models.Rooms.addUsernameByName(roomName, member); + } + + onRemoveMemberFromRoom(member, roomName) { + console.log('[irc] onRemoveMemberFromRoom -> '.yellow, 'roomName:', roomName, 'member:', member); + RocketChat.models.Rooms.removeUsernameByName(roomName, member); + } + + onQuitMember(member) { + console.log('[irc] onQuitMember ->'.yellow, 'username:', member); + RocketChat.models.Rooms.removeUsernameFromAll(member); + Meteor.users.update({ name: member }, { $set: { status: 'offline' }}); + } + + createUserWhenNotExist(name) { + let user; + user = Meteor.users.findOne({ name }); + if (!user) { + console.log('[irc] createNotExistUser ->'.yellow, 'userName:', name); + Meteor.call('registerUser', { + email: `${ name }@rocketchat.org`, + pass: 'rocketchat', + name + }); + Meteor.users.update({ name }, { + $set: { + status: 'online', + username: name + } + }); + user = Meteor.users.findOne({ name }); + } + return user; + } + + createDirectRoomWhenNotExist(source, target) { + console.log('[irc] createDirectRoomWhenNotExist -> '.yellow, 'source:', source, 'target:', target); + const rid = [source._id, target._id].sort().join(''); + const now = new Date(); + RocketChat.models.Rooms.upsert({ _id: rid}, { + $set: { + usernames: [source.username, target.username] + }, + $setOnInsert: { + t: 'd', + msgs: 0, + ts: now + } + }); + RocketChat.models.Subscriptions.upsert({ rid, $and: [{ 'u._id': target._id}]}, { + $setOnInsert: { + name: source.username, + t: 'd', + open: false, + alert: false, + unread: 0, + u: { _id: target._id, username: target.username }} + }); + return { t: 'd', _id: rid }; + } +} + +IrcClient.getByUid = function(uid) { + return ircClientMap[uid]; +}; + +IrcClient.create = function(login) { + let ircClient; + if (login.user == null) { + return login; + } + if (!(login.user._id in ircClientMap)) { + ircClient = new IrcClient(login); + return async(ircClient.connect); + } + return login; +}; + +class IrcLoginer { + constructor(login) { + console.log('[irc] validateLogin -> '.yellow, login); + return IrcClient.create(login); + } +} + +class IrcSender { + constructor(message) { + const name = message.u.username; + const timestamp = message.ts.getTime(); + const cacheKey = `${ name }${ timestamp }`; + if (ircReceiveMessageCache.get(cacheKey)) { + return message; + } + const room = RocketChat.models.Rooms.findOneById(message.rid, { fields: { name: 1, usernames: 1, t: 1 }}); + const ircClient = IrcClient.getByUid(message.u._id); + ircClient.sendMessage(room, message); + return message; + } + +} + +class IrcRoomJoiner { + constructor(user, room) { + const ircClient = IrcClient.getByUid(user._id); + ircClient.joinRoom(room); + return room; + } + +} + +class IrcRoomLeaver { + constructor(user, room) { + const ircClient = IrcClient.getByUid(user._id); + ircClient.leaveRoom(room); + return room; + } + +} + +class IrcLogoutCleanUper { + constructor(user) { + const ircClient = IrcClient.getByUid(user._id); + ircClient.disconnect(); + return user; + } +} + +////// +// Make magic happen + +// Only proceed if the package has been enabled +if (IRC_AVAILABILITY === true) { + RocketChat.callbacks.add('beforeValidateLogin', IrcLoginer, RocketChat.callbacks.priority.LOW, 'irc-loginer'); + RocketChat.callbacks.add('beforeSaveMessage', IrcSender, RocketChat.callbacks.priority.LOW, 'irc-sender'); + RocketChat.callbacks.add('beforeJoinRoom', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner'); + RocketChat.callbacks.add('beforeCreateChannel', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner-create-channel'); + RocketChat.callbacks.add('beforeLeaveRoom', IrcRoomLeaver, RocketChat.callbacks.priority.LOW, 'irc-room-leaver'); + RocketChat.callbacks.add('afterLogoutCleanUp', IrcLogoutCleanUper, RocketChat.callbacks.priority.LOW, 'irc-clean-up'); +} From 21f09e85de6b04ad83f2027df0ebe9cddb23d3a4 Mon Sep 17 00:00:00 2001 From: pmb Date: Fri, 19 May 2017 14:38:34 +0200 Subject: [PATCH 024/102] do not store password if LDAP_Login_Fallback is off --- packages/rocketchat-ldap/server/loginHandler.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-ldap/server/loginHandler.js b/packages/rocketchat-ldap/server/loginHandler.js index 25e5af9045c2..6d79daa3225d 100644 --- a/packages/rocketchat-ldap/server/loginHandler.js +++ b/packages/rocketchat-ldap/server/loginHandler.js @@ -123,9 +123,12 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { 'services.resume.loginTokens': Accounts._hashStampedToken(stampedToken) } }); - syncUserData(user, ldapUser); - Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false}); + + if (RocketChat.settings.get('LDAP_Login_Fallback') !== true) { + Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false}); + } + return { userId: user._id, token: stampedToken.token From d39f96b155cd2e690e16afae31a31c7771e56674 Mon Sep 17 00:00:00 2001 From: pmb Date: Fri, 19 May 2017 14:58:28 +0200 Subject: [PATCH 025/102] restore whitespace --- packages/rocketchat-ldap/server/loginHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rocketchat-ldap/server/loginHandler.js b/packages/rocketchat-ldap/server/loginHandler.js index 6d79daa3225d..81bf8bcb59a0 100644 --- a/packages/rocketchat-ldap/server/loginHandler.js +++ b/packages/rocketchat-ldap/server/loginHandler.js @@ -123,6 +123,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { 'services.resume.loginTokens': Accounts._hashStampedToken(stampedToken) } }); + syncUserData(user, ldapUser); if (RocketChat.settings.get('LDAP_Login_Fallback') !== true) { From 8a23424cb0d53313c85c1519b82909cf82eb094b Mon Sep 17 00:00:00 2001 From: pmb Date: Fri, 19 May 2017 15:19:36 +0200 Subject: [PATCH 026/102] only store passwords if LDAP_Login_Fallback is on --- packages/rocketchat-ldap/server/loginHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-ldap/server/loginHandler.js b/packages/rocketchat-ldap/server/loginHandler.js index 81bf8bcb59a0..d12240a33d77 100644 --- a/packages/rocketchat-ldap/server/loginHandler.js +++ b/packages/rocketchat-ldap/server/loginHandler.js @@ -126,7 +126,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { syncUserData(user, ldapUser); - if (RocketChat.settings.get('LDAP_Login_Fallback') !== true) { + if (RocketChat.settings.get('LDAP_Login_Fallback') === true) { Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false}); } From a9b89bdd0c1747d2d672b13b15dbc7ab1cc60ee7 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 22 May 2017 16:35:59 -0300 Subject: [PATCH 027/102] Add backwards compatibility --- lib/fileUpload.js | 3 +++ .../server/config/configFileUploadAmazonS3.js | 12 +++++++++++- .../server/config/configFileUploadFileSystem.js | 2 ++ .../server/config/configFileUploadGoogleStorage.js | 9 +++++++++ .../server/config/configFileUploadGridFS.js | 6 ------ .../rocketchat-file-upload/server/lib/FileUpload.js | 4 +++- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/fileUpload.js b/lib/fileUpload.js index f5eafb6e0985..ae75ffa40443 100644 --- a/lib/fileUpload.js +++ b/lib/fileUpload.js @@ -73,6 +73,9 @@ if (UploadFS) { } }); + // DEPRECATED: backwards compatibility (remove) + UploadFS.getStores()['rocketchat_uploads'] = UploadFS.getStores()['GridFS:Uploads']; + Meteor.fileStoreAvatar = new UploadFS.store.GridFS({ collection: RocketChat.models.Avatars.model, name: 'GridFS:Avatars', diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js index ebce1c102a61..594f7c5574c7 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js @@ -1,4 +1,5 @@ -/* globals Slingshot, FileUpload, AWS, FileUploadClass */ +/* globals Slingshot, FileUpload, AWS */ +import { FileUploadClass } from '../lib/FileUpload'; import AWS4 from '../lib/AWS4.js'; let S3accessKey; @@ -47,6 +48,15 @@ const deleteFile = function(file) { request.send(); }; +// DEPRECATED: backwards compatibility (remove) +new FileUploadClass({ + name: 's3', + model: 'Uploads', + + get: getFile, + delete: deleteFile +}); + new FileUploadClass({ name: 'AmazonS3:Uploads', diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js index 9ffdb26a7a9c..680019a966ba 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js @@ -121,6 +121,8 @@ const createFileSystemStore = _.debounce(function() { transformWrite }); + UploadFS.getStores()['fileSystem'] = UploadFS.getStores()[FileSystemUploads.name]; + FileSystemAvatars.store = new UploadFS.store.Local({ path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos', collection: FileSystemAvatars.model.model, diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js index c9cd841aacb8..af41098cdc7a 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGoogleStorage.js @@ -91,6 +91,15 @@ const deleteFile = function(file) { HTTP.call('DELETE', url); }; +// DEPRECATED: backwards compatibility (remove) +new FileUploadClass({ + name: 'googleCloudStorage', + model: 'Uploads', + + get: getFile, + delete: deleteFile +}); + new FileUploadClass({ name: 'GoogleCloudStorage:Uploads', diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js index e26b7cc7d6b4..49d6b72e61b9 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js @@ -135,9 +135,6 @@ const insert = function(file, stream, cb) { new FileUploadClass({ name: 'GridFS:Uploads', - getStore() { - return Meteor.fileStore; - }, get(file, req, res) { file = FileUpload.addExtensionTo(file); @@ -155,9 +152,6 @@ new FileUploadClass({ new FileUploadClass({ name: 'GridFS:Avatars', - getStore() { - return Meteor.fileStoreAvatar; - }, get(file, req, res) { const reqModifiedHeader = req.headers['if-modified-since']; diff --git a/packages/rocketchat-file-upload/server/lib/FileUpload.js b/packages/rocketchat-file-upload/server/lib/FileUpload.js index f606d0ad0d4e..b3e616859c29 100644 --- a/packages/rocketchat-file-upload/server/lib/FileUpload.js +++ b/packages/rocketchat-file-upload/server/lib/FileUpload.js @@ -1,3 +1,5 @@ +/* globals UploadFS */ + import mime from 'mime-type/with-db'; Object.assign(FileUpload, { @@ -52,7 +54,7 @@ export class FileUploadClass { constructor({ name, model, store, get, insert, getStore }) { this.name = name; this.model = model || this.getModelFromName(); - this._store = store; + this._store = store || UploadFS.getStore(name); this.get = get; this.insert = insert; From 4431c9da157704ee909e723acc973285923fc35b Mon Sep 17 00:00:00 2001 From: bbrauns Date: Tue, 23 May 2017 10:34:34 +0200 Subject: [PATCH 028/102] Edit de.i18n.json and en.i18n.json regarding the description of the LDAP_Sync_User_Data_FieldMap --- packages/rocketchat-i18n/i18n/de.i18n.json | 4 ++-- packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 24e7b29e401d..48c5d8700a7a 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -704,7 +704,7 @@ "LDAP_Sync_User_Data": "Daten synchronisieren", "LDAP_Sync_User_Data_Description": "Bei der Anmeldung die Benutzerdaten mit dem Server synchronisieren (Beispiel: Name, E-Mail).", "LDAP_Sync_User_Data_FieldMap": "Nutzerdaten-Feldkarte", - "LDAP_Sync_User_Data_FieldMap_Description": "Konfigurieren Sie, wie Benutzer-Account-Felder (wie die E-Mail-Adresse) aus einem LDAP-Datensatz (falls gefunden) geladen werden.
Beispiel: {\"cn\":\"name\", \"mail\":\"email\"} nimmt einen von Menschen lesbaren Namen von dem cn-Attribut und die E-Mail-Adresse vom Mail-Attribut.
Verfügbare Felder beinhalten den Namen und die E-Mail-Adresse.", + "LDAP_Sync_User_Data_FieldMap_Description": "Konfigurieren Sie, wie Benutzer-Account-Felder (wie die E-Mail-Adresse) aus einem LDAP-Datensatz (falls gefunden) geladen werden.
Beispiel: {\"cn\":\"name\", \"mail\":\"email\"} nimmt einen von Menschen lesbaren Namen von dem cn-Attribut und die E-Mail-Adresse vom Mail-Attribut. Zusätzlich ist die Verwendung von Variablen möglich, wie z.B.: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }`. Hierbei wird eine Kombination des Vor- und Nachnamens verwendet.
Verfügbare Felder in Rocket.Chat sind `name` und `email`.", "LDAP_Sync_Users": "Benutzer synchronisieren", "LDAP_Test_Connection": "Testverbindung", "LDAP_Unique_Identifier_Field": "Eindeutige Kennung des Felds", @@ -1375,4 +1375,4 @@ "your_message_optional": "ihre optionale Nachricht", "Your_password_is_wrong": "Falsches Passwort", "Your_push_was_sent_to_s_devices": "Die Push-Nachricht wurde an %s Geräte gesendet." -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 6500fda913b1..bd8647f07a07 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -875,7 +875,7 @@ "LDAP_Sync_User_Data": "Sync Data", "LDAP_Sync_User_Data_Description": "Keep user data in sync with server on login (eg: name, email).", "LDAP_Sync_User_Data_FieldMap": "User Data Field Map", - "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute.
Available fields include `name`, and `email`.", + "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Additionally it is possible to use variables, for example: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }` uses a combination of the user's first name and last name for the rocket chat `name` field.
Available fields in Rocket.Chat: `name`, and `email`.", "LDAP_Sync_Users": "Sync Users", "LDAP_Test_Connection": "Test Connection", "LDAP_Unique_Identifier_Field": "Unique Identifier Field", From 099e3db1b09a07de6ac455ae4bc6215ca99d1f22 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 23 May 2017 08:49:18 -0300 Subject: [PATCH 029/102] Increase unread message on @here mention --- packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index 4d1d5d3fe0fc..aab32579decd 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -44,6 +44,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { RocketChat.models.Subscriptions.incUnreadOfDirectForRoomIdExcludingUserId(message.rid, message.u._id, 1); } else { let toAll = false; + let toHere = false; const mentionIds = []; const highlightsIds = []; const highlights = RocketChat.models.Users.findUsersByUsernamesWithHighlights(room.usernames, { fields: { '_id': 1, 'settings.preferences.highlights': 1 }}).fetch(); @@ -53,6 +54,9 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { if (!toAll && mention._id === 'all') { toAll = true; } + if (!toHere && mention._id === 'here') { + toHere = true; + } if (mention._id !== message.u._id) { mentionIds.push(mention._id); } @@ -67,7 +71,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { } }); - if (toAll) { + if (toAll || toHere) { RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { RocketChat.models.Subscriptions.incUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds)))); From 28956cede49c6eb8d8b7cc411578d072ec1a708a Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Tue, 23 May 2017 14:29:34 -0300 Subject: [PATCH 030/102] remove jasmine tests --- .../jasmine/client/unit/katex.spec.coffee | 4 ---- .../server/unit/models/_Base.spec.coffee | 24 ------------------- 2 files changed, 28 deletions(-) delete mode 100644 packages/rocketchat-katex/tests/jasmine/client/unit/katex.spec.coffee delete mode 100644 packages/rocketchat-lib/tests/jasmine/server/unit/models/_Base.spec.coffee diff --git a/packages/rocketchat-katex/tests/jasmine/client/unit/katex.spec.coffee b/packages/rocketchat-katex/tests/jasmine/client/unit/katex.spec.coffee deleted file mode 100644 index 3130fb6012d0..000000000000 --- a/packages/rocketchat-katex/tests/jasmine/client/unit/katex.spec.coffee +++ /dev/null @@ -1,4 +0,0 @@ -describe 'rocketchat:katex Client', -> - - it 'should exist', -> - expect(RocketChat.katex).toBeDefined() diff --git a/packages/rocketchat-lib/tests/jasmine/server/unit/models/_Base.spec.coffee b/packages/rocketchat-lib/tests/jasmine/server/unit/models/_Base.spec.coffee deleted file mode 100644 index 8ff995ea4950..000000000000 --- a/packages/rocketchat-lib/tests/jasmine/server/unit/models/_Base.spec.coffee +++ /dev/null @@ -1,24 +0,0 @@ -describe 'rocketchat:lib Server | Models | Base', -> - - beforeEach -> - MeteorStubs.install() - this.obj = new RocketChat.models._Base - - afterEach -> - MeteorStubs.uninstall() - - it 'should exist', -> - expect(this.obj).toBeDefined() - - it 'should provide a basename for collections', -> - expect(typeof this.obj._baseName()).toBe('string') - - it 'should carry a Mongo.Collection object when initialized', -> - expect(this.obj.model).toBeFalsy() - expect(this.obj._initModel('carry')).toBeTruthy() - expect(typeof this.obj.model).toBe('object') - - it 'should apply a basename to the Mongo.Collection created', -> - name = 'apply' - expect(this.obj._initModel(name)).toBeTruthy() - expect(this.obj.model._name).toBe(this.obj._baseName() + name) From ef672679ef3575f8bf478964fe0ee6dc717bd405 Mon Sep 17 00:00:00 2001 From: jm-factorin Date: Wed, 24 May 2017 11:00:20 +0800 Subject: [PATCH 031/102] Add support for carriage return in markdown code blocks --- packages/rocketchat-markdown/markdowncode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-markdown/markdowncode.js b/packages/rocketchat-markdown/markdowncode.js index 1ff5ca80f787..65f00ce79ff1 100644 --- a/packages/rocketchat-markdown/markdowncode.js +++ b/packages/rocketchat-markdown/markdowncode.js @@ -51,12 +51,12 @@ class MarkdownCode { } // Separate text in code blocks and non code blocks - const msgParts = message.html.split(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(.*\n?)$/gm); + const msgParts = message.html.split(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\r|\n)*?)```)(.*\n?)$/gm); for (let index = 0; index < msgParts.length; index++) { // Verify if this part is code const part = msgParts[index]; - const codeMatch = part.match(/^```(.*[\n\ ]?)([\s\S]*?)```+?$/); + const codeMatch = part.match(/^```(.*[\r\n\ ]?)([\s\S]*?)```+?$/); if (codeMatch != null) { // Process highlight if this part is code From aeb73e2dc234429600fab461ea94fb5757852bd7 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Wed, 24 May 2017 11:28:54 -0300 Subject: [PATCH 032/102] fix review --- packages/rocketchat-irc/server/server.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/rocketchat-irc/server/server.js b/packages/rocketchat-irc/server/server.js index 6e085df64956..5b32aad87831 100644 --- a/packages/rocketchat-irc/server/server.js +++ b/packages/rocketchat-irc/server/server.js @@ -85,9 +85,7 @@ class IrcClient { // message order could not make sure here this.isConnected = true; const messageBuf = this.msgBuf; - messageBuf.forEach(msg => { - this.socket.write(msg); - }); + messageBuf.forEach(msg => this.socket.write(msg)); } onClose() { @@ -329,8 +327,7 @@ class IrcClient { } createUserWhenNotExist(name) { - let user; - user = Meteor.users.findOne({ name }); + let user = Meteor.users.findOne({ name }); if (!user) { console.log('[irc] createNotExistUser ->'.yellow, 'userName:', name); Meteor.call('registerUser', { @@ -381,12 +378,11 @@ IrcClient.getByUid = function(uid) { }; IrcClient.create = function(login) { - let ircClient; if (login.user == null) { return login; } if (!(login.user._id in ircClientMap)) { - ircClient = new IrcClient(login); + const ircClient = new IrcClient(login); return async(ircClient.connect); } return login; From fcc13c56f4e45c6d35dc914a3e57ee0d3e105f4a Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 24 May 2017 06:39:05 -0300 Subject: [PATCH 033/102] removed files --- .../client/admin/adminImport.coffee | 24 -- .../client/admin/adminImportPrepare.coffee | 152 ------------- .../client/admin/adminImportProgress.coffee | 41 ---- .../rocketchat-importer/lib/_importer.coffee | 1 - .../rocketchat-importer/lib/importTool.coffee | 9 - packages/rocketchat-importer/package.js | 39 ++-- .../server/classes/ImporterBase.coffee | 207 ------------------ .../server/classes/ImporterProgress.coffee | 9 - .../classes/ImporterProgressStep.coffee | 16 -- .../server/classes/ImporterSelection.coffee | 9 - .../classes/ImporterSelectionChannel.coffee | 12 - .../classes/ImporterSelectionUser.coffee | 13 -- .../server/methods/getImportProgress.coffee | 12 - .../server/methods/getSelectionData.coffee | 17 -- .../server/methods/restartImport.coffee | 17 -- .../server/methods/setupImporter.coffee | 19 -- .../server/methods/startImport.coffee | 19 -- .../server/models/Imports.coffee | 3 - .../server/models/RawImports.coffee | 3 - .../server/startup/setImportsToInvalid.coffee | 8 - 20 files changed, 19 insertions(+), 611 deletions(-) delete mode 100644 packages/rocketchat-importer/client/admin/adminImport.coffee delete mode 100644 packages/rocketchat-importer/client/admin/adminImportPrepare.coffee delete mode 100644 packages/rocketchat-importer/client/admin/adminImportProgress.coffee delete mode 100644 packages/rocketchat-importer/lib/_importer.coffee delete mode 100644 packages/rocketchat-importer/lib/importTool.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterBase.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterProgress.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterSelection.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee delete mode 100644 packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee delete mode 100644 packages/rocketchat-importer/server/methods/getImportProgress.coffee delete mode 100644 packages/rocketchat-importer/server/methods/getSelectionData.coffee delete mode 100644 packages/rocketchat-importer/server/methods/restartImport.coffee delete mode 100644 packages/rocketchat-importer/server/methods/setupImporter.coffee delete mode 100644 packages/rocketchat-importer/server/methods/startImport.coffee delete mode 100644 packages/rocketchat-importer/server/models/Imports.coffee delete mode 100644 packages/rocketchat-importer/server/models/RawImports.coffee delete mode 100644 packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee diff --git a/packages/rocketchat-importer/client/admin/adminImport.coffee b/packages/rocketchat-importer/client/admin/adminImport.coffee deleted file mode 100644 index c4a8506248e4..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImport.coffee +++ /dev/null @@ -1,24 +0,0 @@ -Template.adminImport.helpers - isAdmin: -> - return RocketChat.authz.hasRole(Meteor.userId(), 'admin') - isImporters: -> - return Object.keys(Importer.Importers).length > 0 - getDescription: (importer) -> - return TAPi18n.__('Importer_From_Description', { from: importer.name }) - importers: -> - importers = [] - _.each Importer.Importers, (importer, key) -> - importer.key = key - importers.push importer - return importers - -Template.adminImport.events - 'click .start-import': (event) -> - importer = @ - - Meteor.call 'setupImporter', importer.key, (error, data) -> - if error - console.log t('importer_setup_error'), importer.key, error - return handleError(error) - else - FlowRouter.go '/admin/import/prepare/' + importer.key diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee b/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee deleted file mode 100644 index 823418efed04..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee +++ /dev/null @@ -1,152 +0,0 @@ -import toastr from 'toastr' -Template.adminImportPrepare.helpers - isAdmin: -> - return RocketChat.authz.hasRole(Meteor.userId(), 'admin') - importer: -> - importerKey = FlowRouter.getParam('importer') - importer = undefined - _.each Importer.Importers, (i, key) -> - i.key = key - if key == importerKey - importer = i - - return importer - isLoaded: -> - return Template.instance().loaded.get() - isPreparing: -> - return Template.instance().preparing.get() - users: -> - return Template.instance().users.get() - channels: -> - return Template.instance().channels.get() - -Template.adminImportPrepare.events - 'change .import-file-input': (event, template) -> - importer = @ - return if not importer.key - - e = event.originalEvent or event - files = e.target.files - if not files or files.length is 0 - files = e.dataTransfer?.files or [] - - for blob in files - template.preparing.set true - - reader = new FileReader() - reader.readAsDataURL(blob) - reader.onloadend = -> - Meteor.call 'prepareImport', importer.key, reader.result, blob.type, blob.name, (error, data) -> - if error - toastr.error t('Invalid_Import_File_Type') - template.preparing.set false - return - - if !data - console.warn 'The importer ' + importer.key + ' is not set up correctly, as it did not return any data.' - toastr.error t('Importer_not_setup') - template.preparing.set false - return - - if data.step - console.warn 'Invalid file, contains `data.step`.', data - toastr.error t('Invalid_Export_File', importer.key) - template.preparing.set false - return - - template.users.set data.users - template.channels.set data.channels - template.loaded.set true - template.preparing.set false - - 'click .button.start': (event, template) -> - btn = this - $(btn).prop "disabled", true - importer = @ - for user in template.users.get() - user.do_import = $("[name=#{user.user_id}]").is(':checked') - - for channel in template.channels.get() - channel.do_import = $("[name=#{channel.channel_id}]").is(':checked') - - Meteor.call 'startImport', FlowRouter.getParam('importer'), { users: template.users.get(), channels: template.channels.get() }, (error, data) -> - if error - console.warn 'Error on starting the import:', error - return handleError(error) - else - FlowRouter.go '/admin/import/progress/' + FlowRouter.getParam('importer') - - 'click .button.restart': (event, template) -> - Meteor.call 'restartImport', FlowRouter.getParam('importer'), (error, data) -> - if error - console.warn 'Error while restarting the import:', error - handleError(error) - return - - template.users.set [] - template.channels.set [] - template.loaded.set false - - 'click .button.uncheck-deleted-users': (event, template) -> - for user in template.users.get() when user.is_deleted - $("[name=#{user.user_id}]").attr('checked', false); - - 'click .button.uncheck-archived-channels': (event, template) -> - for channel in template.channels.get() when channel.is_archived - $("[name=#{channel.channel_id}]").attr('checked', false); - - -Template.adminImportPrepare.onCreated -> - instance = @ - @preparing = new ReactiveVar true - @loaded = new ReactiveVar false - @users = new ReactiveVar [] - @channels = new ReactiveVar [] - - loadSelection = (progress) -> - if progress?.step - switch progress.step - #When the import is running, take the user to the progress page - when 'importer_importing_started', 'importer_importing_users', 'importer_importing_channels', 'importer_importing_messages', 'importer_finishing' - FlowRouter.go '/admin/import/progress/' + FlowRouter.getParam('importer') - # when the import is done, restart it (new instance) - when 'importer_user_selection' - Meteor.call 'getSelectionData', FlowRouter.getParam('importer'), (error, data) -> - if error - handleError error - instance.users.set data.users - instance.channels.set data.channels - instance.loaded.set true - instance.preparing.set false - when 'importer_new' - instance.preparing.set false - else - Meteor.call 'restartImport', FlowRouter.getParam('importer'), (error, progress) -> - if error - handleError(error) - loadSelection(progress) - else - console.warn 'Invalid progress information.', progress - - # Load the initial progress to determine what we need to do - if FlowRouter.getParam('importer') - Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> - if error - console.warn 'Error while getting the import progress:', error - handleError error - return - - # if the progress isnt defined, that means there currently isn't an instance - # of the importer, so we need to create it - if progress is undefined - Meteor.call 'setupImporter', FlowRouter.getParam('importer'), (err, data) -> - if err - handleError(err) - instance.preparing.set false - loadSelection(data) - else - # Otherwise, we might need to do something based upon the current step - # of the import - loadSelection(progress) - else - FlowRouter.go '/admin/import' diff --git a/packages/rocketchat-importer/client/admin/adminImportProgress.coffee b/packages/rocketchat-importer/client/admin/adminImportProgress.coffee deleted file mode 100644 index d7e9fe7511f4..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImportProgress.coffee +++ /dev/null @@ -1,41 +0,0 @@ -import toastr from 'toastr' -Template.adminImportProgress.helpers - step: -> - return Template.instance().step.get() - completed: -> - return Template.instance().completed.get() - total: -> - return Template.instance().total.get() - -Template.adminImportProgress.onCreated -> - instance = @ - @step = new ReactiveVar t('Loading...') - @completed = new ReactiveVar 0 - @total = new ReactiveVar 0 - @updateProgress = -> - if FlowRouter.getParam('importer') isnt '' - Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> - if error - console.warn 'Error on getting the import progress:', error - handleError error - return - - if progress - if progress.step is 'importer_done' - toastr.success t(progress.step[0].toUpperCase() + progress.step.slice(1)) - FlowRouter.go '/admin/import' - else if progress.step is 'importer_import_failed' - toastr.error t(progress.step[0].toUpperCase() + progress.step.slice(1)) - FlowRouter.go '/admin/import/prepare/' + FlowRouter.getParam('importer') - else - instance.step.set t(progress.step[0].toUpperCase() + progress.step.slice(1)) - instance.completed.set progress.count.completed - instance.total.set progress.count.total - setTimeout(() -> - instance.updateProgress() - , 100) - else - toastr.warning t('Importer_not_in_progress') - FlowRouter.go '/admin/import/prepare/' + FlowRouter.getParam('importer') - - instance.updateProgress() diff --git a/packages/rocketchat-importer/lib/_importer.coffee b/packages/rocketchat-importer/lib/_importer.coffee deleted file mode 100644 index 625541b61604..000000000000 --- a/packages/rocketchat-importer/lib/_importer.coffee +++ /dev/null @@ -1 +0,0 @@ -Importer = {} diff --git a/packages/rocketchat-importer/lib/importTool.coffee b/packages/rocketchat-importer/lib/importTool.coffee deleted file mode 100644 index 99514e045e9a..000000000000 --- a/packages/rocketchat-importer/lib/importTool.coffee +++ /dev/null @@ -1,9 +0,0 @@ -Importer.Importers = {} - -Importer.addImporter = (name, importer, options) -> - if not Importer.Importers[name]? - Importer.Importers[name] = - name: options.name - importer: importer - mimeType: options.mimeType - warnings: options.warnings diff --git a/packages/rocketchat-importer/package.js b/packages/rocketchat-importer/package.js index 5820b21e2ac7..0f9dffc9a96b 100644 --- a/packages/rocketchat-importer/package.js +++ b/packages/rocketchat-importer/package.js @@ -9,7 +9,6 @@ Package.onUse(function(api) { api.use([ 'ecmascript', 'templating', - 'coffeescript', 'check', 'rocketchat:lib' ]); @@ -18,37 +17,37 @@ Package.onUse(function(api) { api.use('templating', 'client'); //Import Framework - api.addFiles('lib/_importer.coffee'); - api.addFiles('lib/importTool.coffee'); - api.addFiles('server/classes/ImporterBase.coffee', 'server'); - api.addFiles('server/classes/ImporterProgress.coffee', 'server'); - api.addFiles('server/classes/ImporterProgressStep.coffee', 'server'); - api.addFiles('server/classes/ImporterSelection.coffee', 'server'); - api.addFiles('server/classes/ImporterSelectionChannel.coffee', 'server'); - api.addFiles('server/classes/ImporterSelectionUser.coffee', 'server'); + api.addFiles('lib/_importer.js'); + api.addFiles('lib/importTool.js'); + api.addFiles('server/classes/ImporterBase.js', 'server'); + api.addFiles('server/classes/ImporterProgress.js', 'server'); + api.addFiles('server/classes/ImporterProgressStep.js', 'server'); + api.addFiles('server/classes/ImporterSelection.js', 'server'); + api.addFiles('server/classes/ImporterSelectionChannel.js', 'server'); + api.addFiles('server/classes/ImporterSelectionUser.js', 'server'); //Database models - api.addFiles('server/models/Imports.coffee', 'server'); - api.addFiles('server/models/RawImports.coffee', 'server'); + api.addFiles('server/models/Imports.js', 'server'); + api.addFiles('server/models/RawImports.js', 'server'); //Server methods - api.addFiles('server/methods/getImportProgress.coffee', 'server'); - api.addFiles('server/methods/getSelectionData.coffee', 'server'); + api.addFiles('server/methods/getImportProgress.js', 'server'); + api.addFiles('server/methods/getSelectionData.js', 'server'); api.addFiles('server/methods/prepareImport.js', 'server'); - api.addFiles('server/methods/restartImport.coffee', 'server'); - api.addFiles('server/methods/setupImporter.coffee', 'server'); - api.addFiles('server/methods/startImport.coffee', 'server'); + api.addFiles('server/methods/restartImport.js', 'server'); + api.addFiles('server/methods/setupImporter.js', 'server'); + api.addFiles('server/methods/startImport.js', 'server'); //Client api.addFiles('client/admin/adminImport.html', 'client'); - api.addFiles('client/admin/adminImport.coffee', 'client'); + api.addFiles('client/admin/adminImport.js', 'client'); api.addFiles('client/admin/adminImportPrepare.html', 'client'); - api.addFiles('client/admin/adminImportPrepare.coffee', 'client'); + api.addFiles('client/admin/adminImportPrepare.js', 'client'); api.addFiles('client/admin/adminImportProgress.html', 'client'); - api.addFiles('client/admin/adminImportProgress.coffee', 'client'); + api.addFiles('client/admin/adminImportProgress.js', 'client'); //Imports database records cleanup, mark all as not valid. - api.addFiles('server/startup/setImportsToInvalid.coffee', 'server'); + api.addFiles('server/startup/setImportsToInvalid.js', 'server'); api.export('Importer'); }); diff --git a/packages/rocketchat-importer/server/classes/ImporterBase.coffee b/packages/rocketchat-importer/server/classes/ImporterBase.coffee deleted file mode 100644 index 197e76d48ee8..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterBase.coffee +++ /dev/null @@ -1,207 +0,0 @@ -# Base class for all Importers. -# -# @example How to subclass an importer -# class ExampleImporter extends RocketChat.importTool._baseImporter -# constructor: -> -# super('Name of Importer', 'Description of the importer, use i18n string.', new RegExp('application\/.*?zip')) -# prepare: (uploadedFileData, uploadedFileContentType, uploadedFileName) => -# super -# startImport: (selectedUsersAndChannels) => -# super -# getProgress: => -# #return the progress report, tbd what is expected -# @version 1.0.0 -Importer.Base = class Importer.Base - @MaxBSONSize = 8000000 - @http = Npm.require 'http' - @https = Npm.require 'https' - - @getBSONSize: (object) -> - # The max BSON object size we can store in MongoDB is 16777216 bytes - # but for some reason the mongo instanace which comes with meteor - # errors out for anything close to that size. So, we are rounding it - # down to 8000000 bytes. - BSON = require('bson').native().BSON - bson = new BSON() - bson.calculateObjectSize object - - @getBSONSafeArraysFromAnArray: (theArray) -> - BSONSize = Importer.Base.getBSONSize theArray - maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Importer.Base.MaxBSONSize))) - safeArrays = [] - i = 0 - while i < theArray.length - safeArrays.push(theArray.slice(i, i += maxSize)) - return safeArrays - - # Constructs a new importer, adding an empty collection, AdmZip property, and empty users & channels - # - # @param [String] name the name of the Importer - # @param [String] description the i18n string which describes the importer - # @param [String] mimeType the of the expected file type - # - constructor: (@name, @description, @mimeType) -> - @logger = new Logger("#{@name} Importer", {}); - @progress = new Importer.Progress @name - @collection = Importer.RawImports - @AdmZip = Npm.require 'adm-zip' - @getFileType = Npm.require 'file-type' - importId = Importer.Imports.insert { 'type': @name, 'ts': Date.now(), 'status': @progress.step, 'valid': true, 'user': Meteor.user()._id } - @importRecord = Importer.Imports.findOne importId - @users = {} - @channels = {} - @messages = {} - - # Takes the uploaded file and extracts the users, channels, and messages from it. - # - # @param [String] dataURI a base64 string of the uploaded file - # @param [String] sentContentType the file type - # @param [String] fileName the name of the uploaded file - # - # @return [Importer.Selection] Contains two properties which are arrays of objects, `channels` and `users`. - # - prepare: (dataURI, sentContentType, fileName) => - fileType = @getFileType(new Buffer(dataURI.split(',')[1], 'base64')) - @logger.debug 'Uploaded file information is:', fileType - @logger.debug 'Expected file type is:', @mimeType - - if not fileType or fileType.mime isnt @mimeType - @logger.warn "Invalid file uploaded for the #{@name} importer." - throw new Meteor.Error('error-invalid-file-uploaded', "Invalid file uploaded to import #{@name} data from.", { step: 'prepare' }) - - @updateProgress Importer.ProgressStep.PREPARING_STARTED - @updateRecord { 'file': fileName } - - # Starts the import process. The implementing method should defer as soon as the selection is set, so the user who started the process - # doesn't end up with a "locked" ui while meteor waits for a response. The returned object should be the progress. - # - # @param [Importer.Selection] selectedUsersAndChannels an object with `channels` and `users` which contains information about which users and channels to import - # - # @return [Importer.Progress] the progress of the import - # - startImport: (importSelection) => - if importSelection is undefined - throw new Error "No selected users and channel data provided to the #{@name} importer." #TODO: Make translatable - else if importSelection.users is undefined - throw new Error "Users in the selected data wasn't found, it must but at least an empty array for the #{@name} importer." #TODO: Make translatable - else if importSelection.channels is undefined - throw new Error "Channels in the selected data wasn't found, it must but at least an empty array for the #{@name} importer." #TODO: Make translatable - - @updateProgress Importer.ProgressStep.IMPORTING_STARTED - - # Gets the Importer.Selection object for the import. - # - # @return [Importer.Selection] the users and channels selection - getSelection: () => - throw new Error "Invalid 'getSelection' called on #{@name}, it must be overridden and super can not be called." - - # Gets the progress of this importer. - # - # @return [Importer.Progress] the progress of the import - # - getProgress: => - return @progress - - # Updates the progress step of this importer. - # - # @return [Importer.Progress] the progress of the import - # - updateProgress: (step) => - @progress.step = step - - @logger.debug "#{@name} is now at #{step}." - @updateRecord { 'status': @progress.step } - - return @progress - - # Adds the passed in value to the total amount of items needed to complete. - # - # @return [Importer.Progress] the progress of the import - # - addCountToTotal: (count) => - @progress.count.total = @progress.count.total + count - @updateRecord { 'count.total': @progress.count.total } - - return @progress - - # Adds the passed in value to the total amount of items completed. - # - # @return [Importer.Progress] the progress of the import - # - addCountCompleted: (count) => - @progress.count.completed = @progress.count.completed + count - - #Only update the database every 500 records - #Or the completed is greater than or equal to the total amount - if (@progress.count.completed % 500 == 0) or @progress.count.completed >= @progress.count.total - @updateRecord { 'count.completed': @progress.count.completed } - - return @progress - - # Updates the import record with the given fields being `set` - # - # @return [Importer.Imports] the import record object - # - updateRecord: (fields) => - Importer.Imports.update { _id: @importRecord._id }, { $set: fields } - @importRecord = Importer.Imports.findOne @importRecord._id - - return @importRecord - - # Uploads the file to the storage. - # - # @param [Object] details an object with details about the upload. name, size, type, and rid - # @param [String] fileUrl url of the file to download/import - # @param [Object] user the Rocket.Chat user - # @param [Object] room the Rocket.Chat room - # @param [Date] timeStamp the timestamp the file was uploaded - # - uploadFile: (details, fileUrl, user, room, timeStamp) => - @logger.debug "Uploading the file #{details.name} from #{fileUrl}." - requestModule = if /https/i.test(fileUrl) then Importer.Base.https else Importer.Base.http - - requestModule.get fileUrl, Meteor.bindEnvironment((stream) -> - fileId = Meteor.fileStore.create details - if fileId - Meteor.fileStore.write stream, fileId, (err, file) -> - if err - throw new Error(err) - else - url = file.url.replace(Meteor.absoluteUrl(), '/') - - attachment = - title: "File Uploaded: #{file.name}" - title_link: url - - if /^image\/.+/.test file.type - attachment.image_url = url - attachment.image_type = file.type - attachment.image_size = file.size - attachment.image_dimensions = file.identify?.size - - if /^audio\/.+/.test file.type - attachment.audio_url = url - attachment.audio_type = file.type - attachment.audio_size = file.size - - if /^video\/.+/.test file.type - attachment.video_url = url - attachment.video_type = file.type - attachment.video_size = file.size - - msg = - rid: details.rid - ts: timeStamp - msg: '' - file: - _id: file._id - groupable: false - attachments: [attachment] - - if details.message_id? and (typeof details.message_id is 'string') - msg['_id'] = details.message_id - - RocketChat.sendMessage user, msg, room, true - else - @logger.error "Failed to create the store for #{fileUrl}!!!" - ) diff --git a/packages/rocketchat-importer/server/classes/ImporterProgress.coffee b/packages/rocketchat-importer/server/classes/ImporterProgress.coffee deleted file mode 100644 index fcc5cd398390..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterProgress.coffee +++ /dev/null @@ -1,9 +0,0 @@ -# Class for all the progress of the importers to use. -Importer.Progress = class Importer.Progress - # Constructs a new progress object. - # - # @param [String] name the name of the Importer - # - constructor: (@name) -> - @step = Importer.ProgressStep.NEW - @count = { completed: 0, total: 0 } diff --git a/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee b/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee deleted file mode 100644 index e3972dd24db9..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee +++ /dev/null @@ -1,16 +0,0 @@ -# "ENUM" of the import step, the value is the translation string -Importer.ProgressStep = Object.freeze - NEW: 'importer_new' - PREPARING_STARTED: 'importer_preparing_started' - PREPARING_USERS: 'importer_preparing_users' - PREPARING_CHANNELS: 'importer_preparing_channels' - PREPARING_MESSAGES: 'importer_preparing_messages' - USER_SELECTION: 'importer_user_selection' - IMPORTING_STARTED: 'importer_importing_started' - IMPORTING_USERS: 'importer_importing_users' - IMPORTING_CHANNELS: 'importer_importing_channels' - IMPORTING_MESSAGES: 'importer_importing_messages' - FINISHING: 'importer_finishing' - DONE: 'importer_done' - ERROR: 'importer_import_failed' - CANCELLED: 'importer_import_cancelled' diff --git a/packages/rocketchat-importer/server/classes/ImporterSelection.coffee b/packages/rocketchat-importer/server/classes/ImporterSelection.coffee deleted file mode 100644 index 314a0c44b29d..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelection.coffee +++ /dev/null @@ -1,9 +0,0 @@ -# Class for all the selection of users and channels for the importers -Importer.Selection = class Importer.Selection - # Constructs a new importer selection object. - # - # @param [String] name the name of the Importer - # @param [Array] users the array of users - # @param [Array] channels the array of channels - # - constructor: (@name, @users, @channels) -> diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee deleted file mode 100644 index 0ff26ef39d54..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee +++ /dev/null @@ -1,12 +0,0 @@ -# Class for the selection channels for ImporterSelection -Importer.SelectionChannel = class Importer.SelectionChannel - # Constructs a new selection channel. - # - # @param [String] channel_id the unique identifier of the channel - # @param [String] name the name of the channel - # @param [Boolean] is_archived whether the channel was archived or not - # @param [Boolean] do_import whether we will be importing the channel or not - # @param [Boolean] is_private whether the channel is private or public - # - constructor: (@channel_id, @name, @is_archived, @do_import, @is_private) -> - #TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee deleted file mode 100644 index 1d46e1df90af..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee +++ /dev/null @@ -1,13 +0,0 @@ -# Class for the selection users for ImporterSelection -Importer.SelectionUser = class Importer.SelectionUser - # Constructs a new selection user. - # - # @param [String] user_id the unique user identifier - # @param [String] username the user's username - # @param [String] email the user's email - # @param [Boolean] is_deleted whether the user was deleted or not - # @param [Boolean] is_bot whether the user is a bot or not - # @param [Boolean] do_import whether we are going to import this user or not - # - constructor: (@user_id, @username, @email, @is_deleted, @is_bot, @do_import) -> - #TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/methods/getImportProgress.coffee b/packages/rocketchat-importer/server/methods/getImportProgress.coffee deleted file mode 100644 index 89ba39919b0e..000000000000 --- a/packages/rocketchat-importer/server/methods/getImportProgress.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Meteor.methods - getImportProgress: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'getImportProgress' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]? - return Importer.Importers[name].importerInstance?.getProgress() - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getImportProgress' } diff --git a/packages/rocketchat-importer/server/methods/getSelectionData.coffee b/packages/rocketchat-importer/server/methods/getSelectionData.coffee deleted file mode 100644 index 8b4780c894a9..000000000000 --- a/packages/rocketchat-importer/server/methods/getSelectionData.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Meteor.methods - getSelectionData: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'getSelectionData' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importerInstance? - progress = Importer.Importers[name].importerInstance.getProgress() - switch progress.step - when Importer.ProgressStep.USER_SELECTION - return Importer.Importers[name].importerInstance.getSelection() - else - return false - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getSelectionData' } diff --git a/packages/rocketchat-importer/server/methods/restartImport.coffee b/packages/rocketchat-importer/server/methods/restartImport.coffee deleted file mode 100644 index aff7a019b43c..000000000000 --- a/packages/rocketchat-importer/server/methods/restartImport.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Meteor.methods - restartImport: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'restartImport' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]? - importer = Importer.Importers[name] - importer.importerInstance.updateProgress Importer.ProgressStep.CANCELLED - importer.importerInstance.updateRecord { valid: false } - importer.importerInstance = undefined - importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType - return importer.importerInstance.getProgress() - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'restartImport' } diff --git a/packages/rocketchat-importer/server/methods/setupImporter.coffee b/packages/rocketchat-importer/server/methods/setupImporter.coffee deleted file mode 100644 index 5ae12ab836c8..000000000000 --- a/packages/rocketchat-importer/server/methods/setupImporter.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Meteor.methods - setupImporter: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'setupImporter' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importer? - importer = Importer.Importers[name] - # If they currently have progress, get it and return the progress. - if importer.importerInstance - return importer.importerInstance.getProgress() - else - importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType - return importer.importerInstance.getProgress() - else - console.warn "Tried to setup #{name} as an importer." - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'setupImporter' } diff --git a/packages/rocketchat-importer/server/methods/startImport.coffee b/packages/rocketchat-importer/server/methods/startImport.coffee deleted file mode 100644 index b119f6eb8a86..000000000000 --- a/packages/rocketchat-importer/server/methods/startImport.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Meteor.methods - startImport: (name, input) -> - # Takes name and object with users / channels selected to import - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'startImport' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importerInstance? - usersSelection = input.users.map (user) -> - return new Importer.SelectionUser user.user_id, user.username, user.email, user.is_deleted, user.is_bot, user.do_import - channelsSelection = input.channels.map (channel) -> - return new Importer.SelectionChannel channel.channel_id, channel.name, channel.is_archived, channel.do_import - - selection = new Importer.Selection name, usersSelection, channelsSelection - Importer.Importers[name].importerInstance.startImport selection - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'startImport' } diff --git a/packages/rocketchat-importer/server/models/Imports.coffee b/packages/rocketchat-importer/server/models/Imports.coffee deleted file mode 100644 index 2e966dc83809..000000000000 --- a/packages/rocketchat-importer/server/models/Imports.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Importer.Imports = new class Importer.Imports extends RocketChat.models._Base - constructor: -> - super('import') diff --git a/packages/rocketchat-importer/server/models/RawImports.coffee b/packages/rocketchat-importer/server/models/RawImports.coffee deleted file mode 100644 index 1495423ea4ac..000000000000 --- a/packages/rocketchat-importer/server/models/RawImports.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Importer.RawImports = new class Importer.RawImports extends RocketChat.models._Base - constructor: -> - super('raw_imports') diff --git a/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee b/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee deleted file mode 100644 index fe4e5056f1eb..000000000000 --- a/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee +++ /dev/null @@ -1,8 +0,0 @@ -Meteor.startup -> - # Make sure all imports are marked as invalid, data clean up since you can't - # restart an import at the moment. - Importer.Imports.update { valid: { $ne: false } }, { $set: { valid: false } }, { multi: true } - - # Clean up all the raw import data, since you can't restart an import at the moment - Importer.Imports.find({ valid: { $ne: true }}).forEach (item) -> - Importer.RawImports.remove { 'import': item._id, 'importer': item.type } From 7f765acc3f31e918447dcf036544fe0761984f1d Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Wed, 24 May 2017 23:01:44 +0200 Subject: [PATCH 034/102] Add option to ignore TLS in SMTP server settings --- packages/rocketchat-lib/server/startup/settings.js | 11 ++++++++++- .../server/startup/settingsOnLoadSMTP.js | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index bd77f407b7d7..7a390e42d2a7 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -467,7 +467,7 @@ RocketChat.settings.addGroup('General', function() { RocketChat.settings.addGroup('Email', function() { this.section('Header_and_Footer', function() { - this.add('Email_Header', '

[Site_Name]

', { + this.add('Email_Header', '

[Site_Name]

', { type: 'code', code: 'text/html', multiline: true, @@ -505,6 +505,15 @@ RocketChat.settings.addGroup('Email', function() { env: true, i18nLabel: 'Port' }); + this.add('SMTP_IgnoreTLS', false, { + type: 'boolean', + env: true, + i18nLabel: 'IgnoreTLS', + enableQuery: { + _id: 'SMTP_Protocol', + value: 'smtp' + } + }); this.add('SMTP_Pool', true, { type: 'boolean', env: true, diff --git a/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js b/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js index 8a3601061aa6..70b8d4f7d253 100644 --- a/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js +++ b/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js @@ -16,6 +16,10 @@ const buildMailURL = _.debounce(function() { process.env.MAIL_URL += `?pool=${ RocketChat.settings.get('SMTP_Pool') }`; + if (RocketChat.settings.get('SMTP_Protocol') === 'smtp' && RocketChat.settings.get('SMTP_IgnoreTLS')) { + process.env.MAIL_URL += '&secure=false&ignoreTLS=true'; + } + return process.env.MAIL_URL; } }, 500); @@ -50,6 +54,10 @@ RocketChat.settings.onload('SMTP_Pool', function() { return buildMailURL(); }); +RocketChat.settings.onload('SMTP_IgnoreTLS', function() { + return buildMailURL(); +}); + Meteor.startup(function() { return buildMailURL(); }); From e76d6e36f45757d59f572b3251475f26630be38a Mon Sep 17 00:00:00 2001 From: Bradley Hilton Date: Wed, 24 May 2017 16:28:57 -0500 Subject: [PATCH 035/102] Add an api method for getting a single message by id, #6783 --- packages/rocketchat-api/server/v1/chat.js | 22 +++++++++++++++++++ packages/rocketchat-lib/package.js | 1 + .../server/methods/getSingleMessage.js | 19 ++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 packages/rocketchat-lib/server/methods/getSingleMessage.js diff --git a/packages/rocketchat-api/server/v1/chat.js b/packages/rocketchat-api/server/v1/chat.js index a003368c9c1f..4404750300f7 100644 --- a/packages/rocketchat-api/server/v1/chat.js +++ b/packages/rocketchat-api/server/v1/chat.js @@ -28,6 +28,28 @@ RocketChat.API.v1.addRoute('chat.delete', { authRequired: true }, { } }); +RocketChat.API.v1.addRoute('chat.getMessage', { authRequired: true }, { + get() { + if (!this.queryParams.msgId) { + return RocketChat.API.v1.failure('The "msgId" query parameter must be provided.'); + } + + + let msg; + Meteor.runAsUser(this.userId, () => { + msg = Meteor.call('getSingleMessage', this.queryParams.msgId); + }); + + if (!msg) { + return RocketChat.API.v1.failure(); + } + + return RocketChat.API.v1.success({ + message: msg + }); + } +}); + RocketChat.API.v1.addRoute('chat.postMessage', { authRequired: true }, { post() { const messageReturn = processWebhookMessage(this.bodyParams, this.user)[0]; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index a6360892d67a..3042469d88fa 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -143,6 +143,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/getFullUserData.js', 'server'); api.addFiles('server/methods/getRoomRoles.js', 'server'); api.addFiles('server/methods/getServerInfo.js', 'server'); + api.addFiles('server/methods/getSingleMessage.js', 'server'); api.addFiles('server/methods/getUserRoles.js', 'server'); api.addFiles('server/methods/insertOrUpdateUser.js', 'server'); api.addFiles('server/methods/joinDefaultChannels.js', 'server'); diff --git a/packages/rocketchat-lib/server/methods/getSingleMessage.js b/packages/rocketchat-lib/server/methods/getSingleMessage.js new file mode 100644 index 000000000000..72dc1a8516a3 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/getSingleMessage.js @@ -0,0 +1,19 @@ +Meteor.methods({ + getSingleMessage(msgId) { + check(msgId, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getSingleMessage' }); + } + + const msg = RocketChat.models.Messages.findOneById(msgId); + + if (!msg && !msg.rid) { + return undefined; + } + + Meteor.call('canAccessRoom', msg.rid, Meteor.userId()); + + return msg; + } +}); From de2ed50259266740a4d6410333c262dff5f84e71 Mon Sep 17 00:00:00 2001 From: Bradley Hilton Date: Wed, 24 May 2017 16:34:31 -0500 Subject: [PATCH 036/102] Add a test for the new rest api endpoint --- tests/end-to-end/api/05-chat.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/end-to-end/api/05-chat.js b/tests/end-to-end/api/05-chat.js index d77eaae05d5f..f10ff93932e9 100644 --- a/tests/end-to-end/api/05-chat.js +++ b/tests/end-to-end/api/05-chat.js @@ -57,6 +57,21 @@ describe('[Chat]', function() { .end(done); }); + it('/chat.getMessage', (done) => { + request.get(api('chat.getMessage')) + .set(credentials) + .query({ + msgId: message._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('message._id', message._id); + }) + .end(done); + }); + it('/chat.update', (done) => { request.post(api('chat.update')) .set(credentials) From 8cdeca6430fbdcd27eaaa8b079b555cb2428dfe8 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 24 May 2017 22:21:18 -0300 Subject: [PATCH 037/102] Add support to AmazonS3 Server for uploads --- package.json | 1 + .../client/lib/FileUploadAmazonS3Server.js | 56 ++++++++ packages/rocketchat-file-upload/package.js | 2 + .../config/configFileUploadAmazonS3Server.js | 114 +++++++++++++++ .../server/startup/settings.js | 3 + packages/rocketchat-file-upload/ufs/client.js | 6 + packages/rocketchat-file-upload/ufs/server.js | 132 ++++++++++++++++++ 7 files changed, 314 insertions(+) create mode 100644 packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3Server.js create mode 100644 packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js create mode 100644 packages/rocketchat-file-upload/ufs/client.js create mode 100644 packages/rocketchat-file-upload/ufs/server.js diff --git a/package.json b/package.json index 2dca607b9f1e..1b27d24f9c9e 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "supertest": "^3.0.0" }, "dependencies": { + "aws-sdk": "^2.55.0", "babel-runtime": "^6.23.0", "bcrypt": "^1.0.2", "codemirror": "^5.25.2", diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3Server.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3Server.js new file mode 100644 index 000000000000..b615c64331d4 --- /dev/null +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3Server.js @@ -0,0 +1,56 @@ +/* globals FileUpload, FileUploadBase, UploadFS, AmazonS3ServerStore:true, AmazonS3ServerStoreAvatar:true */ + +import '../../ufs/client.js'; + +AmazonS3ServerStore = new UploadFS.store.AmazonS3({ + collection: RocketChat.models.Uploads.model, + name: 'AmazonS3Server:Uploads', + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }) +}); + +AmazonS3ServerStoreAvatar = new UploadFS.store.AmazonS3({ + collection: RocketChat.models.Avatars.model, + name: 'AmazonS3Server:Avatars', + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }) +}); + +FileUpload.AmazonS3Server = class FileUploadAmazonS3Server extends FileUploadBase { + constructor(directive, meta, file) { + super(meta, file); + console.log('AmazonS3Server', {directive, meta, file}); + this.store = directive === 'avatar' ? AmazonS3ServerStoreAvatar : AmazonS3ServerStore; + } + + start(callback) { + this.handler = new UploadFS.Uploader({ + store: this.store, + data: this.file, + file: this.meta, + onError: (err) => { + return callback(err); + }, + onComplete: (fileData) => { + const file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); + + file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); + return callback(null, file, 'fs'); + } + }); + + this.handler.onProgress = (file, progress) => { + this.onProgress(progress); + }; + + return this.handler.start(); + } + + onProgress() {} + + stop() { + return this.handler.stop(); + } +}; diff --git a/packages/rocketchat-file-upload/package.js b/packages/rocketchat-file-upload/package.js index 8ba5db02407d..1587984f7b6b 100644 --- a/packages/rocketchat-file-upload/package.js +++ b/packages/rocketchat-file-upload/package.js @@ -28,6 +28,7 @@ Package.onUse(function(api) { api.addFiles('lib/FileUploadBase.js'); api.addFiles('client/lib/FileUploadAmazonS3.js', 'client'); + api.addFiles('client/lib/FileUploadAmazonS3Server.js', 'client'); api.addFiles('client/lib/FileUploadFileSystem.js', 'client'); api.addFiles('client/lib/FileUploadGoogleStorage.js', 'client'); api.addFiles('client/lib/FileUploadGridFS.js', 'client'); @@ -37,6 +38,7 @@ Package.onUse(function(api) { api.addFiles('server/lib/requests.js', 'server'); api.addFiles('server/config/configFileUploadAmazonS3.js', 'server'); + api.addFiles('server/config/configFileUploadAmazonS3Server.js', 'server'); api.addFiles('server/config/configFileUploadFileSystem.js', 'server'); api.addFiles('server/config/configFileUploadGoogleStorage.js', 'server'); api.addFiles('server/config/configFileUploadGridFS.js', 'server'); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js new file mode 100644 index 000000000000..9e4a8cae1e70 --- /dev/null +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js @@ -0,0 +1,114 @@ +/* globals FileUpload, UploadFS, RocketChatFile */ + +import fs from 'fs'; +import { FileUploadClass } from '../lib/FileUpload'; +import '../../ufs/server.js'; + +const Future = Npm.require('fibers/future'); + +const insert = function(file, stream, cb) { + const fileId = this.store.create(file); + + this.store.write(stream, fileId, cb); +}; + +const AmazonS3ServerUploads = new FileUploadClass({ + name: 'AmazonS3Server:Uploads', + // store setted bellow + + get(file, req, res) { + const fileUrl = AmazonS3ServerUploads.store.getS3URL(file); + + if (fileUrl) { + res.setHeader('Location', fileUrl); + res.writeHead(302); + } + res.end(); + }, + + insert +}); + +const onValidate = function(file) { + if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) { + return; + } + + const tmpFile = UploadFS.getTempFilePath(file._id); + + const fut = new Future(); + + const identify = Meteor.bindEnvironment((err, data) => { + if (err != null) { + console.error(err); + return fut.return(); + } + + file.identify = { + format: data.format, + size: data.size + }; + + if ([null, undefined, '', 'Unknown', 'Undefined'].includes(data.Orientation)) { + return fut.return(); + } + + RocketChatFile.gm(tmpFile).autoOrient().write(tmpFile, Meteor.bindEnvironment((err) => { + if (err != null) { + console.error(err); + } + + const size = fs.lstatSync(tmpFile).size; + this.getCollection().direct.update({_id: file._id}, {$set: {size}}); + fut.return(); + })); + }); + + RocketChatFile.gm(tmpFile).identify(identify); + + return fut.wait(); +}; + +const configure = _.debounce(function() { + const stores = UploadFS.getStores(); + delete stores[AmazonS3ServerUploads.name]; + + const Bucket = RocketChat.settings.get('FileUpload_S3_Bucket'); + const Acl = RocketChat.settings.get('FileUpload_S3_Acl'); + const AWSAccessKeyId = RocketChat.settings.get('FileUpload_S3_AWSAccessKeyId'); + const AWSSecretAccessKey = RocketChat.settings.get('FileUpload_S3_AWSSecretAccessKey'); + const URLExpiryTimeSpan = RocketChat.settings.get('FileUpload_S3_URLExpiryTimeSpan'); + const Region = RocketChat.settings.get('FileUpload_S3_Region'); + // const CDN = RocketChat.settings.get('FileUpload_S3_CDN'); + // const BucketURL = RocketChat.settings.get('FileUpload_S3_BucketURL'); + + AmazonS3ServerUploads.store = new UploadFS.store.AmazonS3({ + connection: { + accessKeyId: AWSAccessKeyId, + secretAccessKey: AWSSecretAccessKey, + signatureVersion: 'v4', + params: { + Bucket, + ACL: Acl + }, + region: Region + }, + URLExpiryTimeSpan, + collection: AmazonS3ServerUploads.model.model, + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }), + name: AmazonS3ServerUploads.name, + onFinishUpload(file) { + AmazonS3ServerUploads.model.update({_id: file._id}, { + $set: { + s3: file.s3 + } + }); + }, + onValidate + }); + +}, 500); + +RocketChat.settings.get(/^FileUpload_S3_/, configure); diff --git a/packages/rocketchat-file-upload/server/startup/settings.js b/packages/rocketchat-file-upload/server/startup/settings.js index fd1cea0b15a6..c49b58cd07ff 100644 --- a/packages/rocketchat-file-upload/server/startup/settings.js +++ b/packages/rocketchat-file-upload/server/startup/settings.js @@ -29,6 +29,9 @@ RocketChat.settings.addGroup('FileUpload', function() { }, { key: 'AmazonS3', i18nLabel: 'AmazonS3' + }, { + key: 'AmazonS3Server', + i18nLabel: 'AmazonS3Server' }, { key: 'GoogleCloudStorage', i18nLabel: 'GoogleCloudStorage' diff --git a/packages/rocketchat-file-upload/ufs/client.js b/packages/rocketchat-file-upload/ufs/client.js new file mode 100644 index 000000000000..6d9f66f467a4 --- /dev/null +++ b/packages/rocketchat-file-upload/ufs/client.js @@ -0,0 +1,6 @@ +import {UploadFS} from 'meteor/jalik:ufs'; + +export class AmazonS3Store extends UploadFS.Store {} + +// Add store to UFS namespace +UploadFS.store.AmazonS3 = AmazonS3Store; diff --git a/packages/rocketchat-file-upload/ufs/server.js b/packages/rocketchat-file-upload/ufs/server.js new file mode 100644 index 000000000000..c57e7da0c082 --- /dev/null +++ b/packages/rocketchat-file-upload/ufs/server.js @@ -0,0 +1,132 @@ +import {_} from 'meteor/underscore'; +// import {check} from 'meteor/check'; +import {Meteor} from 'meteor/meteor'; +import {UploadFS} from 'meteor/jalik:ufs'; +import S3 from 'aws-sdk/clients/s3'; +import stream from 'stream'; + +/** + * GridFS store + * @param options + * @constructor + */ +export class AmazonS3Store extends UploadFS.Store { + + constructor(options) { + // Default options + // options.secretAccessKey, + // options.accessKeyId, + // options.region, + // options.sslEnabled // optional + + options = _.extend({ + httpOptions: { + timeout: 6000, + agent: false + } + }, options); + + super(options); + + const classOptions = options; + + if (Meteor.isServer) { + const s3 = new S3(options.connection); + + this.getPath = function(file) { + return `${ RocketChat.hostname }/${ file.rid }/${ file.userId }/${ file._id }`; + }; + + this.getS3URL = function(file) { + const params = { + Key: this.getPath(file), + Expires: classOptions.URLExpiryTimeSpan + }; + + return s3.getSignedUrl('getObject', params); + }; + + /** + * Removes the file + * @param fileId + * @param callback + */ + this.delete = function(fileId, callback) { + const file = this.getCollection().findOne({_id: fileId}); + const params = { + Key: this.getPath(file) + }; + + s3.deleteObject(params, (err, data) => { + if (err) { + console.error(err); + } + + callback && callback(err, data); + }); + }; + + /** + * Returns the file read stream + * @param fileId + * @param file + * @param options + * @return {*} + */ + this.getReadStream = function(fileId, file, options = {}) { + const params = { + Key: this.getPath(file) + }; + + if (options.start && options.end) { + params.Range = `${ options.start } - ${ options.end }`; + } + + return s3.getObject(params).createReadStream(); + }; + + /** + * Returns the file write stream + * @param fileId + * @param file + * @param options + * @return {*} + */ + this.getWriteStream = function(fileId, file/*, options*/) { + const writeStream = new stream.PassThrough(); + writeStream.length = file.size; + + writeStream.on('newListener', (event, listener) => { + if (event === 'finish') { + process.nextTick(() => { + writeStream.removeListener(event, listener); + writeStream.on('real_finish', listener); + }); + } + }); + + file.s3 = { + Key: this.getPath(file) + }; + + s3.putObject({ + Key: file.s3.Key, + Body: writeStream, + ContentType: file.type + + }, (error) => { + if (error) { + console.error(error); + } + + writeStream.emit('real_finish'); + }); + + return writeStream; + }; + } + } +} + +// Add store to UFS namespace +UploadFS.store.AmazonS3 = AmazonS3Store; From e02d5dcf92894f4d135d11b5621b7c5e6445a5d3 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 24 May 2017 22:50:17 -0300 Subject: [PATCH 038/102] Implement AmazonS3 Server for Avatars --- .../config/configFileUploadAmazonS3Server.js | 81 +++++++++++++++---- packages/rocketchat-file-upload/ufs/server.js | 6 +- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js index 9e4a8cae1e70..4d0b9a421c91 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3Server.js @@ -12,20 +12,29 @@ const insert = function(file, stream, cb) { this.store.write(stream, fileId, cb); }; +const get = function(file, req, res) { + const fileUrl = this.store.getS3URL(file); + + if (fileUrl) { + res.setHeader('Location', fileUrl); + res.writeHead(302); + } + res.end(); +}; + const AmazonS3ServerUploads = new FileUploadClass({ name: 'AmazonS3Server:Uploads', // store setted bellow - get(file, req, res) { - const fileUrl = AmazonS3ServerUploads.store.getS3URL(file); + get, + insert +}); - if (fileUrl) { - res.setHeader('Location', fileUrl); - res.writeHead(302); - } - res.end(); - }, +const AmazonS3ServerAvatars = new FileUploadClass({ + name: 'AmazonS3Server:Avatars', + // store setted bellow + get, insert }); @@ -72,6 +81,7 @@ const onValidate = function(file) { const configure = _.debounce(function() { const stores = UploadFS.getStores(); delete stores[AmazonS3ServerUploads.name]; + delete stores[AmazonS3ServerAvatars.name]; const Bucket = RocketChat.settings.get('FileUpload_S3_Bucket'); const Acl = RocketChat.settings.get('FileUpload_S3_Acl'); @@ -82,7 +92,7 @@ const configure = _.debounce(function() { // const CDN = RocketChat.settings.get('FileUpload_S3_CDN'); // const BucketURL = RocketChat.settings.get('FileUpload_S3_BucketURL'); - AmazonS3ServerUploads.store = new UploadFS.store.AmazonS3({ + const config = { connection: { accessKeyId: AWSAccessKeyId, secretAccessKey: AWSSecretAccessKey, @@ -93,21 +103,60 @@ const configure = _.debounce(function() { }, region: Region }, - URLExpiryTimeSpan, + URLExpiryTimeSpan + }; + + AmazonS3ServerUploads.store = new UploadFS.store.AmazonS3(Object.assign({ collection: AmazonS3ServerUploads.model.model, filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload }), name: AmazonS3ServerUploads.name, + onValidate + }, config)); + + AmazonS3ServerAvatars.store = new UploadFS.store.AmazonS3(Object.assign({ + collection: AmazonS3ServerAvatars.model.model, + name: AmazonS3ServerAvatars.name, onFinishUpload(file) { - AmazonS3ServerUploads.model.update({_id: file._id}, { - $set: { - s3: file.s3 + // update file record to match user's username + const user = RocketChat.models.Users.findOneById(file.userId); + const oldAvatar = AmazonS3ServerAvatars.model.findOneByName(user.username); + if (oldAvatar) { + try { + AmazonS3ServerAvatars.deleteById(oldAvatar._id); + } catch (e) { + console.error(e); } - }); + } + AmazonS3ServerAvatars.model.updateFileNameById(file._id, user.username); + // console.log('upload finished ->', file); }, - onValidate - }); + onValidate(file) { + if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) { + return; + } + + const tmpFile = UploadFS.getTempFilePath(file._id); + + const fut = new Future(); + + const height = RocketChat.settings.get('Accounts_AvatarSize'); + const width = height; + + RocketChatFile.gm(tmpFile).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).setFormat('jpeg').write(tmpFile, Meteor.bindEnvironment((err) => { + if (err != null) { + console.error(err); + } + + const size = fs.lstatSync(tmpFile).size; + this.getCollection().direct.update({_id: file._id}, {$set: {size}}); + fut.return(); + })); + + return fut.wait(); + } + }, config)); }, 500); diff --git a/packages/rocketchat-file-upload/ufs/server.js b/packages/rocketchat-file-upload/ufs/server.js index c57e7da0c082..df53aca2d000 100644 --- a/packages/rocketchat-file-upload/ufs/server.js +++ b/packages/rocketchat-file-upload/ufs/server.js @@ -105,12 +105,8 @@ export class AmazonS3Store extends UploadFS.Store { } }); - file.s3 = { - Key: this.getPath(file) - }; - s3.putObject({ - Key: file.s3.Key, + Key: this.getPath(file), Body: writeStream, ContentType: file.type From af39134c8285930c28ba107b278891ac0bb5bf4f Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Thu, 25 May 2017 07:20:15 +0200 Subject: [PATCH 039/102] Fix whitespace --- packages/rocketchat-lib/server/startup/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 7a390e42d2a7..ee841bbda5c4 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -512,7 +512,7 @@ RocketChat.settings.addGroup('Email', function() { enableQuery: { _id: 'SMTP_Protocol', value: 'smtp' - } + } }); this.add('SMTP_Pool', true, { type: 'boolean', From 91f51414bed8425cbfbe01ac581c7d54fc29d6da Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 25 May 2017 10:39:25 -0300 Subject: [PATCH 040/102] use imports --- packages/rocketchat-irc/server/server.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-irc/server/server.js b/packages/rocketchat-irc/server/server.js index 5b32aad87831..c6f356d92ef3 100644 --- a/packages/rocketchat-irc/server/server.js +++ b/packages/rocketchat-irc/server/server.js @@ -1,3 +1,6 @@ +import net from 'net'; +import Lru from 'lru-cache'; + ////// // Assign values @@ -5,8 +8,6 @@ const IRC_AVAILABILITY = RocketChat.settings.get('IRC_Enabled'); // Cache prep -const net = Npm.require('net'); -const Lru = Npm.require('lru-cache'); const MESSAGE_CACHE_SIZE = RocketChat.settings.get('IRC_Message_Cache_Size'); const ircReceiveMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line const ircSendMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line From c516d9ddc8d439e9b2512d111c7cefc9f7b876fb Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 25 May 2017 13:52:16 -0300 Subject: [PATCH 041/102] test commit for travis --- .../rocketchat-ui-sidenav/client/toolbar.html | 2 +- .../end-to-end/ui/04-main-elements-render.js | 1 + tests/pageobjects/side-nav.page.js | 50 ++++++++++++------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/rocketchat-ui-sidenav/client/toolbar.html b/packages/rocketchat-ui-sidenav/client/toolbar.html index 2f2449517463..356d752a6784 100644 --- a/packages/rocketchat-ui-sidenav/client/toolbar.html +++ b/packages/rocketchat-ui-sidenav/client/toolbar.html @@ -19,7 +19,7 @@