From 1c2e35bd936d594aaa0a00b3decd76fc8908ed6e Mon Sep 17 00:00:00 2001
From: Guilherme Gazzo <guilhermegazzo@gmail.com>
Date: Wed, 19 Apr 2017 14:02:52 -0300
Subject: [PATCH] finished conversion rocketchat-lib

---
 packages/rocketchat-lib/package.js            |  25 +-
 .../server/functions/Notifications.coffee     | 119 ---
 .../server/functions/Notifications.js         | 132 +++
 .../functions/checkUsernameAvailability.js    |  20 +
 .../server/functions/sendMessage.coffee       |  50 --
 .../server/functions/sendMessage.js           |  59 ++
 .../server/functions/setUsername.coffee       |  77 --
 .../server/functions/setUsername.js           |  85 ++
 .../server/functions/settings.coffee          | 248 ------
 .../server/functions/settings.js              | 283 +++++++
 .../server/methods/sendMessage.coffee         |  64 --
 .../server/methods/sendMessage.js             |  81 ++
 .../rocketchat-lib/server/models/Messages.js  | 580 +++++++++++++
 .../rocketchat-lib/server/models/Rooms.js     | 760 ++++++++++++++++++
 .../rocketchat-lib/server/models/Settings.js  | 178 ++++
 .../server/models/Subscriptions.js            | 570 +++++++++++++
 .../rocketchat-lib/server/models/Uploads.js   |  91 +++
 .../rocketchat-lib/server/models/Users.js     | 538 +++++++++++++
 18 files changed, 3389 insertions(+), 571 deletions(-)
 delete mode 100644 packages/rocketchat-lib/server/functions/Notifications.coffee
 create mode 100644 packages/rocketchat-lib/server/functions/Notifications.js
 create mode 100644 packages/rocketchat-lib/server/functions/checkUsernameAvailability.js
 delete mode 100644 packages/rocketchat-lib/server/functions/sendMessage.coffee
 create mode 100644 packages/rocketchat-lib/server/functions/sendMessage.js
 delete mode 100644 packages/rocketchat-lib/server/functions/setUsername.coffee
 create mode 100644 packages/rocketchat-lib/server/functions/setUsername.js
 delete mode 100644 packages/rocketchat-lib/server/functions/settings.coffee
 create mode 100644 packages/rocketchat-lib/server/functions/settings.js
 delete mode 100644 packages/rocketchat-lib/server/methods/sendMessage.coffee
 create mode 100644 packages/rocketchat-lib/server/methods/sendMessage.js
 create mode 100644 packages/rocketchat-lib/server/models/Messages.js
 create mode 100644 packages/rocketchat-lib/server/models/Rooms.js
 create mode 100644 packages/rocketchat-lib/server/models/Settings.js
 create mode 100644 packages/rocketchat-lib/server/models/Subscriptions.js
 create mode 100644 packages/rocketchat-lib/server/models/Uploads.js
 create mode 100644 packages/rocketchat-lib/server/models/Users.js

diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js
index 14a27f270f0..b3bccca8c18 100644
--- a/packages/rocketchat-lib/package.js
+++ b/packages/rocketchat-lib/package.js
@@ -21,7 +21,6 @@ Package.onUse(function(api) {
 	api.use('reactive-var');
 	api.use('reactive-dict');
 	api.use('accounts-base');
-	api.use('coffeescript');
 	api.use('ecmascript');
 	api.use('random');
 	api.use('check');
@@ -72,7 +71,7 @@ Package.onUse(function(api) {
 	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/checkUsernameAvailability.js', 'server');
 	api.addFiles('server/functions/checkEmailAvailability.js', 'server');
 	api.addFiles('server/functions/createRoom.js', 'server');
 	api.addFiles('server/functions/deleteMessage.js', 'server');
@@ -82,15 +81,15 @@ Package.onUse(function(api) {
 	api.addFiles('server/functions/removeUserFromRoom.js', 'server');
 	api.addFiles('server/functions/saveUser.js', 'server');
 	api.addFiles('server/functions/saveCustomFields.js', 'server');
-	api.addFiles('server/functions/sendMessage.coffee', 'server');
-	api.addFiles('server/functions/settings.coffee', 'server');
+	api.addFiles('server/functions/sendMessage.js', 'server');
+	api.addFiles('server/functions/settings.js', 'server');
 	api.addFiles('server/functions/setUserAvatar.js', 'server');
-	api.addFiles('server/functions/setUsername.coffee', 'server');
+	api.addFiles('server/functions/setUsername.js', 'server');
 	api.addFiles('server/functions/setRealName.js', 'server');
 	api.addFiles('server/functions/setEmail.js', 'server');
 	api.addFiles('server/functions/unarchiveRoom.js', 'server');
 	api.addFiles('server/functions/updateMessage.js', 'server');
-	api.addFiles('server/functions/Notifications.coffee', 'server');
+	api.addFiles('server/functions/Notifications.js', 'server');
 
 	// SERVER LIB
 	api.addFiles('server/lib/configLogger.js', 'server');
@@ -104,13 +103,13 @@ Package.onUse(function(api) {
 
 	// SERVER MODELS
 	api.addFiles('server/models/_Base.js', 'server');
-	api.addFiles('server/models/Messages.coffee', 'server');
+	api.addFiles('server/models/Messages.js', 'server');
 	api.addFiles('server/models/Reports.js', 'server');
-	api.addFiles('server/models/Rooms.coffee', 'server');
-	api.addFiles('server/models/Settings.coffee', 'server');
-	api.addFiles('server/models/Subscriptions.coffee', 'server');
-	api.addFiles('server/models/Uploads.coffee', 'server');
-	api.addFiles('server/models/Users.coffee', 'server');
+	api.addFiles('server/models/Rooms.js', 'server');
+	api.addFiles('server/models/Settings.js', 'server');
+	api.addFiles('server/models/Subscriptions.js', 'server');
+	api.addFiles('server/models/Uploads.js', 'server');
+	api.addFiles('server/models/Users.js', 'server');
 
 	api.addFiles('server/oauth/oauth.js', 'server');
 	api.addFiles('server/oauth/google.js', 'server');
@@ -153,7 +152,7 @@ Package.onUse(function(api) {
 	api.addFiles('server/methods/robotMethods.js', 'server');
 	api.addFiles('server/methods/saveSetting.js', 'server');
 	api.addFiles('server/methods/sendInvitationEmail.js', 'server');
-	api.addFiles('server/methods/sendMessage.coffee', 'server');
+	api.addFiles('server/methods/sendMessage.js', 'server');
 	api.addFiles('server/methods/sendSMTPTestEmail.js', 'server');
 	api.addFiles('server/methods/setAdminStatus.js', 'server');
 	api.addFiles('server/methods/setRealName.js', 'server');
diff --git a/packages/rocketchat-lib/server/functions/Notifications.coffee b/packages/rocketchat-lib/server/functions/Notifications.coffee
deleted file mode 100644
index 9e08663db6c..00000000000
--- a/packages/rocketchat-lib/server/functions/Notifications.coffee
+++ /dev/null
@@ -1,119 +0,0 @@
-RocketChat.Notifications = new class
-	constructor: ->
-		self = @
-
-		@debug = false
-
-		@streamAll = new Meteor.Streamer 'notify-all'
-		@streamLogged = new Meteor.Streamer 'notify-logged'
-		@streamRoom = new Meteor.Streamer 'notify-room'
-		@streamRoomUsers = new Meteor.Streamer 'notify-room-users'
-		@streamUser = new Meteor.Streamer 'notify-user'
-
-
-		@streamAll.allowWrite('none')
-		@streamLogged.allowWrite('none')
-		@streamRoom.allowWrite('none')
-		@streamRoomUsers.allowWrite (eventName, args...) ->
-			[roomId, e] = eventName.split('/')
-
-			user = Meteor.users.findOne @userId, {fields: {username: 1}}
-			if RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, @userId)?
-				subscriptions = RocketChat.models.Subscriptions.findByRoomIdAndNotUserId(roomId, @userId).fetch()
-				for subscription in subscriptions
-					RocketChat.Notifications.notifyUser(subscription.u._id, e, args...)
-
-			return false
-
-		@streamUser.allowWrite('logged')
-
-		@streamAll.allowRead('all')
-
-		@streamLogged.allowRead('logged')
-
-		@streamRoom.allowRead (eventName) ->
-			if not @userId? then return false
-
-			roomId = eventName.split('/')[0]
-
-			user = Meteor.users.findOne @userId, {fields: {username: 1}}
-			room = RocketChat.models.Rooms.findOneById(roomId)
-			if room.t is 'l' and room.v._id is user._id
-				return true
-
-			return room.usernames.indexOf(user.username) > -1
-
-		@streamRoomUsers.allowRead('none');
-
-		@streamUser.allowRead (eventName) ->
-			userId = eventName.split('/')[0]
-			return @userId? and @userId is userId
-
-
-	notifyAll: (eventName, args...) ->
-		console.log 'notifyAll', arguments if @debug is true
-
-		args.unshift eventName
-		@streamAll.emit.apply @streamAll, args
-
-	notifyLogged: (eventName, args...) ->
-		console.log 'notifyLogged', arguments if @debug is true
-
-		args.unshift eventName
-		@streamLogged.emit.apply @streamLogged, args
-
-	notifyRoom: (room, eventName, args...) ->
-		console.log 'notifyRoom', arguments if @debug is true
-
-		args.unshift "#{room}/#{eventName}"
-		@streamRoom.emit.apply @streamRoom, args
-
-	notifyUser: (userId, eventName, args...) ->
-		console.log 'notifyUser', arguments if @debug is true
-
-		args.unshift "#{userId}/#{eventName}"
-		@streamUser.emit.apply @streamUser, args
-
-
-	notifyAllInThisInstance: (eventName, args...) ->
-		console.log 'notifyAll', arguments if @debug is true
-
-		args.unshift eventName
-		@streamAll.emitWithoutBroadcast.apply @streamAll, args
-
-	notifyLoggedInThisInstance: (eventName, args...) ->
-		console.log 'notifyLogged', arguments if @debug is true
-
-		args.unshift eventName
-		@streamLogged.emitWithoutBroadcast.apply @streamLogged, args
-
-	notifyRoomInThisInstance: (room, eventName, args...) ->
-		console.log 'notifyRoomAndBroadcast', arguments if @debug is true
-
-		args.unshift "#{room}/#{eventName}"
-		@streamRoom.emitWithoutBroadcast.apply @streamRoom, args
-
-	notifyUserInThisInstance: (userId, eventName, args...) ->
-		console.log 'notifyUserAndBroadcast', arguments if @debug is true
-
-		args.unshift "#{userId}/#{eventName}"
-		@streamUser.emitWithoutBroadcast.apply @streamUser, args
-
-
-## Permissions for client
-
-# Enable emit for event typing for rooms and add username to event data
-func = (eventName, username) ->
-	[room, e] = eventName.split('/')
-
-	if e is 'webrtc'
-		return true
-
-	if e is 'typing'
-		user = Meteor.users.findOne(@userId, {fields: {username: 1}})
-		if user?.username is username
-			return true
-
-	return false
-
-RocketChat.Notifications.streamRoom.allowWrite func
diff --git a/packages/rocketchat-lib/server/functions/Notifications.js b/packages/rocketchat-lib/server/functions/Notifications.js
new file mode 100644
index 00000000000..89fec97a279
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/Notifications.js
@@ -0,0 +1,132 @@
+RocketChat.Notifications = new class {
+	constructor() {
+		this.debug = false;
+		this.streamAll = new Meteor.Streamer('notify-all');
+		this.streamLogged = new Meteor.Streamer('notify-logged');
+		this.streamRoom = new Meteor.Streamer('notify-room');
+		this.streamRoomUsers = new Meteor.Streamer('notify-room-users');
+		this.streamUser = new Meteor.Streamer('notify-user');
+		this.streamAll.allowWrite('none');
+		this.streamLogged.allowWrite('none');
+		this.streamRoom.allowWrite('none');
+		this.streamRoomUsers.allowWrite(function(eventName, ...args) {
+			const [roomId, e] = eventName.split('/');
+			// const user = Meteor.users.findOne(this.userId, {
+			// 	fields: {
+			// 		username: 1
+			// 	}
+			// });
+			if (RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) {
+				const subscriptions = RocketChat.models.Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch();
+				subscriptions.forEach(subscription => RocketChat.Notifications.notifyUser(subscription.u._id, e, ...args));
+			}
+			return false;
+		});
+		this.streamUser.allowWrite('logged');
+		this.streamAll.allowRead('all');
+		this.streamLogged.allowRead('logged');
+		this.streamRoom.allowRead(function(eventName) {
+			if (this.userId == null) {
+				return false;
+			}
+			const [roomId] = eventName.split('/');
+			const user = Meteor.users.findOne(this.userId, {
+				fields: {
+					username: 1
+				}
+			});
+			const room = RocketChat.models.Rooms.findOneById(roomId);
+			if (room.t === 'l' && room.v._id === user._id) {
+				return true;
+			}
+			return room.usernames.indexOf(user.username) > -1;
+		});
+		this.streamRoomUsers.allowRead('none');
+		this.streamUser.allowRead(function(eventName) {
+			const [userId] = eventName.split('/');
+			return (this.userId != null) && this.userId === userId;
+		});
+	}
+
+	notifyAll(eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyAll', arguments);
+		}
+		args.unshift(eventName);
+		return this.streamAll.emit.apply(this.streamAll, args);
+	}
+
+	notifyLogged(eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyLogged', arguments);
+		}
+		args.unshift(eventName);
+		return this.streamLogged.emit.apply(this.streamLogged, args);
+	}
+
+	notifyRoom(room, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyRoom', arguments);
+		}
+		args.unshift(`${ room }/${ eventName }`);
+		return this.streamRoom.emit.apply(this.streamRoom, args);
+	}
+
+	notifyUser(userId, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyUser', arguments);
+		}
+		args.unshift(`${ userId }/${ eventName }`);
+		return this.streamUser.emit.apply(this.streamUser, args);
+	}
+
+	notifyAllInThisInstance(eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyAll', arguments);
+		}
+		args.unshift(eventName);
+		return this.streamAll.emitWithoutBroadcast.apply(this.streamAll, args);
+	}
+
+	notifyLoggedInThisInstance(eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyLogged', arguments);
+		}
+		args.unshift(eventName);
+		return this.streamLogged.emitWithoutBroadcast.apply(this.streamLogged, args);
+	}
+
+	notifyRoomInThisInstance(room, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyRoomAndBroadcast', arguments);
+		}
+		args.unshift(`${ room }/${ eventName }`);
+		return this.streamRoom.emitWithoutBroadcast.apply(this.streamRoom, args);
+	}
+
+	notifyUserInThisInstance(userId, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('notifyUserAndBroadcast', arguments);
+		}
+		args.unshift(`${ userId }/${ eventName }`);
+		return this.streamUser.emitWithoutBroadcast.apply(this.streamUser, args);
+	}
+};
+
+RocketChat.Notifications.streamRoom.allowWrite(function(eventName, username) {
+	const [, e] = eventName.split('/');
+	if (e === 'webrtc') {
+		return true;
+	}
+	if (e === 'typing') {
+		const user = Meteor.users.findOne(this.userId, {
+			fields: {
+				username: 1
+			}
+		});
+		if (user != null && user.username === username) {
+			return true;
+		}
+	}
+	return false;
+});
diff --git a/packages/rocketchat-lib/server/functions/checkUsernameAvailability.js b/packages/rocketchat-lib/server/functions/checkUsernameAvailability.js
new file mode 100644
index 00000000000..01e8f8c1b10
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/checkUsernameAvailability.js
@@ -0,0 +1,20 @@
+RocketChat.checkUsernameAvailability = function(username) {
+	return RocketChat.settings.get('Accounts_BlockedUsernameList', function(key, value) {
+		const usernameBlackList = _.map(value.split(','), function(username) {
+			return username.trim();
+		});
+		if (usernameBlackList.length !== 0) {
+			if (usernameBlackList.every(restrictedUsername => {
+				const regex = new RegExp(`^${ s.escapeRegExp(restrictedUsername) }$`, 'i');
+				return !regex.test(s.trim(s.escapeRegExp(username)));
+			})) {
+				return !Meteor.users.findOne({
+					username: {
+						$regex: new RegExp(`^${ s.trim(s.escapeRegExp(username)) }$`, 'i')
+					}
+				});
+			}
+			return false;
+		}
+	});
+};
diff --git a/packages/rocketchat-lib/server/functions/sendMessage.coffee b/packages/rocketchat-lib/server/functions/sendMessage.coffee
deleted file mode 100644
index bdce87faba4..00000000000
--- a/packages/rocketchat-lib/server/functions/sendMessage.coffee
+++ /dev/null
@@ -1,50 +0,0 @@
-RocketChat.sendMessage = (user, message, room, upsert = false) ->
-	if not user or not message or not room._id
-		return false
-
-	unless message.ts?
-		message.ts = new Date()
-
-	message.u = _.pick user, ['_id','username']
-
-	if not Match.test(message.msg, String)
-		message.msg = ''
-
-	message.rid = room._id
-
-	if not room.usernames? || room.usernames.length is 0
-		updated_room = RocketChat.models.Rooms.findOneById(room._id)
-		if updated_room?
-			room = updated_room
-		else
-			room.usernames = []
-
-	if message.parseUrls isnt false
-		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
-
-	# Avoid saving sandstormSessionId to the database
-	sandstormSessionId = null
-	if message.sandstormSessionId
-		sandstormSessionId = message.sandstormSessionId
-		delete message.sandstormSessionId
-
-	if message._id? and upsert
-		_id = message._id
-		delete message._id
-		RocketChat.models.Messages.upsert {_id: _id, 'u._id': message.u._id}, message
-		message._id = _id
-	else
-		message._id = RocketChat.models.Messages.insert message
-
-	###
-	Defer other updates as their return is not interesting to the user
-	###
-	Meteor.defer ->
-		# Execute all callbacks
-		message.sandstormSessionId = sandstormSessionId
-		RocketChat.callbacks.run 'afterSaveMessage', message, room
-
-	return message
diff --git a/packages/rocketchat-lib/server/functions/sendMessage.js b/packages/rocketchat-lib/server/functions/sendMessage.js
new file mode 100644
index 00000000000..d0e4181e733
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/sendMessage.js
@@ -0,0 +1,59 @@
+RocketChat.sendMessage = function(user, message, room, upsert = false) {
+	if (!user || !message || !room._id) {
+		return false;
+	}
+	if (message.ts == null) {
+		message.ts = new Date();
+	}
+	message.u = _.pick(user, ['_id', 'username', 'name']);
+	if (!Match.test(message.msg, String)) {
+		message.msg = '';
+	}
+	message.rid = room._id;
+	if (room.usernames || room.usernames.length === 0) {
+		const updated_room = RocketChat.models.Rooms.findOneById(room._id);
+		if (updated_room != null) {
+			room = updated_room;
+		} else {
+			room.usernames = [];
+		}
+	}
+	if (message.parseUrls !== false) {
+		const 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(function(url) {
+				return {
+					url
+				};
+			});
+		}
+	}
+	message = RocketChat.callbacks.run('beforeSaveMessage', message);
+	// Avoid saving sandstormSessionId to the database
+	let sandstormSessionId = null;
+	if (message.sandstormSessionId) {
+		sandstormSessionId = message.sandstormSessionId;
+		delete message.sandstormSessionId;
+	}
+	if (message._id && upsert) {
+		const _id = message._id;
+		delete message._id;
+		RocketChat.models.Messages.upsert({
+			_id,
+			'u._id': message.u._id
+		}, message);
+		message._id = _id;
+	} else {
+		message._id = RocketChat.models.Messages.insert(message);
+	}
+
+	/*
+	Defer other updates as their return is not interesting to the user
+	*/
+	Meteor.defer(() => {
+		// Execute all callbacks
+		message.sandstormSessionId = sandstormSessionId;
+		return RocketChat.callbacks.run('afterSaveMessage', message, room);
+	});
+	return message;
+};
diff --git a/packages/rocketchat-lib/server/functions/setUsername.coffee b/packages/rocketchat-lib/server/functions/setUsername.coffee
deleted file mode 100644
index a3afa4a49e9..00000000000
--- a/packages/rocketchat-lib/server/functions/setUsername.coffee
+++ /dev/null
@@ -1,77 +0,0 @@
-RocketChat._setUsername = (userId, username) ->
-	username = s.trim username
-	if not userId or not username
-		return false
-
-	try
-		nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$'
-	catch
-		nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$'
-
-	if not nameValidation.test username
-		return false
-
-	user = RocketChat.models.Users.findOneById userId
-
-	# User already has desired username, return
-	if user.username is username
-		return user
-
-	previousUsername = user.username
-
-	# Check username availability or if the user already owns a different casing of the name
-	if ( !previousUsername or !(username.toLowerCase() == previousUsername.toLowerCase()))
-		unless RocketChat.checkUsernameAvailability username
-			return false
-
-	# If first time setting username, send Enrollment Email
-	try
-		if not previousUsername and user.emails?.length > 0 and RocketChat.settings.get 'Accounts_Enrollment_Email'
-			Accounts.sendEnrollmentEmail(user._id)
-	catch error
-
-	user.username = username
-
-	# If first time setting username, check if should set default avatar
-	if not previousUsername and RocketChat.settings.get('Accounts_SetDefaultAvatar') is true
-		avatarSuggestions = getAvatarSuggestionForUser user
-		for service, avatarData of avatarSuggestions
-			if service isnt 'gravatar'
-				RocketChat.setUserAvatar(user, avatarData.blob, avatarData.contentType, service)
-				gravatar = null
-				break
-			else
-				gravatar = avatarData
-		if gravatar?
-			RocketChat.setUserAvatar(user, gravatar.blob, gravatar.contentType, 'gravatar')
-
-	# Username is available; if coming from old username, update all references
-	if previousUsername
-		RocketChat.models.Messages.updateAllUsernamesByUserId user._id, username
-		RocketChat.models.Messages.updateUsernameOfEditByUserId user._id, username
-
-		RocketChat.models.Messages.findByMention(previousUsername).forEach (msg) ->
-			updatedMsg = msg.msg.replace(new RegExp("@#{previousUsername}", "ig"), "@#{username}")
-			RocketChat.models.Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername msg._id, previousUsername, username, updatedMsg
-
-		RocketChat.models.Rooms.replaceUsername previousUsername, username
-		RocketChat.models.Rooms.replaceMutedUsername previousUsername, username
-		RocketChat.models.Rooms.replaceUsernameOfUserByUserId user._id, 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)
-
-	# Set new username
-	RocketChat.models.Users.setUsername user._id, username
-	return user
-
-RocketChat.setUsername = RocketChat.RateLimiter.limitFunction RocketChat._setUsername, 1, 60000,
-	0: () -> return not Meteor.userId() or not RocketChat.authz.hasPermission(Meteor.userId(), 'edit-other-user-info') # Administrators have permission to change others usernames, so don't limit those
diff --git a/packages/rocketchat-lib/server/functions/setUsername.js b/packages/rocketchat-lib/server/functions/setUsername.js
new file mode 100644
index 00000000000..455e6cfbc67
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/setUsername.js
@@ -0,0 +1,85 @@
+
+RocketChat._setUsername = function(userId, u) {
+	const username = s.trim(u);
+	if (!userId || !username) {
+		return false;
+	}
+	let nameValidation;
+	try {
+		nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
+	} catch (error) {
+		nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
+	}
+	if (!nameValidation.test(username)) {
+		return false;
+	}
+	const user = RocketChat.models.Users.findOneById(userId);
+	// User already has desired username, return
+	if (user.username === username) {
+		return user;
+	}
+	const previousUsername = user.username;
+	// Check username availability or if the user already owns a different casing of the name
+	if (!previousUsername || !(username.toLowerCase() === previousUsername.toLowerCase())) {
+		if (!RocketChat.checkUsernameAvailability(username)) {
+			return false;
+		}
+	}
+	//If first time setting username, send Enrollment Email
+	try {
+		if (!previousUsername && user.emails && user.emails.length > 0 && RocketChat.settings.get('Accounts_Enrollment_Email')) {
+			Accounts.sendEnrollmentEmail(user._id);
+		}
+	} catch (e) {
+		console.error(e);
+	}
+	/* globals getAvatarSuggestionForUser */
+	user.username = username;
+	if (!previousUsername && RocketChat.settings.get('Accounts_SetDefaultAvatar') === true) {
+		const avatarSuggestions = getAvatarSuggestionForUser(user);
+		let gravatar;
+		Object.keys(avatarSuggestions).some(service => {
+			const avatarData = avatarSuggestions[service];
+			if (service !== 'gravatar') {
+				RocketChat.setUserAvatar(user, avatarData.blob, avatarData.contentType, service);
+				gravatar = null;
+				return true;
+			} else {
+				gravatar = avatarData;
+			}
+		});
+		if (gravatar != null) {
+			RocketChat.setUserAvatar(user, gravatar.blob, gravatar.contentType, 'gravatar');
+		}
+	}
+	// Username is available; if coming from old username, update all references
+	if (previousUsername) {
+		RocketChat.models.Messages.updateAllUsernamesByUserId(user._id, username);
+		RocketChat.models.Messages.updateUsernameOfEditByUserId(user._id, username);
+		RocketChat.models.Messages.findByMention(previousUsername).forEach(function(msg) {
+			const updatedMsg = msg.msg.replace(new RegExp(`@${ previousUsername }`, 'ig'), `@${ username }`);
+			return RocketChat.models.Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername(msg._id, previousUsername, username, updatedMsg);
+		});
+		RocketChat.models.Rooms.replaceUsername(previousUsername, username);
+		RocketChat.models.Rooms.replaceMutedUsername(previousUsername, username);
+		RocketChat.models.Rooms.replaceUsernameOfUserByUserId(user._id, username);
+		RocketChat.models.Subscriptions.setUserUsernameByUserId(user._id, username);
+		RocketChat.models.Subscriptions.setNameForDirectRoomsWithOldName(previousUsername, username);
+		const rs = RocketChatFileAvatarInstance.getFileWithReadStream(encodeURIComponent(`${ previousUsername }.jpg`));
+		if (rs != null) {
+			RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ username }.jpg`));
+			const ws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${ username }.jpg`), rs.contentType);
+			ws.on('end', Meteor.bindEnvironment(() => RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ previousUsername }.jpg`))));
+			rs.readStream.pipe(ws);
+		}
+	}
+	// Set new username*
+	RocketChat.models.Users.setUsername(user._id, username);
+	return user;
+};
+
+RocketChat.setUsername = RocketChat.RateLimiter.limitFunction(RocketChat._setUsername, 1, 60000, {
+	[0](userId) {
+		return !userId || !RocketChat.authz.hasPermission(userId, 'edit-other-user-info');
+	}
+});
diff --git a/packages/rocketchat-lib/server/functions/settings.coffee b/packages/rocketchat-lib/server/functions/settings.coffee
deleted file mode 100644
index 1098cd7941d..00000000000
--- a/packages/rocketchat-lib/server/functions/settings.coffee
+++ /dev/null
@@ -1,248 +0,0 @@
-blockedSettings = {}
-process.env.SETTINGS_BLOCKED?.split(',').forEach (settingId) ->
-	blockedSettings[settingId] = 1
-
-hiddenSettings = {}
-process.env.SETTINGS_HIDDEN?.split(',').forEach (settingId) ->
-	hiddenSettings[settingId] = 1
-
-RocketChat.settings._sorter = {}
-
-###
-# Add a setting
-# @param {String} _id
-# @param {Mixed} value
-# @param {Object} setting
-###
-RocketChat.settings.add = (_id, value, options = {}) ->
-	# console.log '[functions] RocketChat.settings.add -> '.green, 'arguments:', arguments
-
-	if not _id or
-		not value? and not process?.env?['OVERWRITE_SETTING_' + _id]?
-			return false
-
-	RocketChat.settings._sorter[options.group] ?= 0
-
-	options.packageValue = value
-	options.valueSource = 'packageValue'
-	options.hidden = false
-	options.blocked = options.blocked || false
-	options.sorter ?= RocketChat.settings._sorter[options.group]++
-
-	if options.enableQuery?
-		options.enableQuery = JSON.stringify options.enableQuery
-
-	if options.i18nDefaultQuery?
-		options.i18nDefaultQuery = JSON.stringify options.i18nDefaultQuery
-
-	if process?.env?[_id]?
-		value = process.env[_id]
-		if value.toLowerCase() is "true"
-			value = true
-		else if value.toLowerCase() is "false"
-			value = false
-		options.processEnvValue = value
-		options.valueSource = 'processEnvValue'
-
-	else if Meteor.settings?[_id]?
-		value = Meteor.settings[_id]
-		options.meteorSettingsValue = value
-		options.valueSource = 'meteorSettingsValue'
-
-	if not options.i18nLabel?
-		options.i18nLabel = _id
-
-	# Default description i18n key will be the setting name + "_Description" (eg: LDAP_Enable -> LDAP_Enable_Description)
-	if not options.i18nDescription?
-		options.i18nDescription = "#{_id}_Description"
-
-	if blockedSettings[_id]?
-		options.blocked = true
-
-	if hiddenSettings[_id]?
-		options.hidden = true
-
-	if process?.env?['OVERWRITE_SETTING_' + _id]?
-		value = process.env['OVERWRITE_SETTING_' + _id]
-		if value.toLowerCase() is "true"
-			value = true
-		else if value.toLowerCase() is "false"
-			value = false
-		options.value = value
-		options.processEnvValue = value
-		options.valueSource = 'processEnvValue'
-
-	updateOperations =
-		$set: options
-		$setOnInsert:
-			createdAt: new Date
-
-	if options.editor?
-		updateOperations.$setOnInsert.editor = options.editor
-		delete options.editor
-
-	if not options.value?
-		if options.force is true
-			updateOperations.$set.value = options.packageValue
-		else
-			updateOperations.$setOnInsert.value = value
-
-	query = _.extend { _id: _id }, updateOperations.$set
-
-	if not options.section?
-		updateOperations.$unset = { section: 1 }
-		query.section = { $exists: false }
-
-	existantSetting = RocketChat.models.Settings.db.findOne(query)
-
-	if existantSetting?
-		if not existantSetting.editor? and updateOperations.$setOnInsert.editor?
-			updateOperations.$set.editor = updateOperations.$setOnInsert.editor
-			delete updateOperations.$setOnInsert.editor
-	else
-		updateOperations.$set.ts = new Date
-
-	return RocketChat.models.Settings.upsert { _id: _id }, updateOperations
-
-
-
-###
-# Add a setting group
-# @param {String} _id
-###
-RocketChat.settings.addGroup = (_id, options = {}, cb) ->
-	# console.log '[functions] RocketChat.settings.addGroup -> '.green, 'arguments:', arguments
-
-	if not _id
-		return false
-
-	if _.isFunction(options)
-		cb = options
-		options = {}
-
-	if not options.i18nLabel?
-		options.i18nLabel = _id
-
-	if not options.i18nDescription?
-		options.i18nDescription = "#{_id}_Description"
-
-	options.ts = new Date
-	options.blocked = false
-	options.hidden = false
-
-	if blockedSettings[_id]?
-		options.blocked = true
-
-	if hiddenSettings[_id]?
-		options.hidden = true
-
-	RocketChat.models.Settings.upsert { _id: _id },
-		$set: options
-		$setOnInsert:
-			type: 'group'
-			createdAt: new Date
-
-	if cb?
-		cb.call
-			add: (id, value, options = {}) ->
-				options.group = _id
-				RocketChat.settings.add id, value, options
-
-			section: (section, cb) ->
-				cb.call
-					add: (id, value, options = {}) ->
-						options.group = _id
-						options.section = section
-						RocketChat.settings.add id, value, options
-
-	return
-
-
-###
-# Remove a setting by id
-# @param {String} _id
-###
-RocketChat.settings.removeById = (_id) ->
-	# console.log '[functions] RocketChat.settings.add -> '.green, 'arguments:', arguments
-
-	if not _id
-		return false
-
-	return RocketChat.models.Settings.removeById _id
-
-
-###
-# Update a setting by id
-# @param {String} _id
-###
-RocketChat.settings.updateById = (_id, value, editor) ->
-	# console.log '[functions] RocketChat.settings.updateById -> '.green, 'arguments:', arguments
-
-	if not _id or not value?
-		return false
-
-	if editor?
-		return RocketChat.models.Settings.updateValueAndEditorById _id, value, editor
-
-	return RocketChat.models.Settings.updateValueById _id, value
-
-
-###
-# Update options of a setting by id
-# @param {String} _id
-###
-RocketChat.settings.updateOptionsById = (_id, options) ->
-	# console.log '[functions] RocketChat.settings.updateOptionsById -> '.green, 'arguments:', arguments
-
-	if not _id or not options?
-		return false
-
-	return RocketChat.models.Settings.updateOptionsById _id, options
-
-
-###
-# Update a setting by id
-# @param {String} _id
-###
-RocketChat.settings.clearById = (_id) ->
-	# console.log '[functions] RocketChat.settings.clearById -> '.green, 'arguments:', arguments
-
-	if not _id?
-		return false
-
-	return RocketChat.models.Settings.updateValueById _id, undefined
-
-
-###
-# Update a setting by id
-###
-RocketChat.settings.init = ->
-	RocketChat.settings.initialLoad = true
-	RocketChat.models.Settings.find().observe
-		added: (record) ->
-			Meteor.settings[record._id] = record.value
-			if record.env is true
-				process.env[record._id] = record.value
-			RocketChat.settings.load record._id, record.value, RocketChat.settings.initialLoad
-		changed: (record) ->
-			Meteor.settings[record._id] = record.value
-			if record.env is true
-				process.env[record._id] = record.value
-			RocketChat.settings.load record._id, record.value, RocketChat.settings.initialLoad
-		removed: (record) ->
-			delete Meteor.settings[record._id]
-			if record.env is true
-				delete process.env[record._id]
-			RocketChat.settings.load record._id, undefined, RocketChat.settings.initialLoad
-	RocketChat.settings.initialLoad = false
-
-	for fn in RocketChat.settings.afterInitialLoad
-		fn(Meteor.settings)
-
-
-RocketChat.settings.afterInitialLoad = []
-
-RocketChat.settings.onAfterInitialLoad = (fn) ->
-	RocketChat.settings.afterInitialLoad.push(fn)
-	if RocketChat.settings.initialLoad is false
-		fn(Meteor.settings)
diff --git a/packages/rocketchat-lib/server/functions/settings.js b/packages/rocketchat-lib/server/functions/settings.js
new file mode 100644
index 00000000000..4fa9a3f2b40
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/settings.js
@@ -0,0 +1,283 @@
+const blockedSettings = {};
+
+if (process.env.SETTINGS_BLOCKED) {
+	process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => blockedSettings[settingId] = 1);
+}
+
+const hiddenSettings = {};
+if (process.env.SETTINGS_HIDDEN) {
+	process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => hiddenSettings[settingId] = 1);
+}
+
+RocketChat.settings._sorter = {};
+
+
+/*
+* Add a setting
+* @param {String} _id
+* @param {Mixed} value
+* @param {Object} setting
+*/
+
+RocketChat.settings.add = function(_id, value, options = {}) {
+	if (options == null) {
+		options = {};
+	}
+	if (!_id || value == null) {
+		return false;
+	}
+	if (RocketChat.settings._sorter[options.group] == null) {
+		RocketChat.settings._sorter[options.group] = 0;
+	}
+	options.packageValue = value;
+	options.valueSource = 'packageValue';
+	options.hidden = false;
+	options.blocked = options.blocked || false;
+	if (options.sorter == null) {
+		options.sorter = RocketChat.settings._sorter[options.group]++;
+	}
+	if (options.enableQuery != null) {
+		options.enableQuery = JSON.stringify(options.enableQuery);
+	}
+	if (options.i18nDefaultQuery != null) {
+		options.i18nDefaultQuery = JSON.stringify(options.i18nDefaultQuery);
+	}
+	if (typeof process !== 'undefined' && process.env && process.env._id) {
+		let value = process.env[_id];
+		if (value.toLowerCase() === 'true') {
+			value = true;
+		} else if (value.toLowerCase() === 'false') {
+			value = false;
+		}
+		options.processEnvValue = value;
+		options.valueSource = 'processEnvValue';
+	} else if (Meteor.settings && Meteor.settings) {
+		const value = Meteor.settings[_id];
+		options.meteorSettingsValue = value;
+		options.valueSource = 'meteorSettingsValue';
+	}
+	if (options.i18nLabel == null) {
+		options.i18nLabel = _id;
+	}
+	if (options.i18nDescription == null) {
+		options.i18nDescription = `${ _id }_Description`;
+	}
+	if (blockedSettings[_id] != null) {
+		options.blocked = true;
+	}
+	if (hiddenSettings[_id] != null) {
+		options.hidden = true;
+	}
+	if (typeof process !== 'undefined' && process.env && process.env[`OVERWRITE_SETTING_${ _id }`]) {
+		let value = process.env[`OVERWRITE_SETTING_${ _id }`];
+		if (value.toLowerCase() === 'true') {
+			value = true;
+		} else if (value.toLowerCase() === 'false') {
+			value = false;
+		}
+		options.value = value;
+		options.processEnvValue = value;
+		options.valueSource = 'processEnvValue';
+	}
+	const updateOperations = {
+		$set: options,
+		$setOnInsert: {
+			createdAt: new Date
+		}
+	};
+	if (options.editor != null) {
+		updateOperations.$setOnInsert.editor = options.editor;
+		delete options.editor;
+	}
+	if (options.value == null) {
+		if (options.force === true) {
+			updateOperations.$set.value = options.packageValue;
+		} else {
+			updateOperations.$setOnInsert.value = value;
+		}
+	}
+	const query = _.extend({
+		_id
+	}, updateOperations.$set);
+	if (options.section == null) {
+		updateOperations.$unset = {
+			section: 1
+		};
+		query.section = {
+			$exists: false
+		};
+	}
+	const existantSetting = RocketChat.models.Settings.db.findOne(query);
+	if (existantSetting != null) {
+		if (existantSetting.editor == null && updateOperations.$setOnInsert.editor != null) {
+			updateOperations.$set.editor = updateOperations.$setOnInsert.editor;
+			delete updateOperations.$setOnInsert.editor;
+		}
+	} else {
+		updateOperations.$set.ts = new Date;
+	}
+	return RocketChat.models.Settings.upsert({
+		_id
+	}, updateOperations);
+};
+
+
+/*
+* Add a setting group
+* @param {String} _id
+*/
+
+RocketChat.settings.addGroup = function(_id, options = {}, cb) {
+	if (!_id) {
+		return false;
+	}
+	if (_.isFunction(options)) {
+		cb = options;
+		options = {};
+	}
+	if (options.i18nLabel == null) {
+		options.i18nLabel = _id;
+	}
+	if (options.i18nDescription == null) {
+		options.i18nDescription = `${ _id }_Description`;
+	}
+	options.ts = new Date;
+	options.blocked = false;
+	options.hidden = false;
+	if (blockedSettings[_id] != null) {
+		options.blocked = true;
+	}
+	if (hiddenSettings[_id] != null) {
+		options.hidden = true;
+	}
+	RocketChat.models.Settings.upsert({
+		_id
+	}, {
+		$set: options,
+		$setOnInsert: {
+			type: 'group',
+			createdAt: new Date
+		}
+	});
+	if (cb != null) {
+		cb.call({
+			add(id, value, options) {
+				if (options == null) {
+					options = {};
+				}
+				options.group = _id;
+				return RocketChat.settings.add(id, value, options);
+			},
+			section(section, cb) {
+				return cb.call({
+					add(id, value, options) {
+						if (options == null) {
+							options = {};
+						}
+						options.group = _id;
+						options.section = section;
+						return RocketChat.settings.add(id, value, options);
+					}
+				});
+			}
+		});
+	}
+};
+
+
+/*
+* Remove a setting by id
+* @param {String} _id
+*/
+
+RocketChat.settings.removeById = function(_id) {
+	if (!_id) {
+		return false;
+	}
+	return RocketChat.models.Settings.removeById(_id);
+};
+
+
+/*
+* Update a setting by id
+* @param {String} _id
+*/
+
+RocketChat.settings.updateById = function(_id, value, editor) {
+	if (!_id || value == null) {
+		return false;
+	}
+	if (editor != null) {
+		return RocketChat.models.Settings.updateValueAndEditorById(_id, value, editor);
+	}
+	return RocketChat.models.Settings.updateValueById(_id, value);
+};
+
+
+/*
+* Update options of a setting by id
+* @param {String} _id
+*/
+
+RocketChat.settings.updateOptionsById = function(_id, options) {
+	if (!_id || options == null) {
+		return false;
+	}
+	return RocketChat.models.Settings.updateOptionsById(_id, options);
+};
+
+
+/*
+* Update a setting by id
+* @param {String} _id
+*/
+
+RocketChat.settings.clearById = function(_id) {
+	if (_id == null) {
+		return false;
+	}
+	return RocketChat.models.Settings.updateValueById(_id, undefined);
+};
+
+
+/*
+* Update a setting by id
+*/
+
+RocketChat.settings.init = function() {
+	RocketChat.settings.initialLoad = true;
+	RocketChat.models.Settings.find().observe({
+		added(record) {
+			Meteor.settings[record._id] = record.value;
+			if (record.env === true) {
+				process.env[record._id] = record.value;
+			}
+			return RocketChat.settings.load(record._id, record.value, RocketChat.settings.initialLoad);
+		},
+		changed(record) {
+			Meteor.settings[record._id] = record.value;
+			if (record.env === true) {
+				process.env[record._id] = record.value;
+			}
+			return RocketChat.settings.load(record._id, record.value, RocketChat.settings.initialLoad);
+		},
+		removed(record) {
+			delete Meteor.settings[record._id];
+			if (record.env === true) {
+				delete process.env[record._id];
+			}
+			return RocketChat.settings.load(record._id, undefined, RocketChat.settings.initialLoad);
+		}
+	});
+	RocketChat.settings.initialLoad = false;
+	RocketChat.settings.afterInitialLoad.forEach(fn => fn(Meteor.settings));
+};
+
+RocketChat.settings.afterInitialLoad = [];
+
+RocketChat.settings.onAfterInitialLoad = function(fn) {
+	RocketChat.settings.afterInitialLoad.push(fn);
+	if (RocketChat.settings.initialLoad === false) {
+		return fn(Meteor.settings);
+	}
+};
diff --git a/packages/rocketchat-lib/server/methods/sendMessage.coffee b/packages/rocketchat-lib/server/methods/sendMessage.coffee
deleted file mode 100644
index 95e8f71a66a..00000000000
--- a/packages/rocketchat-lib/server/methods/sendMessage.coffee
+++ /dev/null
@@ -1,64 +0,0 @@
-import moment from 'moment'
-
-Meteor.methods
-	sendMessage: (message) ->
-
-		check message, Object
-
-		if not Meteor.userId()
-			throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'sendMessage' })
-
-		if message.ts
-			tsDiff = Math.abs(moment(message.ts).diff())
-			if tsDiff > 60000
-				throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', { method: 'sendMessage', message_ts: message.ts, server_ts: new Date().getTime() })
-			else if tsDiff > 10000
-				message.ts = new Date()
-		else
-			message.ts = new Date()
-
-		if message.msg?.length > RocketChat.settings.get('Message_MaxAllowedSize')
-			throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', { method: 'sendMessage' })
-
-		user = RocketChat.models.Users.findOneById Meteor.userId(), fields: username: 1, name: 1
-
-		room = Meteor.call 'canAccessRoom', message.rid, user._id
-
-		if not room
-			return false
-
-		subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId());
-		if subscription and (subscription.blocked or subscription.blocker)
-			RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
-				_id: Random.id()
-				rid: room._id
-				ts: new Date
-				msg: TAPi18n.__('room_is_blocked', {}, user.language)
-			}
-			return false
-
-		if user.username in (room.muted or [])
-			RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
-				_id: Random.id()
-				rid: room._id
-				ts: new Date
-				msg: TAPi18n.__('You_have_been_muted', {}, user.language)
-			}
-			return false
-
-		message.alias = user.name if not message.alias? and RocketChat.settings.get 'Message_SetNameToAliasEnabled'
-		if Meteor.settings.public.sandstorm
-			message.sandstormSessionId = this.connection.sandstormSessionId()
-
-		RocketChat.metrics.messagesSent.inc() # This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
-		RocketChat.sendMessage user, message, room
-
-# Limit a user, who does not have the "bot" role, to sending 5 msgs/second
-DDPRateLimiter.addRule
-	type: 'method'
-	name: 'sendMessage'
-	userId: (userId) ->
-		user = RocketChat.models.Users.findOneById(userId)
-		return true if not user?.roles
-		return 'bot' not in user.roles
-, 5, 1000
diff --git a/packages/rocketchat-lib/server/methods/sendMessage.js b/packages/rocketchat-lib/server/methods/sendMessage.js
new file mode 100644
index 00000000000..2f6e41502fc
--- /dev/null
+++ b/packages/rocketchat-lib/server/methods/sendMessage.js
@@ -0,0 +1,81 @@
+import moment from 'moment';
+
+Meteor.methods({
+	sendMessage(message) {
+		check(message, Object);
+		if (!Meteor.userId()) {
+			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+				method: 'sendMessage'
+			});
+		}
+		if (message.ts) {
+			const tsDiff = Math.abs(moment(message.ts).diff());
+			if (tsDiff > 60000) {
+				throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', {
+					method: 'sendMessage',
+					message_ts: message.ts,
+					server_ts: new Date().getTime()
+				});
+			} else if (tsDiff > 10000) {
+				message.ts = new Date();
+			}
+		} else {
+			message.ts = new Date();
+		}
+		if (message.msg && message.msg.length > RocketChat.settings.get('Message_MaxAllowedSize')) {
+			throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', {
+				method: 'sendMessage'
+			});
+		}
+		const user = RocketChat.models.Users.findOneById(Meteor.userId(), {
+			fields: {
+				username: 1,
+				name: 1
+			}
+		});
+		const room = Meteor.call('canAccessRoom', message.rid, user._id);
+		if (!room) {
+			return false;
+		}
+		const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId());
+		if (subscription && subscription.blocked || subscription.blocker) {
+			RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
+				_id: Random.id(),
+				rid: room._id,
+				ts: new Date,
+				msg: TAPi18n.__('room_is_blocked', {}, user.language)
+			});
+			return false;
+		}
+
+		if ((room.muted||[]).includes(user.username)) {
+			RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
+				_id: Random.id(),
+				rid: room._id,
+				ts: new Date,
+				msg: TAPi18n.__('You_have_been_muted', {}, user.language)
+			});
+			return false;
+		}
+		if (message.alias == null && RocketChat.settings.get('Message_SetNameToAliasEnabled')) {
+			message.alias = user.name;
+		}
+		if (Meteor.settings['public'].sandstorm) {
+			message.sandstormSessionId = this.connection.sandstormSessionId();
+		}
+		RocketChat.metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
+		return RocketChat.sendMessage(user, message, room);
+	}
+});
+// Limit a user, who does not have the "bot" role, to sending 5 msgs/second
+DDPRateLimiter.addRule({
+	type: 'method',
+	name: 'sendMessage',
+	userId(userId) {
+		const user = RocketChat.models.Users.findOneById(userId);
+		if (user == null || !user.roles) {
+			return true;
+		}
+		return user.roles.includes('bot');
+	}
+}, 5, 1000);
diff --git a/packages/rocketchat-lib/server/models/Messages.js b/packages/rocketchat-lib/server/models/Messages.js
new file mode 100644
index 00000000000..dabf1893663
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Messages.js
@@ -0,0 +1,580 @@
+RocketChat.models.Messages = new class extends RocketChat.models._Base {
+	constructor() {
+		super('message');
+
+		this.tryEnsureIndex({ 'rid': 1, 'ts': 1 });
+		this.tryEnsureIndex({ 'ts': 1 });
+		this.tryEnsureIndex({ 'u._id': 1 });
+		this.tryEnsureIndex({ 'editedAt': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'editedBy._id': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'rid': 1, 't': 1, 'u._id': 1 });
+		this.tryEnsureIndex({ 'expireAt': 1 }, { expireAfterSeconds: 0 });
+		this.tryEnsureIndex({ 'msg': 'text' });
+		this.tryEnsureIndex({ 'file._id': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'mentions.username': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'pinned': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'snippeted': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'location': '2dsphere' });
+		this.tryEnsureIndex({ 'slackBotId': 1, 'slackTs': 1 }, { sparse: 1 });
+	}
+
+	// FIND
+	findByMention(username, options) {
+		const query =	{'mentions.username': username};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByMentionAndRoomId(username, rid, options) {
+		const query = {
+			_hidden: { $ne: true },
+			'mentions.username': username,
+			rid
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomId(roomId, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+
+			rid: roomId
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdNotContainingTypes(roomId, types, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+
+			rid: roomId
+		};
+
+		if (Match.test(types, [String]) && (types.length > 0)) {
+			query.t =
+			{$nin: types};
+		}
+
+		return this.find(query, options);
+	}
+
+	findInvisibleByRoomId(roomId, options) {
+		const query = {
+			_hidden: true,
+			rid: roomId
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdAfterTimestamp(roomId, timestamp, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$gt: timestamp
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBeforeTimestamp(roomId, timestamp, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$lt: timestamp
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBeforeTimestampInclusive(roomId, timestamp, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$lte: timestamp
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBetweenTimestamps(roomId, afterTimestamp, beforeTimestamp, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$gt: afterTimestamp,
+				$lt: beforeTimestamp
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$gte: afterTimestamp,
+				$lte: beforeTimestamp
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBeforeTimestampNotContainingTypes(roomId, timestamp, types, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$lt: timestamp
+			}
+		};
+
+		if (Match.test(types, [String]) && (types.length > 0)) {
+			query.t =
+			{$nin: types};
+		}
+
+		return this.find(query, options);
+	}
+
+	findVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, afterTimestamp, beforeTimestamp, types, options) {
+		const query = {
+			_hidden: {
+				$ne: true
+			},
+			rid: roomId,
+			ts: {
+				$gt: afterTimestamp,
+				$lt: beforeTimestamp
+			}
+		};
+
+		if (Match.test(types, [String]) && (types.length > 0)) {
+			query.t =
+			{$nin: types};
+		}
+
+		return this.find(query, options);
+	}
+
+	findVisibleCreatedOrEditedAfterTimestamp(timestamp, options) {
+		const query = {
+			_hidden: { $ne: true },
+			$or: [{
+				ts: {
+					$gt: timestamp
+				}
+			},
+			{
+				'editedAt': {
+					$gt: timestamp
+				}
+			}
+			]
+		};
+
+		return this.find(query, options);
+	}
+
+	findStarredByUserAtRoom(userId, roomId, options) {
+		const query = {
+			_hidden: { $ne: true },
+			'starred._id': userId,
+			rid: roomId
+		};
+
+		return this.find(query, options);
+	}
+
+	findPinnedByRoom(roomId, options) {
+		const query = {
+			t: { $ne: 'rm' },
+			_hidden: { $ne: true },
+			pinned: true,
+			rid: roomId
+		};
+
+		return this.find(query, options);
+	}
+
+	findSnippetedByRoom(roomId, options) {
+		const query = {
+			_hidden: { $ne: true },
+			snippeted: true,
+			rid: roomId
+		};
+
+		return this.find(query, options);
+	}
+
+	getLastTimestamp(options) {
+		if (options == null) { options = {}; }
+		const query = { ts: { $exists: 1 } };
+		options.sort = { ts: -1 };
+		options.limit = 1;
+		const [message] = this.find(query, options).fetch();
+		return message && message.ts;
+	}
+
+	findByRoomIdAndMessageIds(rid, messageIds, options) {
+		const query = {
+			rid,
+			_id: {
+				$in: messageIds
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findOneBySlackBotIdAndSlackTs(slackBotId, slackTs) {
+		const query = {
+			slackBotId,
+			slackTs
+		};
+
+		return this.findOne(query);
+	}
+
+	findOneBySlackTs(slackTs) {
+		const query =	{slackTs};
+
+		return this.findOne(query);
+	}
+
+	cloneAndSaveAsHistoryById(_id) {
+		const me = RocketChat.models.Users.findOneById(Meteor.userId());
+		const record = this.findOneById(_id);
+		record._hidden = true;
+		record.parent = record._id;
+		record.editedAt = new Date;
+		record.editedBy = {
+			_id: Meteor.userId(),
+			username: me.username
+		};
+		delete record._id;
+		return this.insert(record);
+	}
+
+	// UPDATE
+	setHiddenById(_id, hidden) {
+		if (hidden == null) { hidden = true; }
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				_hidden: hidden
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setAsDeletedByIdAndUser(_id, user) {
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				msg: '',
+				t: 'rm',
+				urls: [],
+				mentions: [],
+				attachments: [],
+				reactions: [],
+				editedAt: new Date(),
+				editedBy: {
+					_id: user._id,
+					username: user.username
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setPinnedByIdAndUserId(_id, pinnedBy, pinned, pinnedAt) {
+		if (pinned == null) { pinned = true; }
+		if (pinnedAt == null) { pinnedAt = 0; }
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				pinned,
+				pinnedAt: pinnedAt || new Date,
+				pinnedBy
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setSnippetedByIdAndUserId(message, snippetName, snippetedBy, snippeted, snippetedAt) {
+		if (snippeted == null) { snippeted = true; }
+		if (snippetedAt == null) { snippetedAt = 0; }
+		const query =	{_id: message._id};
+
+		const msg = `\`\`\`${ message.msg }\`\`\``;
+
+		const update = {
+			$set: {
+				msg,
+				snippeted,
+				snippetedAt: snippetedAt || new Date,
+				snippetedBy,
+				snippetName
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setUrlsById(_id, urls) {
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				urls
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateAllUsernamesByUserId(userId, username) {
+		const query =	{'u._id': userId};
+
+		const update = {
+			$set: {
+				'u.username': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	updateUsernameOfEditByUserId(userId, username) {
+		const query =	{'editedBy._id': userId};
+
+		const update = {
+			$set: {
+				'editedBy.username': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	updateUsernameAndMessageOfMentionByIdAndOldUsername(_id, oldUsername, newUsername, newMessage) {
+		const query = {
+			_id,
+			'mentions.username': oldUsername
+		};
+
+		const update = {
+			$set: {
+				'mentions.$.username': newUsername,
+				'msg': newMessage
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateUserStarById(_id, userId, starred) {
+		let update;
+		const query =	{_id};
+
+		if (starred) {
+			update = {
+				$addToSet: {
+					starred: { _id: userId }
+				}
+			};
+		} else {
+			update = {
+				$pull: {
+					starred: { _id: Meteor.userId() }
+				}
+			};
+		}
+
+		return this.update(query, update);
+	}
+
+	upgradeEtsToEditAt() {
+		const query =	{ets: { $exists: 1 }};
+
+		const update = {
+			$rename: {
+				'ets': 'editedAt'
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setMessageAttachments(_id, attachments) {
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				attachments
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setSlackBotIdAndSlackTs(_id, slackBotId, slackTs) {
+		const query =	{_id};
+
+		const update = {
+			$set: {
+				slackBotId,
+				slackTs
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+
+	// INSERT
+	createWithTypeRoomIdMessageAndUser(type, roomId, message, user, extraData) {
+		const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { sysMes: 1 }});
+		if ((room != null ? room.sysMes : undefined) === false) {
+			return;
+		}
+		const record = {
+			t: type,
+			rid: roomId,
+			ts: new Date,
+			msg: message,
+			u: {
+				_id: user._id,
+				username: user.username
+			},
+			groupable: false
+		};
+
+		_.extend(record, extraData);
+
+		record._id = this.insertOrUpsert(record);
+		RocketChat.models.Rooms.incMsgCountById(room._id, 1);
+		return record;
+	}
+
+	createUserJoinWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('uj', roomId, message, user, extraData);
+	}
+
+	createUserLeaveWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('ul', roomId, message, user, extraData);
+	}
+
+	createUserRemovedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('ru', roomId, message, user, extraData);
+	}
+
+	createUserAddedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('au', roomId, message, user, extraData);
+	}
+
+	createCommandWithRoomIdAndUser(command, roomId, user, extraData) {
+		return this.createWithTypeRoomIdMessageAndUser('command', roomId, command, user, extraData);
+	}
+
+	createUserMutedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('user-muted', roomId, message, user, extraData);
+	}
+
+	createUserUnmutedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('user-unmuted', roomId, message, user, extraData);
+	}
+
+	createNewModeratorWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('new-moderator', roomId, message, user, extraData);
+	}
+
+	createModeratorRemovedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('moderator-removed', roomId, message, user, extraData);
+	}
+
+	createNewOwnerWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('new-owner', roomId, message, user, extraData);
+	}
+
+	createOwnerRemovedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('owner-removed', roomId, message, user, extraData);
+	}
+
+	createSubscriptionRoleAddedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('subscription-role-added', roomId, message, user, extraData);
+	}
+
+	createSubscriptionRoleRemovedWithRoomIdAndUser(roomId, user, extraData) {
+		const message = user.username;
+		return this.createWithTypeRoomIdMessageAndUser('subscription-role-removed', roomId, message, user, extraData);
+	}
+
+	// REMOVE
+	removeById(_id) {
+		const query =	{_id};
+
+		return this.remove(query);
+	}
+
+	removeByRoomId(roomId) {
+		const query =	{rid: roomId};
+
+		return this.remove(query);
+	}
+
+	removeByUserId(userId) {
+		const query =	{'u._id': userId};
+
+		return this.remove(query);
+	}
+
+	getMessageByFileId(fileID) {
+		return this.findOne({ 'file._id': fileID });
+	}
+};
diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js
new file mode 100644
index 00000000000..763155e1c83
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Rooms.js
@@ -0,0 +1,760 @@
+class ModelRooms extends RocketChat.models._Base {
+	constructor() {
+		super(...arguments);
+
+		this.tryEnsureIndex({ 'name': 1 }, { unique: 1, sparse: 1 });
+		this.tryEnsureIndex({ 'default': 1 });
+		this.tryEnsureIndex({ 'usernames': 1 });
+		this.tryEnsureIndex({ 't': 1 });
+		this.tryEnsureIndex({ 'u._id': 1 });
+
+		this.cache.ignoreUpdatedFields.push('msgs', 'lm');
+		this.cache.ensureIndex(['t', 'name'], 'unique');
+		this.cache.options = {fields: {usernames: 0}};
+	}
+
+	findOneByIdOrName(_idOrName, options) {
+		const query = {
+			$or: [{
+				_id: _idOrName
+			}, {
+				name: _idOrName
+			}]
+		};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByImportId(_id, options) {
+		const query = {importIds: _id};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByName(name, options) {
+		const query = {name};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByNameAndType(name, type, options) {
+		const query = {
+			name,
+			t: type
+		};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByIdContainingUsername(_id, username, options) {
+		const query = {
+			_id,
+			usernames: username
+		};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByNameAndTypeNotContainingUsername(name, type, username, options) {
+		const query = {
+			name,
+			t: type,
+			usernames: {
+				$ne: username
+			}
+		};
+
+		return this.findOne(query, options);
+	}
+
+
+	// FIND
+
+	findById(roomId, options) {
+		return this.find({ _id: roomId }, options);
+	}
+
+	findByIds(roomIds, options) {
+		return this.find({ _id: {$in: [].concat(roomIds)} }, options);
+	}
+
+	findByType(type, options) {
+		const query = {t: type};
+
+		return this.find(query, options);
+	}
+
+	findByTypes(types, options) {
+		const query = {
+			t: {
+				$in: types
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByUserId(userId, options) {
+		const query = {'u._id': userId};
+
+		return this.find(query, options);
+	}
+
+	findBySubscriptionUserId(userId, options) {
+		let data;
+		if (this.useCache) {
+			data = RocketChat.models.Subscriptions.findByUserId(userId).fetch();
+			data = data.map(function(item) {
+				if (item._room) {
+					return item._room;
+				}
+				console.log('Empty Room for Subscription', item);
+				return {};
+			});
+			return this.arrayToCursor(this.processQueryOptionsOnResult(data, options));
+		}
+
+		data = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch();
+		data = data.map(item => item.rid);
+
+		const query = {
+			_id: {
+				$in: data
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findBySubscriptionUserIdUpdatedAfter(userId, _updatedAt, options) {
+		if (this.useCache) {
+			let data = RocketChat.models.Subscriptions.findByUserId(userId).fetch();
+			data = data.map(function(item) {
+				if (item._room) {
+					return item._room;
+				}
+				console.log('Empty Room for Subscription', item);
+				return {};
+			});
+			data = data.filter(item => item._updatedAt > _updatedAt);
+			return this.arrayToCursor(this.processQueryOptionsOnResult(data, options));
+		}
+
+		let ids = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch();
+		ids = ids.map(item => item.rid);
+
+		const query = {
+			_id: {
+				$in: ids
+			},
+			_updatedAt: {
+				$gt: _updatedAt
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByNameContaining(name, options) {
+		const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i');
+
+		const query = {
+			$or: [
+				{name: nameRegex},
+				{
+					t: 'd',
+					usernames: nameRegex
+				}
+			]
+		};
+
+		return this.find(query, options);
+	}
+
+	findByNameContainingTypesWithUsername(name, types, options) {
+		const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i');
+
+		const $or = [];
+		for (const type of Array.from(types)) {
+			const obj = {name: nameRegex, t: type.type};
+			if (type.username != null) {
+				obj.usernames = type.username;
+			}
+			if (type.ids != null) {
+				obj._id = {$in: type.ids};
+			}
+			$or.push(obj);
+		}
+
+		const query = {$or};
+
+		return this.find(query, options);
+	}
+
+	findContainingTypesWithUsername(types, options) {
+
+		const $or = [];
+		for (const type of Array.from(types)) {
+			const obj = {t: type.type};
+			if (type.username != null) {
+				obj.usernames = type.username;
+			}
+			if (type.ids != null) {
+				obj._id = {$in: type.ids};
+			}
+			$or.push(obj);
+		}
+
+		const query = {$or};
+
+		return this.find(query, options);
+	}
+
+	findByNameContainingAndTypes(name, types, options) {
+		const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i');
+
+		const query = {
+			t: {
+				$in: types
+			},
+			$or: [
+				{name: nameRegex},
+				{
+					t: 'd',
+					usernames: nameRegex
+				}
+			]
+		};
+
+		return this.find(query, options);
+	}
+
+	findByNameAndTypeNotContainingUsername(name, type, username, options) {
+		const query = {
+			t: type,
+			name,
+			usernames: {
+				$ne: username
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByNameStartingAndTypes(name, types, options) {
+		const nameRegex = new RegExp(`^${ s.trim(s.escapeRegExp(name)) }`, 'i');
+
+		const query = {
+			t: {
+				$in: types
+			},
+			$or: [
+				{name: nameRegex},
+				{
+					t: 'd',
+					usernames: nameRegex
+				}
+			]
+		};
+
+		return this.find(query, options);
+	}
+
+	findByDefaultAndTypes(defaultValue, types, options) {
+		const query = {
+			default: defaultValue,
+			t: {
+				$in: types
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeContainingUsername(type, username, options) {
+		const query = {
+			t: type,
+			usernames: username
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeContainingUsernames(type, username, options) {
+		const query = {
+			t: type,
+			usernames: { $all: [].concat(username) }
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypesAndNotUserIdContainingUsername(types, userId, username, options) {
+		const query = {
+			t: {
+				$in: types
+			},
+			uid: {
+				$ne: userId
+			},
+			usernames: username
+		};
+
+		return this.find(query, options);
+	}
+
+	findByContainingUsername(username, options) {
+		const query = {usernames: username};
+
+		return this.find(query, options);
+	}
+
+	findByTypeAndName(type, name, options) {
+		if (this.useCache) {
+			return this.cache.findByIndex('t,name', [type, name], options);
+		}
+
+		const query = {
+			name,
+			t: type
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeAndNameContainingUsername(type, name, username, options) {
+		const query = {
+			name,
+			t: type,
+			usernames: username
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeAndArchivationState(type, archivationstate, options) {
+		const query = {t: type};
+
+		if (archivationstate) {
+			query.archived = true;
+		} else {
+			query.archived = { $ne: true };
+		}
+
+		return this.find(query, options);
+	}
+
+	// UPDATE
+	addImportIds(_id, importIds) {
+		importIds = [].concat(importIds);
+		const query = {_id};
+
+		const update = {
+			$addToSet: {
+				importIds: {
+					$each: importIds
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	archiveById(_id) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				archived: true
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	unarchiveById(_id) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				archived: false
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	addUsernameById(_id, username, muted) {
+		const query = {_id};
+
+		const update = {
+			$addToSet: {
+				usernames: username
+			}
+		};
+
+		if (muted) {
+			update.$addToSet.muted = username;
+		}
+
+		return this.update(query, update);
+	}
+
+	addUsernamesById(_id, usernames) {
+		const query = {_id};
+
+		const update = {
+			$addToSet: {
+				usernames: {
+					$each: usernames
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	addUsernameByName(name, username) {
+		const query = {name};
+
+		const update = {
+			$addToSet: {
+				usernames: username
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	removeUsernameById(_id, username) {
+		const query = {_id};
+
+		const update = {
+			$pull: {
+				usernames: username
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	removeUsernamesById(_id, usernames) {
+		const query = {_id};
+
+		const update = {
+			$pull: {
+				usernames: {
+					$in: usernames
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	removeUsernameFromAll(username) {
+		const query = {usernames: username};
+
+		const update = {
+			$pull: {
+				usernames: username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	removeUsernameByName(name, username) {
+		const query = {name};
+
+		const update = {
+			$pull: {
+				usernames: username
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setNameById(_id, name) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				name
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	incMsgCountById(_id, inc) {
+		if (inc == null) { inc = 1; }
+		const query = {_id};
+
+		const update = {
+			$inc: {
+				msgs: inc
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	incMsgCountAndSetLastMessageTimestampById(_id, inc, lastMessageTimestamp) {
+		if (inc == null) { inc = 1; }
+		const query = {_id};
+
+		const update = {
+			$set: {
+				lm: lastMessageTimestamp
+			},
+			$inc: {
+				msgs: inc
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	replaceUsername(previousUsername, username) {
+		const query = {usernames: previousUsername};
+
+		const update = {
+			$set: {
+				'usernames.$': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	replaceMutedUsername(previousUsername, username) {
+		const query = {muted: previousUsername};
+
+		const update = {
+			$set: {
+				'muted.$': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	replaceUsernameOfUserByUserId(userId, username) {
+		const query = {'u._id': userId};
+
+		const update = {
+			$set: {
+				'u.username': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setJoinCodeById(_id, joinCode) {
+		let update;
+		const query = {_id};
+
+		if ((joinCode != null ? joinCode.trim() : undefined) !== '') {
+			update = {
+				$set: {
+					joinCodeRequired: true,
+					joinCode
+				}
+			};
+		} else {
+			update = {
+				$set: {
+					joinCodeRequired: false
+				},
+				$unset: {
+					joinCode: 1
+				}
+			};
+		}
+
+		return this.update(query, update);
+	}
+
+	setUserById(_id, user) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				u: {
+					_id: user._id,
+					username: user.username
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setTypeById(_id, type) {
+		const query = {_id};
+		const update = {
+			$set: {
+				t: type
+			}
+		};
+		if (type === 'p') {
+			update.$unset = {default: ''};
+		}
+
+		return this.update(query, update);
+	}
+
+	setTopicById(_id, topic) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				topic
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setAnnouncementById(_id, announcement) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				announcement
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	muteUsernameByRoomId(_id, username) {
+		const query = {_id};
+
+		const update = {
+			$addToSet: {
+				muted: username
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	unmuteUsernameByRoomId(_id, username) {
+		const query = {_id};
+
+		const update = {
+			$pull: {
+				muted: username
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	saveDefaultById(_id, defaultValue) {
+		const query = {_id};
+
+		const update = {
+			$set: {
+				default: defaultValue === 'true'
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setTopicAndTagsById(_id, topic, tags) {
+		const setData = {};
+		const unsetData = {};
+
+		if (topic != null) {
+			if (!_.isEmpty(s.trim(topic))) {
+				setData.topic = s.trim(topic);
+			} else {
+				unsetData.topic = 1;
+			}
+		}
+
+		if (tags != null) {
+			if (!_.isEmpty(s.trim(tags))) {
+				setData.tags = s.trim(tags).split(',').map(tag => s.trim(tag));
+			} else {
+				unsetData.tags = 1;
+			}
+		}
+
+		const update = {};
+
+		if (!_.isEmpty(setData)) {
+			update.$set = setData;
+		}
+
+		if (!_.isEmpty(unsetData)) {
+			update.$unset = unsetData;
+		}
+
+		if (_.isEmpty(update)) {
+			return;
+		}
+
+		return this.update({ _id }, update);
+	}
+
+	// INSERT
+	createWithTypeNameUserAndUsernames(type, name, user, usernames, extraData) {
+		const room = {
+			name,
+			t: type,
+			usernames,
+			msgs: 0,
+			u: {
+				_id: user._id,
+				username: user.username
+			}
+		};
+
+		_.extend(room, extraData);
+
+		room._id = this.insert(room);
+		return room;
+	}
+
+	createWithIdTypeAndName(_id, type, name, extraData) {
+		const room = {
+			_id,
+			ts: new Date(),
+			t: type,
+			name,
+			usernames: [],
+			msgs: 0
+		};
+
+		_.extend(room, extraData);
+
+		this.insert(room);
+		return room;
+	}
+
+
+	// REMOVE
+	removeById(_id) {
+		const query = {_id};
+
+		return this.remove(query);
+	}
+
+	removeByTypeContainingUsername(type, username) {
+		const query = {
+			t: type,
+			usernames: username
+		};
+
+		return this.remove(query);
+	}
+}
+
+RocketChat.models.Rooms = new ModelRooms('room', true);
diff --git a/packages/rocketchat-lib/server/models/Settings.js b/packages/rocketchat-lib/server/models/Settings.js
new file mode 100644
index 00000000000..7da29bee37f
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Settings.js
@@ -0,0 +1,178 @@
+class ModelSettings extends RocketChat.models._Base {
+	constructor() {
+		super(...arguments);
+
+		this.tryEnsureIndex({ 'blocked': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'hidden': 1 }, { sparse: 1 });
+	}
+
+	// FIND
+	findById(_id) {
+		const query = {_id};
+
+		return this.find(query);
+	}
+
+	findOneNotHiddenById(_id) {
+		const query = {
+			_id,
+			hidden: { $ne: true }
+		};
+
+		return this.findOne(query);
+	}
+
+	findByIds(_id = []) {
+		_id = [].concat(_id);
+
+		const query = {
+			_id: {
+				$in: _id
+			}
+		};
+
+		return this.find(query);
+	}
+
+	findByRole(role, options) {
+		const query = {role};
+
+		return this.find(query, options);
+	}
+
+	findPublic(options) {
+		const query = {public: true};
+
+		return this.find(query, options);
+	}
+
+	findNotHiddenPublic(ids = []) {
+		const filter = {
+			hidden: { $ne: true },
+			public: true
+		};
+
+		if (ids.length > 0) {
+			filter._id =
+				{$in: ids};
+		}
+
+		return this.find(filter, { fields: {_id: 1, value: 1} });
+	}
+
+	findNotHiddenPublicUpdatedAfter(updatedAt) {
+		const filter = {
+			hidden: { $ne: true },
+			public: true,
+			_updatedAt: {
+				$gt: updatedAt
+			}
+		};
+
+		return this.find(filter, { fields: {_id: 1, value: 1} });
+	}
+
+	findNotHiddenPrivate() {
+		return this.find({
+			hidden: { $ne: true },
+			public: { $ne: true }
+		});
+	}
+
+	findNotHidden(options) {
+		return this.find({ hidden: { $ne: true } }, options);
+	}
+
+	findNotHiddenUpdatedAfter(updatedAt) {
+		return this.find({
+			hidden: { $ne: true },
+			_updatedAt: {
+				$gt: updatedAt
+			}
+		});
+	}
+
+	// UPDATE
+	updateValueById(_id, value) {
+		const query = {
+			blocked: { $ne: true },
+			value: { $ne: value },
+			_id
+		};
+
+		const update = {
+			$set: {
+				value
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateValueAndEditorById(_id, value, editor) {
+		const query = {
+			blocked: { $ne: true },
+			value: { $ne: value },
+			_id
+		};
+
+		const update = {
+			$set: {
+				value,
+				editor
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateValueNotHiddenById(_id, value) {
+		const query = {
+			_id,
+			hidden: { $ne: true },
+			blocked: { $ne: true }
+		};
+
+		const update = {
+			$set: {
+				value
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateOptionsById(_id, options) {
+		const query = {
+			blocked: { $ne: true },
+			_id
+		};
+
+		const update = {$set: options};
+
+		return this.update(query, update);
+	}
+
+	// INSERT
+	createWithIdAndValue(_id, value) {
+		const record = {
+			_id,
+			value,
+			_createdAt: new Date
+		};
+
+		return this.insert(record);
+	}
+
+	// REMOVE
+	removeById(_id) {
+		const query = {
+			blocked: { $ne: true },
+			_id
+		};
+
+		return this.remove(query);
+	}
+}
+
+RocketChat.models.Settings = new ModelSettings('settings', true);
diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js
new file mode 100644
index 00000000000..67986040e02
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Subscriptions.js
@@ -0,0 +1,570 @@
+class ModelSubscriptions extends RocketChat.models._Base {
+	constructor() {
+		super(...arguments);
+
+		this.tryEnsureIndex({ 'rid': 1, 'u._id': 1 }, { unique: 1 });
+		this.tryEnsureIndex({ 'rid': 1, 'alert': 1, 'u._id': 1 });
+		this.tryEnsureIndex({ 'rid': 1, 'roles': 1 });
+		this.tryEnsureIndex({ 'u._id': 1, 'name': 1, 't': 1 });
+		this.tryEnsureIndex({ 'u._id': 1, 'name': 1, 't': 1, 'code': 1 }, { unique: 1 });
+		this.tryEnsureIndex({ 'open': 1 });
+		this.tryEnsureIndex({ 'alert': 1 });
+		this.tryEnsureIndex({ 'unread': 1 });
+		this.tryEnsureIndex({ 'ts': 1 });
+		this.tryEnsureIndex({ 'ls': 1 });
+		this.tryEnsureIndex({ 'audioNotification': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'desktopNotifications': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'mobilePushNotifications': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'emailNotifications': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'autoTranslate': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'autoTranslateLanguage': 1 }, { sparse: 1 });
+
+		this.cache.ensureIndex('rid', 'array');
+		this.cache.ensureIndex('u._id', 'array');
+		this.cache.ensureIndex('name', 'array');
+		this.cache.ensureIndex(['rid', 'u._id'], 'unique');
+		this.cache.ensureIndex(['name', 'u._id'], 'unique');
+	}
+
+
+	// FIND ONE
+	findOneByRoomIdAndUserId(roomId, userId) {
+		if (this.useCache) {
+			return this.cache.findByIndex('rid,u._id', [roomId, userId]).fetch();
+		}
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		return this.findOne(query);
+	}
+
+	findOneByRoomNameAndUserId(roomName, userId) {
+		if (this.useCache) {
+			return this.cache.findByIndex('name,u._id', [roomName, userId]).fetch();
+		}
+		const query = {
+			name: roomName,
+			'u._id': userId
+		};
+
+		return this.findOne(query);
+	}
+
+	// FIND
+	findByUserId(userId, options) {
+		if (this.useCache) {
+			return this.cache.findByIndex('u._id', userId, options);
+		}
+
+		const query =
+			{'u._id': userId};
+
+		return this.find(query, options);
+	}
+
+	findByUserIdUpdatedAfter(userId, updatedAt, options) {
+		const query = {
+			'u._id': userId,
+			_updatedAt: {
+				$gt: updatedAt
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	// FIND
+	findByRoomIdAndRoles(roomId, roles, options) {
+		roles = [].concat(roles);
+		const query = {
+			'rid': roomId,
+			'roles': { $in: roles }
+		};
+
+		return this.find(query, options);
+	}
+
+	findByType(types, options) {
+		const query = {
+			t: {
+				$in: types
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeAndUserId(type, userId, options) {
+		const query = {
+			t: type,
+			'u._id': userId
+		};
+
+		return this.find(query, options);
+	}
+
+	findByTypeNameAndUserId(type, name, userId, options) {
+		const query = {
+			t: type,
+			name,
+			'u._id': userId
+		};
+
+		return this.find(query, options);
+	}
+
+	findByRoomId(roomId, options) {
+		if (this.useCache) {
+			return this.cache.findByIndex('rid', roomId, options);
+		}
+
+		const query =
+			{rid: roomId};
+
+		return this.find(query, options);
+	}
+
+	findByRoomIdAndNotUserId(roomId, userId, options) {
+		const query = {
+			rid: roomId,
+			'u._id': {
+				$ne: userId
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	getLastSeen(options) {
+		if (options == null) { options = {}; }
+		const query = { ls: { $exists: 1 } };
+		options.sort = { ls: -1 };
+		options.limit = 1;
+		const [subscription] = this.find(query, options).fetch();
+		return subscription && subscription.ls;
+	}
+
+	findByRoomIdAndUserIds(roomId, userIds) {
+		const query = {
+			rid: roomId,
+			'u._id': {
+				$in: userIds
+			}
+		};
+
+		return this.find(query);
+	}
+
+	// UPDATE
+	archiveByRoomId(roomId) {
+		const query =
+			{rid: roomId};
+
+		const update = {
+			$set: {
+				alert: false,
+				open: false,
+				archived: true
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	unarchiveByRoomId(roomId) {
+		const query =
+			{rid: roomId};
+
+		const update = {
+			$set: {
+				alert: false,
+				open: true,
+				archived: false
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	hideByRoomIdAndUserId(roomId, userId) {
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		const update = {
+			$set: {
+				alert: false,
+				open: false
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	openByRoomIdAndUserId(roomId, userId) {
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		const update = {
+			$set: {
+				open: true
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setAsReadByRoomIdAndUserId(roomId, userId) {
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		const update = {
+			$set: {
+				open: true,
+				alert: false,
+				unread: 0,
+				ls: new Date
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setAsUnreadByRoomIdAndUserId(roomId, userId, firstMessageUnreadTimestamp) {
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		const update = {
+			$set: {
+				open: true,
+				alert: true,
+				ls: firstMessageUnreadTimestamp
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setFavoriteByRoomIdAndUserId(roomId, userId, favorite) {
+		if (favorite == null) { favorite = true; }
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		const update = {
+			$set: {
+				f: favorite
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateNameAndAlertByRoomId(roomId, name) {
+		const query =
+			{rid: roomId};
+
+		const update = {
+			$set: {
+				name,
+				alert: true
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	updateNameByRoomId(roomId, name) {
+		const query =
+			{rid: roomId};
+
+		const update = {
+			$set: {
+				name
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setUserUsernameByUserId(userId, username) {
+		const query =
+			{'u._id': userId};
+
+		const update = {
+			$set: {
+				'u.username': username
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setNameForDirectRoomsWithOldName(oldName, name) {
+		const query = {
+			name: oldName,
+			t: 'd'
+		};
+
+		const update = {
+			$set: {
+				name
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	incUnreadOfDirectForRoomIdExcludingUserId(roomId, userId, inc) {
+		if (inc == null) { inc = 1; }
+		const query = {
+			rid: roomId,
+			t: 'd',
+			'u._id': {
+				$ne: userId
+			}
+		};
+
+		const update = {
+			$set: {
+				alert: true,
+				open: true
+			},
+			$inc: {
+				unread: inc
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	incUnreadForRoomIdExcludingUserId(roomId, userId, inc) {
+		if (inc == null) { inc = 1; }
+		const query = {
+			rid: roomId,
+			'u._id': {
+				$ne: userId
+			}
+		};
+
+		const update = {
+			$set: {
+				alert: true,
+				open: true
+			},
+			$inc: {
+				unread: inc
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	incUnreadForRoomIdAndUserIds(roomId, userIds, inc) {
+		if (inc == null) { inc = 1; }
+		const query = {
+			rid: roomId,
+			'u._id': {
+				$in: userIds
+			}
+		};
+
+		const update = {
+			$set: {
+				alert: true,
+				open: true
+			},
+			$inc: {
+				unread: inc
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setAlertForRoomIdExcludingUserId(roomId, userId) {
+		const query = {
+			rid: roomId,
+			'u._id': {
+				$ne: userId
+			},
+			$or: [
+				{ alert: { $ne: true } },
+				{ open: { $ne: true } }
+			]
+		};
+
+		const update = {
+			$set: {
+				alert: true,
+				open: true
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	setBlockedByRoomId(rid, blocked, blocker) {
+		const query = {
+			rid,
+			'u._id': blocked
+		};
+
+		const update = {
+			$set: {
+				blocked: true
+			}
+		};
+
+		const query2 = {
+			rid,
+			'u._id': blocker
+		};
+
+		const update2 = {
+			$set: {
+				blocker: true
+			}
+		};
+
+		return this.update(query, update) && this.update(query2, update2);
+	}
+
+	unsetBlockedByRoomId(rid, blocked, blocker) {
+		const query = {
+			rid,
+			'u._id': blocked
+		};
+
+		const update = {
+			$unset: {
+				blocked: 1
+			}
+		};
+
+		const query2 = {
+			rid,
+			'u._id': blocker
+		};
+
+		const update2 = {
+			$unset: {
+				blocker: 1
+			}
+		};
+
+		return this.update(query, update) && this.update(query2, update2);
+	}
+
+	updateTypeByRoomId(roomId, type) {
+		const query =
+			{rid: roomId};
+
+		const update = {
+			$set: {
+				t: type
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	addRoleById(_id, role) {
+		const query =
+			{_id};
+
+		const update = {
+			$addToSet: {
+				roles: role
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	removeRoleById(_id, role) {
+		const query =
+			{_id};
+
+		const update = {
+			$pull: {
+				roles: role
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setArchivedByUsername(username, archived) {
+		const query = {
+			t: 'd',
+			name: username
+		};
+
+		const update = {
+			$set: {
+				archived
+			}
+		};
+
+		return this.update(query, update, { multi: true });
+	}
+
+	// INSERT
+	createWithRoomAndUser(room, user, extraData) {
+		const subscription = {
+			open: false,
+			alert: false,
+			unread: 0,
+			ts: room.ts,
+			rid: room._id,
+			name: room.name,
+			t: room.t,
+			u: {
+				_id: user._id,
+				username: user.username
+			}
+		};
+
+		_.extend(subscription, extraData);
+
+		return this.insert(subscription);
+	}
+
+
+	// REMOVE
+	removeByUserId(userId) {
+		const query =
+			{'u._id': userId};
+
+		return this.remove(query);
+	}
+
+	removeByRoomId(roomId) {
+		const query =
+			{rid: roomId};
+
+		return this.remove(query);
+	}
+
+	removeByRoomIdAndUserId(roomId, userId) {
+		const query = {
+			rid: roomId,
+			'u._id': userId
+		};
+
+		return this.remove(query);
+	}
+}
+
+RocketChat.models.Subscriptions = new ModelSubscriptions('subscription', true);
diff --git a/packages/rocketchat-lib/server/models/Uploads.js b/packages/rocketchat-lib/server/models/Uploads.js
new file mode 100644
index 00000000000..252d48fb878
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Uploads.js
@@ -0,0 +1,91 @@
+RocketChat.models.Uploads = new class extends RocketChat.models._Base {
+	constructor() {
+		super('uploads');
+
+		this.tryEnsureIndex({ 'rid': 1 });
+		this.tryEnsureIndex({ 'uploadedAt': 1 });
+	}
+
+	findNotHiddenFilesOfRoom(roomId, limit) {
+		const fileQuery = {
+			rid: roomId,
+			complete: true,
+			uploading: false,
+			_hidden: {
+				$ne: true
+			}
+		};
+
+		const fileOptions = {
+			limit,
+			sort: {
+				uploadedAt: -1
+			},
+			fields: {
+				_id: 1,
+				userId: 1,
+				rid: 1,
+				name: 1,
+				description: 1,
+				type: 1,
+				url: 1,
+				uploadedAt: 1
+			}
+		};
+
+		return this.find(fileQuery, fileOptions);
+	}
+
+	insertFileInit(roomId, userId, store, file, extra) {
+		const fileData = {
+			rid: roomId,
+			userId,
+			store,
+			complete: false,
+			uploading: true,
+			progress: 0,
+			extension: s.strRightBack(file.name, '.'),
+			uploadedAt: new Date()
+		};
+
+		_.extend(fileData, file, extra);
+
+		if ((this.model.direct != null ? this.model.direct.insert : undefined) != null) {
+			file = this.model.direct.insert(fileData);
+		} else {
+			file = this.insert(fileData);
+		}
+
+		return file;
+	}
+
+	updateFileComplete(fileId, userId, file) {
+		let result;
+		if (!fileId) {
+			return;
+		}
+
+		const filter = {
+			_id: fileId,
+			userId
+		};
+
+		const update = {
+			$set: {
+				complete: true,
+				uploading: false,
+				progress: 1
+			}
+		};
+
+		update.$set = _.extend(file, update.$set);
+
+		if ((this.model.direct != null ? this.model.direct.insert : undefined) != null) {
+			result = this.model.direct.update(filter, update);
+		} else {
+			result = this.update(filter, update);
+		}
+
+		return result;
+	}
+};
diff --git a/packages/rocketchat-lib/server/models/Users.js b/packages/rocketchat-lib/server/models/Users.js
new file mode 100644
index 00000000000..941416475c6
--- /dev/null
+++ b/packages/rocketchat-lib/server/models/Users.js
@@ -0,0 +1,538 @@
+class ModelUsers extends RocketChat.models._Base {
+	constructor() {
+		super(...arguments);
+
+		this.tryEnsureIndex({ 'roles': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'name': 1 });
+		this.tryEnsureIndex({ 'lastLogin': 1 });
+		this.tryEnsureIndex({ 'status': 1 });
+		this.tryEnsureIndex({ 'active': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'statusConnection': 1 }, { sparse: 1 });
+		this.tryEnsureIndex({ 'type': 1 });
+
+		this.cache.ensureIndex('username', 'unique');
+	}
+
+	findOneByImportId(_id, options) {
+		return this.findOne({ importIds: _id }, options);
+	}
+
+	findOneByUsername(username, options) {
+		const query =	{username};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByEmailAddress(emailAddress, options) {
+		const query =	{'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i')};
+
+		return this.findOne(query, options);
+	}
+
+	findOneAdmin(admin, options) {
+		const query =	{admin};
+
+		return this.findOne(query, options);
+	}
+
+	findOneByIdAndLoginToken(_id, token, options) {
+		const query = {
+			_id,
+			'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
+		};
+
+		return this.findOne(query, options);
+	}
+
+
+	// FIND
+	findById(userId) {
+		const query =	{_id: userId};
+
+		return this.find(query);
+	}
+
+	findUsersNotOffline(options) {
+		const query = {
+			username: {
+				$exists: 1
+			},
+			status: {
+				$in: ['online', 'away', 'busy']
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+
+	findByUsername(username, options) {
+		const query =	{username};
+
+		return this.find(query, options);
+	}
+
+	findUsersByUsernamesWithHighlights(usernames, options) {
+		if (this.useCache) {
+			const result = {
+				fetch() {
+					return RocketChat.models.Users.getDynamicView('highlights').data().filter(record => usernames.indexOf(record.username) > -1);
+				},
+				count() {
+					return result.fetch().length;
+				},
+				forEach(fn) {
+					return result.fetch().forEach(fn);
+				}
+			};
+			return result;
+		}
+
+		const query = {
+			username: { $in: usernames },
+			'settings.preferences.highlights.0': {
+				$exists: true
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findActiveByUsernameOrNameRegexWithExceptions(searchTerm, exceptions, options) {
+		if (exceptions == null) { exceptions = []; }
+		if (options == null) { options = {}; }
+		if (!_.isArray(exceptions)) {
+			exceptions = [ exceptions ];
+		}
+
+		const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i');
+		const query = {
+			$or: [{
+				username: termRegex
+			}, {
+				name: termRegex
+			}],
+			active: true,
+			type: {
+				$in: ['user', 'bot']
+			},
+			$and: [{
+				username: {
+					$exists: true
+				}
+			}, {
+				username: {
+					$nin: exceptions
+				}
+			}]
+		};
+
+		return this.find(query, options);
+	}
+
+	findByActiveUsersExcept(searchTerm, exceptions, options) {
+		if (exceptions == null) { exceptions = []; }
+		if (options == null) { options = {}; }
+		if (!_.isArray(exceptions)) {
+			exceptions = [ exceptions ];
+		}
+
+		const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i');
+		const query = {
+			$and: [
+				{
+					active: true,
+					$or: [
+						{
+							username: termRegex
+						},
+						{
+							name: termRegex
+						}
+					]
+				},
+				{
+					username: { $exists: true, $nin: exceptions }
+				}
+			]
+		};
+
+		return this.find(query, options);
+	}
+
+	findUsersByNameOrUsername(nameOrUsername, options) {
+		const query = {
+			username: {
+				$exists: 1
+			},
+
+			$or: [
+				{name: nameOrUsername},
+				{username: nameOrUsername}
+			],
+
+			type: {
+				$in: ['user']
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress, options) {
+		const query = {
+			$or: [
+				{name: usernameNameOrEmailAddress},
+				{username: usernameNameOrEmailAddress},
+				{'emails.address': usernameNameOrEmailAddress}
+			],
+			type: {
+				$in: ['user', 'bot']
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	findLDAPUsers(options) {
+		const query =	{ldap: true};
+
+		return this.find(query, options);
+	}
+
+	findCrowdUsers(options) {
+		const query =	{crowd: true};
+
+		return this.find(query, options);
+	}
+
+	getLastLogin(options) {
+		if (options == null) { options = {}; }
+		const query = { lastLogin: { $exists: 1 } };
+		options.sort = { lastLogin: -1 };
+		options.limit = 1;
+		const [user] = this.find(query, options).fetch();
+		return user && user.lastLogin;
+	}
+
+	findUsersByUsernames(usernames, options) {
+		const query = {
+			username: {
+				$in: usernames
+			}
+		};
+
+		return this.find(query, options);
+	}
+
+	// UPDATE
+	addImportIds(_id, importIds) {
+		importIds = [].concat(importIds);
+
+		const query =	{_id};
+
+		const update = {
+			$addToSet: {
+				importIds: {
+					$each: importIds
+				}
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	updateLastLoginById(_id) {
+		const update = {
+			$set: {
+				lastLogin: new Date
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setServiceId(_id, serviceName, serviceId) {
+		const update =
+		{$set: {}};
+
+		const serviceIdKey = `services.${ serviceName }.id`;
+		update.$set[serviceIdKey] = serviceId;
+
+		return this.update(_id, update);
+	}
+
+	setUsername(_id, username) {
+		const update =
+		{$set: {username}};
+
+		return this.update(_id, update);
+	}
+
+	setEmail(_id, email) {
+		const update = {
+			$set: {
+				emails: [{
+					address: email,
+					verified: false
+				}
+				]
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setEmailVerified(_id, email) {
+		const query = {
+			_id,
+			emails: {
+				$elemMatch: {
+					address: email,
+					verified: false
+				}
+			}
+		};
+
+		const update = {
+			$set: {
+				'emails.$.verified': true
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	setName(_id, name) {
+		const update = {
+			$set: {
+				name
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setCustomFields(_id, fields) {
+		const values = {};
+		Object.keys(fields).reduce(key => {
+			const value = fields[key];
+			values[`customFields.${ key }`] = value;
+		});
+
+		const update = {$set: values};
+
+		return this.update(_id, update);
+	}
+
+	setAvatarOrigin(_id, origin) {
+		const update = {
+			$set: {
+				avatarOrigin: origin
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	unsetAvatarOrigin(_id) {
+		const update = {
+			$unset: {
+				avatarOrigin: 1
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setUserActive(_id, active) {
+		if (active == null) { active = true; }
+		const update = {
+			$set: {
+				active
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setAllUsersActive(active) {
+		const update = {
+			$set: {
+				active
+			}
+		};
+
+		return this.update({}, update, { multi: true });
+	}
+
+	unsetLoginTokens(_id) {
+		const update = {
+			$set: {
+				'services.resume.loginTokens' : []
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	unsetRequirePasswordChange(_id) {
+		const update = {
+			$unset: {
+				'requirePasswordChange' : true,
+				'requirePasswordChangeReason' : true
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	resetPasswordAndSetRequirePasswordChange(_id, requirePasswordChange, requirePasswordChangeReason) {
+		const update = {
+			$unset: {
+				'services.password': 1
+			},
+			$set: {
+				requirePasswordChange,
+				requirePasswordChangeReason
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setLanguage(_id, language) {
+		const update = {
+			$set: {
+				language
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setProfile(_id, profile) {
+		const update = {
+			$set: {
+				'settings.profile': profile
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setPreferences(_id, preferences) {
+		const update = {
+			$set: {
+				'settings.preferences': preferences
+			}
+		};
+
+		return this.update(_id, update);
+	}
+
+	setUtcOffset(_id, utcOffset) {
+		const query = {
+			_id,
+			utcOffset: {
+				$ne: utcOffset
+			}
+		};
+
+		const update = {
+			$set: {
+				utcOffset
+			}
+		};
+
+		return this.update(query, update);
+	}
+
+	saveUserById(_id, data) {
+		const setData = {};
+		const unsetData = {};
+
+		if (data.name != null) {
+			if (!_.isEmpty(s.trim(data.name))) {
+				setData.name = s.trim(data.name);
+			} else {
+				unsetData.name = 1;
+			}
+		}
+
+		if (data.email != null) {
+			if (!_.isEmpty(s.trim(data.email))) {
+				setData.emails = [{address: s.trim(data.email)}];
+			} else {
+				unsetData.emails = 1;
+			}
+		}
+
+		if (data.phone != null) {
+			if (!_.isEmpty(s.trim(data.phone))) {
+				setData.phone = [{phoneNumber: s.trim(data.phone)}];
+			} else {
+				unsetData.phone = 1;
+			}
+		}
+
+		const update = {};
+
+		if (!_.isEmpty(setData)) {
+			update.$set = setData;
+		}
+
+		if (!_.isEmpty(unsetData)) {
+			update.$unset = unsetData;
+		}
+
+		if (_.isEmpty(update)) {
+			return true;
+		}
+
+		return this.update({ _id }, update);
+	}
+
+// INSERT
+	create(data) {
+		const user = {
+			createdAt: new Date,
+			avatarOrigin: 'none'
+		};
+
+		_.extend(user, data);
+
+		return this.insert(user);
+	}
+
+
+// REMOVE
+	removeById(_id) {
+		return this.remove(_id);
+	}
+
+/*
+Find users to send a message by email if:
+- he is not online
+- has a verified email
+- has not disabled email notifications
+- `active` is equal to true (false means they were deactivated and can't login)
+*/
+	getUsersToSendOfflineEmail(usersIds) {
+		const query = {
+			_id: {
+				$in: usersIds
+			},
+			active: true,
+			status: 'offline',
+			statusConnection: {
+				$ne: 'online'
+			},
+			'emails.verified': true
+		};
+
+		return this.find(query, { fields: { name: 1, username: 1, emails: 1, 'settings.preferences.emailNotificationMode': 1 } });
+	}
+}
+
+RocketChat.models.Users = new ModelUsers(Meteor.users, true);
-- 
GitLab