diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 597c99ad76f..c3f6314f993 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -56,14 +56,21 @@ Package.onUse(function(api) { api.addFiles('server/lib/RateLimiter.coffee', 'server'); // SERVER FUNCTIONS + api.addFiles('server/functions/addUserToDefaultChannels.js', 'server'); + api.addFiles('server/functions/addUserToRoom.js', 'server'); + api.addFiles('server/functions/archiveRoom.js', 'server'); api.addFiles('server/functions/checkUsernameAvailability.coffee', 'server'); api.addFiles('server/functions/checkEmailAvailability.js', 'server'); - api.addFiles('server/functions/createPrivateGroup.js', 'server'); + api.addFiles('server/functions/createRoom.js', 'server'); + api.addFiles('server/functions/deleteMessage.js', 'server'); api.addFiles('server/functions/deleteUser.js', 'server'); + api.addFiles('server/functions/removeUserFromRoom.js', 'server'); api.addFiles('server/functions/sendMessage.coffee', 'server'); api.addFiles('server/functions/settings.coffee', 'server'); + api.addFiles('server/functions/setUserAvatar.js', 'server'); api.addFiles('server/functions/setUsername.coffee', 'server'); api.addFiles('server/functions/setEmail.js', 'server'); + api.addFiles('server/functions/updateMessage.js', 'server'); api.addFiles('server/functions/Notifications.coffee', 'server'); // SERVER LIB @@ -89,12 +96,18 @@ Package.onUse(function(api) { // SERVER METHODS api.addFiles('server/methods/addOAuthService.coffee', 'server'); + api.addFiles('server/methods/addUserToRoom.coffee', 'server'); + api.addFiles('server/methods/archiveRoom.coffee', 'server'); api.addFiles('server/methods/checkRegistrationSecretURL.coffee', 'server'); + api.addFiles('server/methods/createChannel.coffee', 'server'); api.addFiles('server/methods/createPrivateGroup.coffee', 'server'); + api.addFiles('server/methods/deleteMessage.coffee', 'server'); api.addFiles('server/methods/deleteUserOwnAccount.js', 'server'); api.addFiles('server/methods/getRoomRoles.js', 'server'); api.addFiles('server/methods/getUserRoles.js', 'server'); + api.addFiles('server/methods/joinRoom.coffee', 'server'); api.addFiles('server/methods/joinDefaultChannels.coffee', 'server'); + api.addFiles('server/methods/leaveRoom.coffee', 'server'); api.addFiles('server/methods/removeOAuthService.coffee', 'server'); api.addFiles('server/methods/robotMethods.coffee', 'server'); api.addFiles('server/methods/saveSetting.coffee', 'server'); @@ -107,6 +120,8 @@ Package.onUse(function(api) { api.addFiles('server/methods/insertOrUpdateUser.coffee', 'server'); api.addFiles('server/methods/setEmail.js', 'server'); api.addFiles('server/methods/restartServer.coffee', 'server'); + api.addFiles('server/methods/unarchiveRoom.coffee', 'server'); + api.addFiles('server/methods/updateMessage.coffee', 'server'); api.addFiles('server/methods/filterBadWords.js', ['server']); api.addFiles('server/methods/filterATAllTag.js', 'server'); diff --git a/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js b/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js new file mode 100644 index 00000000000..a520ff941f5 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js @@ -0,0 +1,25 @@ +RocketChat.addUserToDefaultChannels = function(user, silenced) { + RocketChat.callbacks.run('beforeJoinDefaultChannels', user); + let defaultRooms = RocketChat.models.Rooms.findByDefaultAndTypes(true, ['c', 'p'], {fields: {usernames: 0}}).fetch(); + defaultRooms.forEach((room) => { + + // put user in default rooms + RocketChat.models.Rooms.addUsernameById(room._id, user.username); + + if (!RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id)) { + + // Add a subscription to this user + RocketChat.models.Subscriptions.createWithRoomAndUser(room, user, { + ts: new Date(), + open: true, + alert: true, + unread: 1 + }); + + // Insert user joined message + if (!silenced) { + RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(room._id, user); + } + } + }); +}; diff --git a/packages/rocketchat-lib/server/functions/addUserToRoom.js b/packages/rocketchat-lib/server/functions/addUserToRoom.js new file mode 100644 index 00000000000..c5496a9335d --- /dev/null +++ b/packages/rocketchat-lib/server/functions/addUserToRoom.js @@ -0,0 +1,42 @@ +RocketChat.addUserToRoom = function(rid, user, inviter) { + let now = new Date(); + let room = RocketChat.models.Rooms.findOneById(rid); + + // Check if user is already in room + let subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id); + if (subscription) { + return; + } + + if (room.t === 'c') { + RocketChat.callbacks.run('beforeJoinRoom', user, room); + } + + RocketChat.models.Rooms.addUsernameById(rid, user.username); + RocketChat.models.Subscriptions.createWithRoomAndUser(room, user, { + ts: now, + open: true, + alert: true, + unread: 1 + }); + + if (inviter) { + RocketChat.models.Messages.createUserAddedWithRoomIdAndUser(rid, user, { + ts: now, + u: { + _id: inviter._id, + username: inviter.username + } + }); + } else { + RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(rid, user, { ts: now }); + } + + if (room.t === 'c') { + Meteor.defer(function() { + RocketChat.callbacks.run('afterJoinRoom', user, room); + }); + } + + return true; +}; diff --git a/packages/rocketchat-lib/server/functions/archiveRoom.js b/packages/rocketchat-lib/server/functions/archiveRoom.js new file mode 100644 index 00000000000..ef2aafeffe4 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/archiveRoom.js @@ -0,0 +1,4 @@ +RocketChat.archiveRoom = function(rid) { + RocketChat.models.Rooms.archiveById(rid); + RocketChat.models.Subscriptions.archiveByRoomId(rid); +}; diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js new file mode 100644 index 00000000000..02f222b447c --- /dev/null +++ b/packages/rocketchat-lib/server/functions/createRoom.js @@ -0,0 +1,83 @@ +/* globals RocketChat */ +RocketChat.createRoom = function(type, name, owner, members) { + name = s.trim(name); + owner = s.trim(owner); + members = [].concat(members); + + if (!name) { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { function: 'RocketChat.createRoom' }); + } + + owner = RocketChat.models.Users.findOneByUsername(owner, { fields: { username: 1 }}); + if (!owner) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'RocketChat.createRoom' }); + } + + let nameValidation; + try { + nameValidation = new RegExp('^' + RocketChat.settings.get('UTF8_Names_Validation') + '$'); + } catch (error) { + nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$'); + } + + if (!nameValidation.test(name)) { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { function: 'RocketChat.createRoom' }); + } + + let now = new Date(); + if (!_.contains(members, owner.username)) { + members.push(owner.username); + } + + // avoid duplicate names + let room = RocketChat.models.Rooms.findOneByName(name); + if (room) { + if (room.archived) { + throw new Meteor.Error('error-archived-duplicate-name', 'There\'s an archived channel with name ' + name, { function: 'RocketChat.createRoom', room_name: name }); + } else { + throw new Meteor.Error('error-duplicate-channel-name', 'A channel with name \'' + name + '\' exists', { function: 'RocketChat.createRoom', room_name: name }); + } + } + + if (type === 'c') { + RocketChat.callbacks.run('beforeCreateChannel', owner, { + t: 'c', + name: name, + ts: now, + usernames: members, + u: { + _id: owner._id, + username: owner.username + } + }); + } + + room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, name, owner.username, members, { ts: now }); + + for (let username of members) { + let member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }}); + if (!member) { + continue; + } + + let extra = { open: true }; + + if (username === owner.username) { + extra.ls = now; + } + + RocketChat.models.Subscriptions.createWithRoomAndUser(room, member, extra); + } + + RocketChat.authz.addUserRoles(owner._id, ['owner'], room._id); + + if (type === 'c') { + Meteor.defer(() => { + RocketChat.callbacks.run('afterCreateChannel', owner, room); + }); + } + + return { + rid: room._id + }; +}; diff --git a/packages/rocketchat-lib/server/functions/deleteMessage.js b/packages/rocketchat-lib/server/functions/deleteMessage.js new file mode 100644 index 00000000000..54ac4dd9175 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/deleteMessage.js @@ -0,0 +1,31 @@ +/* globals FileUpload */ +RocketChat.deleteMessage = function(message, user) { + let keepHistory = RocketChat.settings.get('Message_KeepHistory'); + let showDeletedStatus = RocketChat.settings.get('Message_ShowDeletedStatus'); + + if (keepHistory) { + if (showDeletedStatus) { + RocketChat.models.Messages.cloneAndSaveAsHistoryById(message._id); + } else { + RocketChat.models.Messages.setHiddenById(message._id, true); + } + + if (message.file && message.file._id) { + RocketChat.models.Uploads.update(message.file._id, { $set: { _hidden: true } }); + } + } else { + if (!showDeletedStatus) { + RocketChat.models.Messages.removeById(message._id); + } + + if (message.file && message.file._id) { + FileUpload.delete(message.file._id); + } + } + + if (showDeletedStatus) { + RocketChat.models.Messages.setAsDeletedByIdAndUser(message._id, user); + } else { + RocketChat.Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id }); + } +}; diff --git a/packages/rocketchat-lib/server/functions/removeUserFromRoom.js b/packages/rocketchat-lib/server/functions/removeUserFromRoom.js new file mode 100644 index 00000000000..57fa231c86b --- /dev/null +++ b/packages/rocketchat-lib/server/functions/removeUserFromRoom.js @@ -0,0 +1,23 @@ +RocketChat.removeUserFromRoom = function(rid, user) { + let room = RocketChat.models.Rooms.findOneById(rid); + + if (room) { + RocketChat.callbacks.run('beforeLeaveRoom', user, room); + RocketChat.models.Rooms.removeUsernameById(rid, user.username); + + if (room.usernames.indexOf(user.username) !== -1) { + let removedUser = user; + RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(rid, removedUser); + } + + if (room.t === 'l') { + RocketChat.models.Messages.createCommandWithRoomIdAndUser('survey', rid, user); + } + + RocketChat.models.Subscriptions.removeByRoomIdAndUserId(rid, user._id); + + Meteor.defer(function() { + RocketChat.callbacks.run('afterLeaveRoom', user, room); + }); + } +}; diff --git a/packages/rocketchat-lib/server/functions/setUserAvatar.js b/packages/rocketchat-lib/server/functions/setUserAvatar.js new file mode 100644 index 00000000000..40213653b40 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/setUserAvatar.js @@ -0,0 +1,55 @@ +RocketChat.setUserAvatar = function(user, dataURI, contentType, service) { + if (service === 'initials') { + return RocketChat.models.Users.setAvatarOrigin(user._id, service); + } + + if (service === 'url') { + let result = null; + + try { + result = HTTP.get(dataURI, { npmRequestOptions: {encoding: 'binary'} }); + } catch (error) { + console.log(`Error while handling the setting of the avatar from a url (${dataURI}) for ${user.username}:`, error); + throw new Meteor.Error('error-avatar-url-handling', `Error while handling avatar setting from a URL (${dataURI}) for ${user.username}`, { function: 'RocketChat.setUserAvatar', url: dataURI, username: user.username }); + } + + if (result.statusCode !== 200) { + console.log(`Not a valid response, ${result.statusCode}, from the avatar url: ${dataURI}`); + throw new Meteor.Error('error-avatar-invalid-url', `Invalid avatar URL: ${dataURI}`, { function: 'RocketChat.setUserAvatar', url: dataURI }); + } + + if (!/image\/.+/.test(result.headers['content-type'])) { + console.log(`Not a valid content-type from the provided url, ${result.headers['content-type']}, from the avatar url: ${dataURI}`); + throw new Meteor.Error('error-avatar-invalid-url', `Invalid avatar URL: ${dataURI}`, { function: 'RocketChat.setUserAvatar', url: dataURI }); + } + + let ars = RocketChatFile.bufferToStream(new Buffer(result.content, 'binary')); + RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${user.username}.jpg`)); + let aws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${user.username}.jpg`), result.headers['content-type']); + aws.on('end', Meteor.bindEnvironment(function() { + Meteor.setTimeout(function() { + console.log(`Set ${user.username}'s avatar from the url: ${dataURI}`); + RocketChat.models.Users.setAvatarOrigin(user._id, service); + RocketChat.Notifications.notifyAll('updateAvatar', { username: user.username }); + }, 500); + })); + + ars.pipe(aws); + return; + } + + let fileData = RocketChatFile.dataURIParse(dataURI); + let image = fileData.image; + contentType = fileData.contentType; + + let rs = RocketChatFile.bufferToStream(new Buffer(image, 'base64')); + RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${user.username}.jpg`)); + let ws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${user.username}.jpg`), contentType); + ws.on('end', Meteor.bindEnvironment(function() { + Meteor.setTimeout(function() { + RocketChat.models.Users.setAvatarOrigin(user._id, service); + RocketChat.Notifications.notifyAll('updateAvatar', {username: user.username}); + }, 500); + })); + rs.pipe(ws); +}; diff --git a/packages/rocketchat-lib/server/functions/unarchiveRoom.js b/packages/rocketchat-lib/server/functions/unarchiveRoom.js new file mode 100644 index 00000000000..3884e06c404 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/unarchiveRoom.js @@ -0,0 +1,4 @@ +RocketChat.unarchiveRoom = function(rid) { + RocketChat.models.Rooms.unarchiveById(rid); + RocketChat.models.Subscriptions.unarchiveByRoomId(rid); +}; diff --git a/packages/rocketchat-lib/server/functions/updateMessage.js b/packages/rocketchat-lib/server/functions/updateMessage.js new file mode 100644 index 00000000000..065d0244b32 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/updateMessage.js @@ -0,0 +1,30 @@ +RocketChat.updateMessage = function(message, user) { + // If we keep history of edits, insert a new message to store history information + if (RocketChat.settings.get('Message_KeepHistory')) { + RocketChat.models.Messages.cloneAndSaveAsHistoryById(message._id); + } + + message.editedAt = new Date(); + message.editedBy = { + _id: user._id, + username: user.username + }; + + let urls = message.msg.match(/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g); + if (urls) { + message.urls = urls.map((url) => { return { url: url } }); + } + + message = RocketChat.callbacks.run('beforeSaveMessage', message); + + let tempid = message._id; + delete message._id; + + RocketChat.models.Messages.update({ _id: tempid }, { $set: message }); + + let room = RocketChat.models.Rooms.findOneById(message.rid); + + Meteor.defer(function() { + RocketChat.callbacks.run('afterSaveMessage', RocketChat.models.Messages.findOneById(tempid), room); + }); +}; diff --git a/server/methods/addUserToRoom.coffee b/packages/rocketchat-lib/server/methods/addUserToRoom.coffee similarity index 64% rename from server/methods/addUserToRoom.coffee rename to packages/rocketchat-lib/server/methods/addUserToRoom.coffee index 0b05a92925a..47287b4d05c 100644 --- a/server/methods/addUserToRoom.coffee +++ b/packages/rocketchat-lib/server/methods/addUserToRoom.coffee @@ -3,7 +3,6 @@ Meteor.methods if not Meteor.userId() throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'addUserToRoom' } - fromId = Meteor.userId() unless Match.test data?.rid, String throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'addUserToRoom' } @@ -15,34 +14,15 @@ Meteor.methods if room.usernames.indexOf(Meteor.user().username) is -1 throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'addUserToRoom' } - # if room.username isnt Meteor.user().username and room.t is 'c' + fromId = Meteor.userId() if not RocketChat.authz.hasPermission(fromId, 'add-user-to-room', room._id) throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'addUserToRoom' } if room.t is 'd' throw new Meteor.Error 'error-cant-invite-for-direct-room', 'Can\'t invite user to direct rooms', { method: 'addUserToRoom' } - # verify if user is already in room - if room.usernames.indexOf(data.username) isnt -1 - return newUser = RocketChat.models.Users.findOneByUsername data.username - - RocketChat.models.Rooms.addUsernameById data.rid, data.username - - now = new Date() - - RocketChat.models.Subscriptions.createWithRoomAndUser room, newUser, - ts: now - open: true - alert: true - unread: 1 - - fromUser = RocketChat.models.Users.findOneById fromId - RocketChat.models.Messages.createUserAddedWithRoomIdAndUser data.rid, newUser, - ts: now - u: - _id: fromUser._id - username: fromUser.username + RocketChat.addUserToRoom(data.rid, newUser, Meteor.user()); return true diff --git a/server/methods/archiveRoom.coffee b/packages/rocketchat-lib/server/methods/archiveRoom.coffee similarity index 63% rename from server/methods/archiveRoom.coffee rename to packages/rocketchat-lib/server/methods/archiveRoom.coffee index 6f8030e2df2..9f7d75cfce1 100644 --- a/server/methods/archiveRoom.coffee +++ b/packages/rocketchat-lib/server/methods/archiveRoom.coffee @@ -11,11 +11,4 @@ Meteor.methods unless RocketChat.authz.hasPermission(Meteor.userId(), 'archive-room', room._id) throw new Meteor.Error 'error-not-authorized', 'Not authorized', { method: 'archiveRoom' } - RocketChat.models.Rooms.archiveById rid - - for username in room.usernames - member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }}) - if not member? - continue - - RocketChat.models.Subscriptions.archiveByRoomIdAndUserId rid, member._id + RocketChat.archiveRoom(rid) diff --git a/packages/rocketchat-lib/server/methods/createChannel.coffee b/packages/rocketchat-lib/server/methods/createChannel.coffee new file mode 100644 index 00000000000..f6aad714e85 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/createChannel.coffee @@ -0,0 +1,9 @@ +Meteor.methods + createChannel: (name, members) -> + if not Meteor.userId() + throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'createChannel' } + + if RocketChat.authz.hasPermission(Meteor.userId(), 'create-c') isnt true + throw new Meteor.Error 'error-not-allowed', "Not allowed", { method: 'createChannel' } + + return RocketChat.createRoom('c', name, Meteor.user()?.username, members); diff --git a/packages/rocketchat-lib/server/methods/createPrivateGroup.coffee b/packages/rocketchat-lib/server/methods/createPrivateGroup.coffee index cc41a5263dd..e90eec50333 100644 --- a/packages/rocketchat-lib/server/methods/createPrivateGroup.coffee +++ b/packages/rocketchat-lib/server/methods/createPrivateGroup.coffee @@ -6,4 +6,4 @@ Meteor.methods unless RocketChat.authz.hasPermission(Meteor.userId(), 'create-p') throw new Meteor.Error 'error-not-allowed', "Not allowed", { method: 'createPrivateGroup' } - return RocketChat.createPrivateGroup(name, Meteor.user()?.username, members); + return RocketChat.createRoom('p', name, Meteor.user()?.username, members); diff --git a/server/methods/deleteMessage.coffee b/packages/rocketchat-lib/server/methods/deleteMessage.coffee similarity index 60% rename from server/methods/deleteMessage.coffee rename to packages/rocketchat-lib/server/methods/deleteMessage.coffee index ac87740869e..fa7ddfe58e1 100644 --- a/server/methods/deleteMessage.coffee +++ b/packages/rocketchat-lib/server/methods/deleteMessage.coffee @@ -22,27 +22,4 @@ Meteor.methods if currentTsDiff > blockDeleteInMinutes throw new Meteor.Error 'error-message-deleting-blocked', 'Message deleting is blocked', { method: 'deleteMessage' } - - keepHistory = RocketChat.settings.get 'Message_KeepHistory' - showDeletedStatus = RocketChat.settings.get 'Message_ShowDeletedStatus' - - if keepHistory - if showDeletedStatus - RocketChat.models.Messages.cloneAndSaveAsHistoryById originalMessage._id - else - RocketChat.models.Messages.setHiddenById originalMessage._id, true - - if originalMessage.file?._id? - RocketChat.models.Uploads.update originalMessage.file._id, {$set: {_hidden: true}} - - else - if not showDeletedStatus - RocketChat.models.Messages.removeById originalMessage._id - - if originalMessage.file?._id? - FileUpload.delete(originalMessage.file._id) - - if showDeletedStatus - RocketChat.models.Messages.setAsDeletedById originalMessage._id - else - RocketChat.Notifications.notifyRoom originalMessage.rid, 'deleteMessage', {_id: originalMessage._id} + RocketChat.deleteMessage(originalMessage, Meteor.user()); diff --git a/packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee b/packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee index 120eef213c6..3ff88510940 100644 --- a/packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee +++ b/packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee @@ -3,28 +3,5 @@ Meteor.methods if not Meteor.userId() throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'joinDefaultChannels' }) - this.unblock() - - user = Meteor.user() - - RocketChat.callbacks.run 'beforeJoinDefaultChannels', user - - defaultRooms = RocketChat.models.Rooms.findByDefaultAndTypes(true, ['c', 'p'], {fields: {usernames: 0}}).fetch() - - defaultRooms.forEach (room) -> - - # put user in default rooms - RocketChat.models.Rooms.addUsernameById room._id, user.username - - if not RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id)? - - # Add a subscription to this user - RocketChat.models.Subscriptions.createWithRoomAndUser room, user, - ts: new Date() - open: true - alert: true - unread: 1 - - # Insert user joined message - if not silenced - RocketChat.models.Messages.createUserJoinWithRoomIdAndUser room._id, user + this.unblock(); + RocketChat.addUserToDefaultChannels(Meteor.user(), silenced); diff --git a/packages/rocketchat-lib/server/methods/joinRoom.coffee b/packages/rocketchat-lib/server/methods/joinRoom.coffee new file mode 100644 index 00000000000..463f66dec25 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/joinRoom.coffee @@ -0,0 +1,14 @@ +Meteor.methods + joinRoom: (rid) -> + if not Meteor.userId() + throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'joinRoom' } + + room = RocketChat.models.Rooms.findOneById rid + + if not room? + throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'joinRoom' } + + if room.t isnt 'c' or RocketChat.authz.hasPermission(Meteor.userId(), 'view-c-room') isnt true + throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'joinRoom' } + + RocketChat.addUserToRoom(rid, Meteor.user()) diff --git a/server/methods/leaveRoom.coffee b/packages/rocketchat-lib/server/methods/leaveRoom.coffee similarity index 56% rename from server/methods/leaveRoom.coffee rename to packages/rocketchat-lib/server/methods/leaveRoom.coffee index c7931f0c758..f00ab1de6a5 100644 --- a/server/methods/leaveRoom.coffee +++ b/packages/rocketchat-lib/server/methods/leaveRoom.coffee @@ -15,19 +15,4 @@ Meteor.methods if numOwners is 1 throw new Meteor.Error 'error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'leaveRoom' } - RocketChat.callbacks.run 'beforeLeaveRoom', user, room - - RocketChat.models.Rooms.removeUsernameById rid, user.username - - if room.usernames.indexOf(user.username) isnt -1 - removedUser = user - RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser rid, removedUser - - if room.t is 'l' - RocketChat.models.Messages.createCommandWithRoomIdAndUser 'survey', rid, user - - RocketChat.models.Subscriptions.removeByRoomIdAndUserId rid, Meteor.userId() - - Meteor.defer -> - - RocketChat.callbacks.run 'afterLeaveRoom', user, room + RocketChat.removeUserFromRoom(rid, Meteor.user()); diff --git a/server/methods/unarchiveRoom.coffee b/packages/rocketchat-lib/server/methods/unarchiveRoom.coffee similarity index 63% rename from server/methods/unarchiveRoom.coffee rename to packages/rocketchat-lib/server/methods/unarchiveRoom.coffee index 8bf78dea7b1..699398bf06e 100644 --- a/server/methods/unarchiveRoom.coffee +++ b/packages/rocketchat-lib/server/methods/unarchiveRoom.coffee @@ -11,11 +11,4 @@ Meteor.methods unless RocketChat.authz.hasPermission(Meteor.userId(), 'unarchive-room', room._id) throw new Meteor.Error 'error-not-authorized', 'Not authorized', { method: 'unarchiveRoom' } - RocketChat.models.Rooms.unarchiveById rid - - for username in room.usernames - member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }}) - if not member? - continue - - RocketChat.models.Subscriptions.unarchiveByRoomIdAndUserId rid, member._id + RocketChat.unarchiveRoom(rid); diff --git a/server/methods/updateMessage.coffee b/packages/rocketchat-lib/server/methods/updateMessage.coffee similarity index 56% rename from server/methods/updateMessage.coffee rename to packages/rocketchat-lib/server/methods/updateMessage.coffee index 162dc26972d..1e9d27e6569 100644 --- a/server/methods/updateMessage.coffee +++ b/packages/rocketchat-lib/server/methods/updateMessage.coffee @@ -24,29 +24,4 @@ Meteor.methods if currentTsDiff > blockEditInMinutes throw new Meteor.Error 'error-message-editing-blocked', 'Message editing is blocked', { method: 'updateMessage' } - # If we keep history of edits, insert a new message to store history information - if RocketChat.settings.get 'Message_KeepHistory' - RocketChat.models.Messages.cloneAndSaveAsHistoryById originalMessage._id - - message.editedAt = new Date() - message.editedBy = - _id: Meteor.userId() - username: me.username - - if urls = message.msg.match /([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g - message.urls = urls.map (url) -> url: url - - message = RocketChat.callbacks.run 'beforeSaveMessage', message - - tempid = message._id - delete message._id - - RocketChat.models.Messages.update - _id: tempid - , - $set: message - - room = RocketChat.models.Rooms.findOneById message.rid - - Meteor.defer -> - RocketChat.callbacks.run 'afterSaveMessage', RocketChat.models.Messages.findOneById(tempid), room + RocketChat.updateMessage(message, Meteor.user()); diff --git a/packages/rocketchat-lib/server/models/Messages.coffee b/packages/rocketchat-lib/server/models/Messages.coffee index 58da31060e7..bdb9a92571e 100644 --- a/packages/rocketchat-lib/server/models/Messages.coffee +++ b/packages/rocketchat-lib/server/models/Messages.coffee @@ -152,8 +152,7 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base return @update query, update - setAsDeletedById: (_id) -> - me = RocketChat.models.Users.findOneById Meteor.userId() + setAsDeletedByIdAndUser: (_id, user) -> query = _id: _id @@ -166,8 +165,8 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base attachments: [] editedAt: new Date() editedBy: - _id: Meteor.userId() - username: me.username + _id: user._id + username: user.username return @update query, update diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee index 34c7c5a983e..c5b30c6139d 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.coffee +++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee @@ -87,10 +87,9 @@ RocketChat.models.Subscriptions = new class extends RocketChat.models._Base return @find query # UPDATE - archiveByRoomIdAndUserId: (roomId, userId) -> + archiveByRoomId: (roomId) -> query = rid: roomId - 'u._id': userId update = $set: @@ -100,10 +99,9 @@ RocketChat.models.Subscriptions = new class extends RocketChat.models._Base return @update query, update - unarchiveByRoomIdAndUserId: (roomId, userId) -> + unarchiveByRoomId: (roomId) -> query = rid: roomId - 'u._id': userId update = $set: diff --git a/packages/rocketchat-slackbridge/slackbridge.js b/packages/rocketchat-slackbridge/slackbridge.js index d3fa1d06567..ec50802f205 100644 --- a/packages/rocketchat-slackbridge/slackbridge.js +++ b/packages/rocketchat-slackbridge/slackbridge.js @@ -124,13 +124,8 @@ class SlackBridge { } try { - if (isGroup) { - let channel = RocketChat.createPrivateGroup(channelData, users); - channelData.rocketId = channel._id; - } else { - let channel = Meteor.call('createChannel', channelData.name, users); - channelData.rocketId = channel._id; - } + let channel = RocketChat.createRoom(isGroup ? 'p' : 'c', channelData.name, creator, users); + channelData.rocketId = channel._id; } catch (e) { if (!hasRetried) { // If first time trying to create channel fails, could be because of multiple messages received at the same time. Try again once after 1s. @@ -178,30 +173,37 @@ class SlackBridge { userData.name = existingUser.username; } else { userData.rocketId = Accounts.createUser({ email: userData.profile.email, password: Date.now() + userData.name + userData.profile.email.toUpperCase() }); - Meteor.runAsUser(userData.rocketId, () => { - Meteor.call('setUsername', userData.name); - Meteor.call('joinDefaultChannels', true); - let url = null; - if (userData.profile.image_original) { - url = userData.profile.image_original; - } else if (userData.profile.image_512) { - url = userData.profile.image_512; - } - Meteor.call('setAvatarFromService', url, null, 'url'); - // Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600 - if (userData.tz_offset) { - Meteor.call('userSetUtcOffset', userData.tz_offset / 3600); - } - if (userData.profile.real_name) { - RocketChat.models.Users.setName(userData.rocketId, userData.profile.real_name); + let userUpdate = { + $set: { + username: userData.name, + utcOffset: userData.tz_offset / 3600 // Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600 } - }); - // Deleted users are 'inactive' users in Rocket.Chat + }; + + if (userData.profile.real_name) { + userUpdate['name'] = userData.profile.real_name; + } + if (userData.deleted) { - RocketChat.models.Users.setUserActive(userData.rocketId, false); - RocketChat.models.Users.unsetLoginTokens(userData.rocketId); + userUpdate['active'] = false; + userUpdate['services.resume.loginTokens'] = []; } + + RocketChat.models.Users.update({ _id: userData.rocketId }, { $set: userUpdate }); + + let user = RocketChat.models.Users.findOneById(userData.rocketId); + + let url = null; + if (userData.profile.image_original) { + url = userData.profile.image_original; + } else if (userData.profile.image_512) { + url = userData.profile.image_512; + } + RocketChat.setUserAvatar(user, url); + + RocketChat.addUserToDefaultChannels(user); } + RocketChat.models.Users.update({ _id: userData.rocketId }, { $addToSet: { importIds: userData.id } }); if (!this.userTags[userId]) { this.userTags[userId] = { slack: `<@${userId}>`, rocket: `@${userData.name}` }; @@ -306,45 +308,48 @@ class SlackBridge { this.editMessage(room, user, message); return; case 'message_deleted': - msgObj = RocketChat.models.Messages.findOneById(`${message.channel}S${message.deleted_ts}`); - if (msgObj) { - Meteor.runAsUser(user._id, () => { - Meteor.call('deleteMessage', msgObj); - }); + if (message.previous_message) { + let _id = `slack-${message.channel}-${message.previous_message.ts.replace(/\./g, '-')}`; + msgObj = RocketChat.models.Messages.findOneById(_id); + if (msgObj) { + RocketChat.deleteMessage(msgObj, user); + } } return; case 'channel_join': - return this.joinRoom(room, user); + RocketChat.addUserToRoom(room._id, user); + return; case 'group_join': if (message.inviter) { let inviter = message.inviter ? this.findUser(message.inviter) || this.addUser(message.inviter) : null; - if (inviter) { - return this.joinPrivateGroup(inviter, room, user); - } + RocketChat.addUserToRoom(room._id, user, inviter); + return; } break; case 'channel_leave': case 'group_leave': - return this.leaveRoom(room, user); + RocketChat.removeUserFromRoom(room._id, user); + return; case 'channel_topic': case 'group_topic': - this.setRoomTopic(room, user, message.topic); + RocketChat.saveRoomTopic(room._id, message.topic, user); return; case 'channel_purpose': case 'group_purpose': - this.setRoomTopic(room, user, message.purpose); + RocketChat.saveRoomTopic(room._id, message.purpose, user); return; case 'channel_name': case 'group_name': - this.setRoomName(room, user, message.name); + let name = RocketChat.saveRoomName(room._id, message.name); + RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(room._id, name, user); return; case 'channel_archive': case 'group_archive': - this.archiveRoom(room, user); + RocketChat.archiveRoom(room); return; case 'channel_unarchive': case 'group_unarchive': - this.unarchiveRoom(room, user); + RocketChat.unarchiveRoom(room); return; case 'file_share': if (message.file && message.file.url_private_download !== undefined) { @@ -395,81 +400,18 @@ class SlackBridge { } } - /** - * Archives a room - **/ - archiveRoom(room, user) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('archiveRoom', room._id); - }); - } - - /** - * Unarchives a room - **/ - unarchiveRoom(room, user) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('unarchiveRoom', room._id); - }); - } - - /** - * Adds user to room and sends a message - **/ - joinRoom(room, user) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('joinRoom', room._id); - }); - } - - /** - * Adds user to room and sends a message - **/ - joinPrivateGroup(inviter, room, user) { - Meteor.runAsUser(inviter._id, () => { - return Meteor.call('addUserToRoom', { rid: room._id, username: user.username }); - }); - } - - /** - * Removes user from room and sends a message - **/ - leaveRoom(room, user) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('leaveRoom', room._id); - }); - } - - /** - * Sets room topic - **/ - setRoomTopic(room, user, topic) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('saveRoomSettings', room._id, 'roomTopic', topic); - }); - } - - /** - * Sets room name - **/ - setRoomName(room, user, name) { - Meteor.runAsUser(user._id, () => { - return Meteor.call('saveRoomSettings', room._id, 'roomName', name); - }); - } - /** * Edits a message **/ editMessage(room, user, message) { let msgObj = { - _id: `${message.channel}S${message.message.ts}`, + //@TODO _id + _id: `slack-${message.channel}-${message.message.ts.replace(/\./g, '-')}`, rid: room._id, msg: this.convertSlackMessageToRocketChat(message.message.text) }; - Meteor.runAsUser(user._id, () => { - return Meteor.call('updateMessage', msgObj); - }); + + RocketChat.updateMessage(msgObj, user); } /** diff --git a/server/methods/createChannel.coffee b/server/methods/createChannel.coffee deleted file mode 100644 index 1cc3fcf1201..00000000000 --- a/server/methods/createChannel.coffee +++ /dev/null @@ -1,65 +0,0 @@ -Meteor.methods - createChannel: (name, members) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'createChannel' } - - try - nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$' - catch - nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$' - - if not nameValidation.test name - throw new Meteor.Error 'error-invalid-name', "Invalid name", { method: 'createChannel' } - - if RocketChat.authz.hasPermission(Meteor.userId(), 'create-c') isnt true - throw new Meteor.Error 'error-not-allowed', "Not allowed", { method: 'createChannel' } - - now = new Date() - user = Meteor.user() - - members.push user.username if user.username not in members - - # avoid duplicate names - if RocketChat.models.Rooms.findOneByName name - if RocketChat.models.Rooms.findOneByName(name).archived - throw new Meteor.Error 'error-archived-duplicate-name', "There's an archived channel with name " + name, { method: 'createChannel', room_name: name } - else - throw new Meteor.Error 'error-duplicate-channel-name', "A channel with name '" + name + "' exists", { method: 'createChannel', room_name: name } - - # name = s.slugify name - - RocketChat.callbacks.run 'beforeCreateChannel', user, - t: 'c' - name: name - ts: now - usernames: members - u: - _id: user._id - username: user.username - - # create new room - room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames 'c', name, user, members, - ts: now - - for username in members - member = RocketChat.models.Users.findOneByUsername username - if not member? - continue - - extra = - open: true - - if username is user.username - extra.ls = now - - RocketChat.models.Subscriptions.createWithRoomAndUser room, member, extra - - # set creator as channel moderator. permission limited to channel by scoping to rid - RocketChat.authz.addUserRoles(Meteor.userId(), ['owner'], room._id) - - Meteor.defer -> - RocketChat.callbacks.run 'afterCreateChannel', user, room - - return { - rid: room._id - } diff --git a/server/methods/joinRoom.coffee b/server/methods/joinRoom.coffee deleted file mode 100644 index 5cfe335253d..00000000000 --- a/server/methods/joinRoom.coffee +++ /dev/null @@ -1,39 +0,0 @@ -Meteor.methods - joinRoom: (rid) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'joinRoom' } - - room = RocketChat.models.Rooms.findOneById rid - - if not room? - throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'joinRoom' } - - if room.t isnt 'c' or RocketChat.authz.hasPermission(Meteor.userId(), 'view-c-room') isnt true - throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'joinRoom' } - - now = new Date() - - # Check if user is already in room - subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, Meteor.userId() - if subscription? - return - - user = RocketChat.models.Users.findOneById Meteor.userId() - - RocketChat.callbacks.run 'beforeJoinRoom', user, room - - RocketChat.models.Rooms.addUsernameById rid, user.username - - RocketChat.models.Subscriptions.createWithRoomAndUser room, user, - ts: now - open: true - alert: true - unread: 1 - - RocketChat.models.Messages.createUserJoinWithRoomIdAndUser rid, user, - ts: now - - Meteor.defer -> - RocketChat.callbacks.run 'afterJoinRoom', user, room - - return true diff --git a/server/methods/setAvatarFromService.coffee b/server/methods/setAvatarFromService.coffee index ad516e8107f..dedcddea7ad 100644 --- a/server/methods/setAvatarFromService.coffee +++ b/server/methods/setAvatarFromService.coffee @@ -8,53 +8,7 @@ Meteor.methods user = Meteor.user() - if service is 'initials' - RocketChat.models.Users.setAvatarOrigin user._id, service - return - - if service is 'url' - result = null - - try - result = HTTP.get dataURI, npmRequestOptions: {encoding: 'binary'} - catch e - console.log "Error while handling the setting of the avatar from a url (#{dataURI}) for #{user.username}:", e - throw new Meteor.Error('error-avatar-url-handling', 'Error while handling avatar setting from a URL ('+ dataURI +') for ' + user.username, { method: 'setAvatarFromService', url: dataURI, username: user.username }); - - if result.statusCode isnt 200 - console.log "Not a valid response, #{result.statusCode}, from the avatar url: #{dataURI}" - throw new Meteor.Error('error-avatar-invalid-url', 'Invalid avatar URL: ' + dataURI, { method: 'setAvatarFromService', url: dataURI }) - - if not /image\/.+/.test result.headers['content-type'] - console.log "Not a valid content-type from the provided url, #{result.headers['content-type']}, from the avatar url: #{dataURI}" - throw new Meteor.Error('error-avatar-invalid-url', 'Invalid avatar URL: ' + dataURI, { method: 'setAvatarFromService', url: dataURI }) - - ars = RocketChatFile.bufferToStream new Buffer(result.content, 'binary') - RocketChatFileAvatarInstance.deleteFile encodeURIComponent("#{user.username}.jpg") - aws = RocketChatFileAvatarInstance.createWriteStream encodeURIComponent("#{user.username}.jpg"), result.headers['content-type'] - aws.on 'end', Meteor.bindEnvironment -> - Meteor.setTimeout -> - console.log "Set #{user.username}'s avatar from the url: #{dataURI}" - RocketChat.models.Users.setAvatarOrigin user._id, service - RocketChat.Notifications.notifyAll 'updateAvatar', { username: user.username } - , 500 - - ars.pipe(aws) - return - - {image, contentType} = RocketChatFile.dataURIParse dataURI - - rs = RocketChatFile.bufferToStream new Buffer(image, 'base64') - RocketChatFileAvatarInstance.deleteFile encodeURIComponent("#{user.username}.jpg") - ws = RocketChatFileAvatarInstance.createWriteStream encodeURIComponent("#{user.username}.jpg"), contentType - ws.on 'end', Meteor.bindEnvironment -> - Meteor.setTimeout -> - RocketChat.models.Users.setAvatarOrigin user._id, service - RocketChat.Notifications.notifyAll 'updateAvatar', {username: user.username} - , 500 - - rs.pipe(ws) - return + return RocketChat.setUserAvatar(user, dataURI, contentType, service); DDPRateLimiter.addRule type: 'method'