diff --git a/client/lib/tapi18n.coffee b/client/lib/tapi18n.coffee
index 1cf705e3952..b93b8f8193a 100644
--- a/client/lib/tapi18n.coffee
+++ b/client/lib/tapi18n.coffee
@@ -13,3 +13,6 @@
@isRtl = (language) ->
# https://en.wikipedia.org/wiki/Right-to-left#cite_note-2
return language?.split('-').shift().toLowerCase() in ['ar', 'dv', 'fa', 'he', 'ku', 'ps', 'sd', 'ug', 'ur', 'yi']
+
+UI.registerHelper '_t', (key) ->
+ return TAPi18next.t key
\ No newline at end of file
diff --git a/client/stylesheets/base.less b/client/stylesheets/base.less
index 0d1830fc5a3..c44cd969208 100644
--- a/client/stylesheets/base.less
+++ b/client/stylesheets/base.less
@@ -2375,15 +2375,87 @@ a.github-fork {
cursor: pointer;
}
&:hover:not(.system) .edit-message {
- display: inline-block;
+ display: block;
}
.delete-message {
display: none;
cursor: pointer;
}
&:hover:not(.system) .delete-message {
+ display: block;
+ }
+ .message-cog-container {
+ position: relative;
display: inline-block;
+ .message-cog {
+ visibility: hidden;
+ cursor: pointer;
+ }
+ }
+ &:hover:not(.system) .message-cog {
+ visibility: visible;
}
+
+ @keyframes dropdown-in {
+ 0% {
+ display: none;
+ opacity: 0;
+ }
+
+ 1% {
+ display: block;
+ opacity: 0;
+ transform: scale(0);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ .message-dropdown {
+ position: absolute;
+ top: -5px;
+ left: -2px;
+ z-index: 1000;
+ display: none;
+ background-color: #fff;
+ border: 1px solid #eee;
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 1px 1px 4px #B3B3B3;
+ transition: transform .15s ease-in-out, opacity .15s ease-in-out;
+ animation: dropdown-in .15s ease-in-out;
+
+ ul {
+ display: flex;
+ display: -webkit-flex;
+ padding: 0px;
+ font-size: 14px;
+
+ li {
+ display: block;
+ padding: 0px 8px;
+ font-weight: 400;
+ line-height: 26px;
+ cursor: pointer;
+ color: #666;
+ &:first-child {
+ padding-left: 6px;
+ background-color: #f8f8f8;
+ border-right: 1px solid #eee;
+ }
+ &:last-child {
+ padding-right: 13px;
+ }
+ &:hover {
+ background-color: #eee;
+ }
+ }
+ }
+ }
+
.user {
display: inline-block;
font-weight: 600;
@@ -2437,6 +2509,12 @@ a.github-fork {
float: left;
}
}
+
+ // .message-dropdown {
+ // top: 100%;
+ // left: 0;
+ // }
+
&:hover {
.time {
display: inline-block;
diff --git a/client/views/app/message.coffee b/client/views/app/message.coffee
index bdd7fb52b13..75764bda858 100644
--- a/client/views/app/message.coffee
+++ b/client/views/app/message.coffee
@@ -1,5 +1,7 @@
Template.message.helpers
-
+ actions: ->
+ return RocketChat.MessageAction.getButtons(this)
+
own: ->
return 'own' if this.u?._id is Meteor.userId()
diff --git a/client/views/app/message.html b/client/views/app/message.html
index 70e01485246..2ff13ac47cd 100644
--- a/client/views/app/message.html
+++ b/client/views/app/message.html
@@ -10,14 +10,21 @@
{{#if private}}
{{_ "Only_you_can_see_this_message"}}
{{/if}}
-
- {{#if canEdit}}
-
- {{/if}}
- {{#if canDelete}}
-
+ {{#if actions.length}}
+
+
+
+
+
+ {{#each actions}}
+
+ {{/each}}
+
+
+
{{/if}}
+
{{{body}}}
diff --git a/client/views/app/room.coffee b/client/views/app/room.coffee
index d71bf3f5407..0c6b3efa500 100644
--- a/client/views/app/room.coffee
+++ b/client/views/app/room.coffee
@@ -405,12 +405,13 @@ Template.room.events
'click .see-all': (e, instance) ->
instance.showUsersOffline.set(!instance.showUsersOffline.get())
- "click .edit-message": (e) ->
- Template.instance().chatMessages.edit(e.currentTarget.parentNode.parentNode)
- input = Template.instance().find('.input-message')
- Meteor.setTimeout ->
- input.focus()
- , 200
+ 'click .message-cog': (e) ->
+ message_id = $(e.currentTarget).closest('.message').attr('id')
+ $('.message-dropdown:visible').hide()
+ $("\##{message_id} .message-dropdown").show()
+
+ 'click .message-dropdown-close': ->
+ $('.message-dropdown:visible').hide()
"click .editing-commands-cancel > a": (e) ->
Template.instance().chatMessages.clearEditing()
@@ -431,30 +432,6 @@ Template.room.events
'click .image-to-download': (event) ->
ChatMessage.update {_id: this._arguments[1]._id, 'urls.url': $(event.currentTarget).data('url')}, {$set: {'urls.$.downloadImages': true}}
- 'click .delete-message': (event) ->
- message = @_arguments[1]
- msg = event.currentTarget.parentNode.parentNode
- instance = Template.instance()
- return if msg.classList.contains("system")
- swal {
- title: t('Are_you_sure')
- text: t('You_will_not_be_able_to_recover')
- type: 'warning'
- showCancelButton: true
- confirmButtonColor: '#DD6B55'
- confirmButtonText: t('Yes_delete_it')
- cancelButtonText: t('Cancel')
- closeOnConfirm: false
- html: false
- }, ->
- swal
- title: t('Deleted')
- text: t('Your_entry_has_been_deleted')
- type: 'success'
- timer: 1000
- showConfirmButton: false
-
- instance.chatMessages.deleteMsg(message)
'click .pin-message': (event) ->
message = @_arguments[1]
instance = Template.instance()
@@ -569,6 +546,12 @@ Template.room.onCreated ->
@autorun ->
self.subscribe 'fullUserData', Session.get('showUserInfo'), 1
+ for button in RocketChat.MessageAction.getButtons()
+ if _.isFunction button.action
+ evt = {}
+ evt["click .#{button.id}"] = button.action
+ Template.room.events evt
+
Template.room.onDestroyed ->
RocketChat.TabBar.resetButtons()
diff --git a/packages/rocketchat-ldap/package.js b/packages/rocketchat-ldap/package.js
index 8333f584058..0d510d5fdff 100644
--- a/packages/rocketchat-ldap/package.js
+++ b/packages/rocketchat-ldap/package.js
@@ -12,11 +12,11 @@ Npm.depends({
// Loads all i18n.json files into tapi18nFiles
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
-tapi18nFiles = fs.readdirSync('packages/rocketchat-ldap/i18n').forEach(function(filename) {
+tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-ldap/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-ldap/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
-});
+}));
Package.onUse(function(api) {
api.versionsFrom('1.0.3.1');
@@ -35,7 +35,6 @@ Package.onUse(function(api) {
// Common
// TAP
api.addFiles('package-tap.i18n');
- api.addFiles(tapi18nFiles);
// Client
api.addFiles('ldap_client.js', 'client');
@@ -43,6 +42,7 @@ Package.onUse(function(api) {
api.addFiles('ldap_server.js', 'server');
api.addFiles('config_server.coffee', 'server');
+ api.addFiles(tapi18nFiles);
api.export('LDAP', 'server');
api.export('LDAP_DEFAULTS', 'server');
diff --git a/packages/rocketchat-lib/client/MessageAction.coffee b/packages/rocketchat-lib/client/MessageAction.coffee
new file mode 100644
index 00000000000..d43de43796a
--- /dev/null
+++ b/packages/rocketchat-lib/client/MessageAction.coffee
@@ -0,0 +1,104 @@
+RocketChat.MessageAction = new class
+ buttons = new ReactiveVar {}
+
+ ###
+ config expects the following keys (only id is mandatory):
+ id (mandatory)
+ icon: string
+ i18nLabel: string
+ action: function(event, instance)
+ validation: function(message)
+ order: integer
+ ###
+ addButton = (config) ->
+ unless config?.id
+ throw new Meteor.Error "MessageAction-addButton-error", "Button id was not informed."
+
+ Tracker.nonreactive ->
+ btns = buttons.get()
+ btns[config.id] = config
+ buttons.set btns
+
+ removeButton = (id) ->
+ Tracker.nonreactive ->
+ btns = buttons.get()
+ delete btns[id]
+ buttons.set btns
+
+ updateButton = (id, config) ->
+ Tracker.nonreactive ->
+ btns = buttons.get()
+ if btns[id]
+ btns[id] = _.extend btns[id], config
+ buttons.set btns
+
+ getButtons = (message) ->
+ allButtons = _.toArray buttons.get()
+ if message
+ allowedButtons = _.compact _.map allButtons, (button) ->
+ unless button.validation?
+ return true
+ if button.validation(message)
+ return button
+ else
+ allowedButtons = allButtons
+
+ return _.sortBy allowedButtons, 'order'
+
+ resetButtons = ->
+ buttons.set {}
+
+ addButton: addButton
+ removeButton: removeButton
+ updateButton: updateButton
+ getButtons: getButtons
+ resetButtons: resetButtons
+
+Meteor.startup ->
+ RocketChat.MessageAction.addButton
+ id: 'edit-message'
+ icon: 'icon-pencil'
+ i18nLabel: 'rocketchat-lib:Edit'
+ action: (event, instance) ->
+ message = $(event.currentTarget).closest('.message')[0]
+ instance.chatMessages.edit(message)
+ $("\##{message.id} .message-dropdown").hide()
+ input = instance.find('.input-message')
+ Meteor.setTimeout ->
+ input.focus()
+ , 200
+ validation: (message) ->
+ return RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid ) or RocketChat.settings.get('Message_AllowEditing') and message.u?._id is Meteor.userId()
+ order: 1
+
+ RocketChat.MessageAction.addButton
+ id: 'delete-message'
+ icon: 'icon-trash-1'
+ i18nLabel: 'rocketchat-lib:Delete'
+ action: (event, instance) ->
+ message = @_arguments[1]
+ msg = $(event.currentTarget).closest('.message')[0]
+ $("\##{msg.id} .message-dropdown").hide()
+ return if msg.classList.contains("system")
+ swal {
+ title: t('Are_you_sure')
+ text: t('You_will_not_be_able_to_recover')
+ type: 'warning'
+ showCancelButton: true
+ confirmButtonColor: '#DD6B55'
+ confirmButtonText: t('Yes_delete_it')
+ cancelButtonText: t('Cancel')
+ closeOnConfirm: false
+ html: false
+ }, ->
+ swal
+ title: t('Deleted')
+ text: t('Your_entry_has_been_deleted')
+ type: 'success'
+ timer: 1000
+ showConfirmButton: false
+
+ instance.chatMessages.deleteMsg(message)
+ validation: (message) ->
+ return RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid ) or RocketChat.settings.get('Message_AllowDeleting') and message.u?._id is Meteor.userId()
+ order: 2
\ No newline at end of file
diff --git a/packages/rocketchat-lib/i18n/en.i18n.json b/packages/rocketchat-lib/i18n/en.i18n.json
new file mode 100644
index 00000000000..9c76e8e402a
--- /dev/null
+++ b/packages/rocketchat-lib/i18n/en.i18n.json
@@ -0,0 +1,4 @@
+{
+ "Edit": "Edit",
+ "Delete": "Delete"
+}
\ No newline at end of file
diff --git a/packages/rocketchat-lib/i18n/pt.i18n.json b/packages/rocketchat-lib/i18n/pt.i18n.json
new file mode 100644
index 00000000000..a5227168f1f
--- /dev/null
+++ b/packages/rocketchat-lib/i18n/pt.i18n.json
@@ -0,0 +1,4 @@
+{
+ "Edit": "Editar",
+ "Delete": "Excluir"
+}
\ No newline at end of file
diff --git a/packages/rocketchat-lib/package-tap.i18n b/packages/rocketchat-lib/package-tap.i18n
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js
index 3c3d6334a6a..e3d524219e2 100644
--- a/packages/rocketchat-lib/package.js
+++ b/packages/rocketchat-lib/package.js
@@ -15,6 +15,19 @@ Package.onUse(function(api) {
api.use('underscore');
api.use('underscorestring:underscore.string');
+ // TAPi18n
+ api.use('templating', 'client');
+ var _ = Npm.require('underscore');
+ var fs = Npm.require('fs');
+ tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-lib/i18n'), function(filename) {
+ if (fs.statSync('packages/rocketchat-lib/i18n/' + filename).size > 16) {
+ return 'i18n/' + filename;
+ }
+ }));
+ api.use(["tap:i18n@1.5.1"], ["client", "server"]);
+ api.imply('tap:i18n');
+ api.addFiles("package-tap.i18n", ["client", "server"]);
+
// COMMON
api.addFiles('lib/core.coffee');
api.addFiles('lib/callbacks.coffee');
@@ -23,15 +36,14 @@ Package.onUse(function(api) {
api.addFiles('settings/lib/settings.coffee');
api.addFiles('settings/lib/rocketchat.coffee');
-
// CLIENT
api.addFiles('client/Notifications.coffee', 'client');
api.addFiles('client/TabBar.coffee', 'client');
+ api.addFiles('client/MessageAction.coffee', 'client');
api.addFiles('settings/client/startup.coffee', 'client');
api.addFiles('settings/client/rocketchat.coffee', 'client');
-
// SERVER
api.addFiles('server/functions/checkUsernameAvailability.coffee', 'server');
api.addFiles('server/functions/setUsername.coffee', 'server');
@@ -46,6 +58,7 @@ Package.onUse(function(api) {
api.addFiles('server/Notifications.coffee', 'server');
+ // Settings
api.addFiles('settings/server/methods.coffee', 'server');
api.addFiles('settings/server/publication.coffee', 'server');
api.addFiles('settings/server/startup.coffee', 'server');
@@ -59,6 +72,8 @@ Package.onUse(function(api) {
api.addFiles('server/models/Subscriptions.coffee', 'server');
api.addFiles('server/models/Rooms.coffee', 'server');
+ // TAPi18n -- needs to be added last
+ api.addFiles(tapi18nFiles, ["client", "server"]);
// EXPORT
api.export('RocketChat');
diff --git a/packages/rocketchat-webrtc/webrtc.js b/packages/rocketchat-webrtc/webrtc.js
index dfc03dace1b..c9188e1ffb4 100644
--- a/packages/rocketchat-webrtc/webrtc.js
+++ b/packages/rocketchat-webrtc/webrtc.js
@@ -3,6 +3,10 @@ webrtc = {
pc: undefined,
to: undefined,
room: undefined,
+ activeMediastream: undefined,
+ remoteDataSDP: undefined,
+ mode: undefined,
+ lastSeenTimestamp: new Date(),
debug: false,
config: {
iceServers: [
@@ -14,17 +18,28 @@ webrtc = {
data.to = webrtc.to;
data.room = webrtc.room;
data.from = Meteor.user().username;
+ data.mod = (webrtc.mode ? webrtc.mode : 0);
RocketChat.Notifications.notifyUser(data.to, 'webrtc', data);
},
stop: function(sendEvent) {
+ if (webrtc.activeMediastream) {
+ webrtc.activeMediastream = undefined;
+ }
+
if (webrtc.pc) {
if (webrtc.pc.signalingState != 'closed') {
webrtc.pc.close();
- }
- if (sendEvent != false) {
- RocketChat.Notifications.notifyUser(webrtc.to, 'webrtc', {to: webrtc.to, room: webrtc.room, from: Meteor.userId(), close: true});
+ webrtc.pc = undefined;
+ webrtc.mode = 0;
}
}
+
+
+ this.onRemoteUrl();
+ this.onSelfUrl();
+ if (sendEvent != false) {
+ RocketChat.Notifications.notifyUser(webrtc.to, 'webrtc', {to: webrtc.to, room: webrtc.room, from: Meteor.userId(), close: true});
+ }
},
log: function() {
if (webrtc.debug === true) {
@@ -39,6 +54,18 @@ function onError() {
console.log(arguments);
}
+webrtc.activateLocalStream = function() {
+ var media ={ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}}} ;
+
+ // get the local stream, show it in the local video element and send it
+ navigator.getUserMedia(media, function (stream) {
+ webrtc.log('getUserMedia got stream');
+ webrtc.onSelfUrl(URL.createObjectURL(stream));
+ webrtc.activeMediastream = stream;
+
+ }, function(e) { webrtc.log('getUserMedia failed during activateLocalStream ' + e); });
+}
+
// run start(true) to initiate a call
webrtc.start = function (isCaller, fromUsername) {
webrtc.pc = new RTCPeerConnection(webrtc.config);
@@ -62,7 +89,7 @@ webrtc.start = function (isCaller, fromUsername) {
// once remote stream arrives, show it in the remote video element
webrtc.pc.onaddstream = function (evt) {
- webrtc.log('onaddstream', arguments)
+ webrtc.log('onaddstream', arguments);
webrtc.onRemoteUrl(URL.createObjectURL(evt.stream));
};
@@ -70,23 +97,53 @@ webrtc.start = function (isCaller, fromUsername) {
webrtc.log('oniceconnectionstatechange', arguments)
var srcElement = evt.srcElement || evt.target;
if (srcElement.iceConnectionState == 'disconnected' || srcElement.iceConnectionState == 'closed') {
- webrtc.pc.getLocalStreams().forEach(function(stream) {
- stream.stop();
- webrtc.onSelfUrl();
- });
- webrtc.pc.getRemoteStreams().forEach(function(stream) {
- if (stream.stop) {
+ if (webrtc.pc) {
+ webrtc.pc.getLocalStreams().forEach(function(stream) {
stream.stop();
- }
- webrtc.onRemoteUrl();
- });
- webrtc.pc = undefined;
+ webrtc.onSelfUrl();
+ });
+ webrtc.pc.getRemoteStreams().forEach(function(stream) {
+ if (stream.stop) {
+ stream.stop();
+ }
+ webrtc.onRemoteUrl();
+ });
+ webrtc.pc = undefined;
+ webrtc.mode = 0;
+ }
+
}
}
- var getUserMedia = function() {
+
+ var gotDescription = function(desc) {
+ webrtc.pc.setLocalDescription(desc, function() {}, onError);
+ webrtc.send({ "sdp": desc.toJSON(), cid: webrtc.cid });
+
+ }
+
+ var CreateMonitoringOffer = function() {
+
+ webrtc.pc.createOffer(gotDescription, onError, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });
+
+ }
+
+ var AutoConnectStream = function() {
+ webrtc.pc.addStream(webrtc.activeMediastream);
+ webrtc.pc.setRemoteDescription(new RTCSessionDescription(webrtc.remoteDataSDP));
+ webrtc.pc.createAnswer(gotDescription, onError);
+
+ }
+ var LocalGetUserMedia = function() {
+
+
+
+ var media ={ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}}} ;
+
+
// get the local stream, show it in the local video element and send it
- navigator.getUserMedia({ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}} }, function (stream) {
+ navigator.getUserMedia(media, function (stream) {
+ webrtc.log('getUserMedia got stream');
webrtc.onSelfUrl(URL.createObjectURL(stream));
webrtc.pc.addStream(stream);
@@ -97,36 +154,49 @@ webrtc.start = function (isCaller, fromUsername) {
webrtc.pc.createAnswer(gotDescription, onError);
}
- function gotDescription(desc) {
- webrtc.pc.setLocalDescription(desc, function() {}, onError);
- webrtc.send({ "sdp": desc.toJSON(), cid: webrtc.cid });
- }
- }, function() {});
+ }, function(e) { webrtc.log('getUserMedia failed' + e); });
+
}
if (isCaller) {
- getUserMedia();
- } else {
- swal({
- title: "Video call from "+fromUsername,
- text: "Do you want to accept?",
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: "Yes",
- cancelButtonText: "No"
- }, function(isConfirm){
- if (isConfirm) {
- getUserMedia();
- } else {
- webrtc.stop();
+ webrtc.log('isCaller LocalGetUserMedia');
+ if (webrtc.mode) {
+ if (webrtc.mode === 2) {
+ CreateMonitoringOffer();
+ } else { // node === 1
+
+ LocalGetUserMedia();
}
- });
+ } else {
+ // no mode
+ LocalGetUserMedia();
+ }
+
+ } else {
+ if (!webrtc.activeMediastream) {
+ swal({
+ title: "Video call from "+fromUsername,
+ text: "Do you want to accept?",
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes",
+ cancelButtonText: "No"
+ }, function(isConfirm){
+ if (isConfirm) {
+ LocalGetUserMedia();
+ } else {
+ webrtc.stop();
+ }
+ });
+ } else {
+ AutoConnectStream();
+ }
}
}
RocketChat.Notifications.onUser('webrtc', function(data) {
- webrtc.log('stream.on', Meteor.userId(), data)
+ webrtc.log('processIncomingRtcMessage()', Meteor.userId(), data)
if (!webrtc.to) {
webrtc.to = data.room.replace(Meteor.userId(), '');
}
@@ -135,15 +205,38 @@ RocketChat.Notifications.onUser('webrtc', function(data) {
webrtc.room = data.room;
}
- if (data.close == true) {
+
+ // do not stop local video if in monitoring mode
+ if (data.close == true) {
+
+ if (webrtc.activeMediastream) {
+ if (webrtc.pc) {
+ webrtc.pc.getRemoteStreams().forEach(function(stream) {
+ if (!stream.stop) {
+ stream.stop();
+ }
+ });
+ webrtc.pc = undefined;
+ webrtc.mode = 0;
+ }
+
+ } else {
+
webrtc.stop(false);
- return
+
+ }
+ return
}
- if (!webrtc.pc)
+
+ if (!webrtc.pc) {
+ if ((webrtc.activeMediastream) && (data.sdp != undefined)){
+ webrtc.remoteDataSDP = data.sdp;
+ }
webrtc.start(false, data.from);
+ }
- if (data.sdp) {
+ if (data.sdp != undefined) {
webrtc.pc.setRemoteDescription(new RTCSessionDescription(data.sdp));
} else {
if( ["closed", "failed", "disconnected", "completed"].indexOf(webrtc.pc.iceConnectionState) === -1) {
diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee
index bf6b440764b..c359823ebc4 100644
--- a/server/lib/accounts.coffee
+++ b/server/lib/accounts.coffee
@@ -24,9 +24,6 @@ Accounts.onCreateUser (options, user) ->
user.status = 'offline'
user.active = not RocketChat.settings.get 'Accounts_ManuallyApproveNewUsers'
- # when inserting first user give them admin privileges otherwise make a regular user
- roleName = if RocketChat.models.Users.findOne() then 'user' else 'admin'
-
if not user?.name? or user.name is ''
if options.profile?.name?
user.name = options.profile?.name
@@ -45,14 +42,20 @@ Accounts.onCreateUser (options, user) ->
verified: true
]
- Meteor.defer ->
- # need to defer role assignment because underlying alanning:roles requires user
- # to exist in users collection
- RocketChat.authz.addUsersToRoles( user._id, roleName)
- RocketChat.callbacks.run 'afterCreateUser', options, user
-
return user
+# Wrap insertUserDoc to allow executing code after Accounts.insertUserDoc is run
+Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc) ->
+ options = arguments[1]
+ user = arguments[2]
+ _id = insertUserDoc(options, user)
+
+ # when inserting first user give them admin privileges otherwise make a regular user
+ roleName = if RocketChat.models.Users.findOne() then 'user' else 'admin'
+
+ RocketChat.authz.addUsersToRoles(_id, roleName)
+ RocketChat.callbacks.run 'afterCreateUser', options, user
+ return _id
Accounts.validateLoginAttempt (login) ->
login = RocketChat.callbacks.run 'beforeValidateLogin', login