From fa4b479e9256e3267baf54d472e05b37eb0145a3 Mon Sep 17 00:00:00 2001 From: Bradley Hilton Date: Thu, 8 Dec 2016 10:07:42 -0600 Subject: [PATCH] Finish moving over existing users rest api to the v1/users --- packages/rocketchat-api/server/routes.coffee | 149 ------------ packages/rocketchat-api/server/v1/users.js | 218 ++++++++++++++++++ .../server/functions/getFullUserData.js | 50 ++++ .../server/methods/getFullUserData.js | 11 + 4 files changed, 279 insertions(+), 149 deletions(-) create mode 100644 packages/rocketchat-api/server/v1/users.js create mode 100644 packages/rocketchat-lib/server/functions/getFullUserData.js create mode 100644 packages/rocketchat-lib/server/methods/getFullUserData.js diff --git a/packages/rocketchat-api/server/routes.coffee b/packages/rocketchat-api/server/routes.coffee index 6aa8a12b746..b4132825bb4 100644 --- a/packages/rocketchat-api/server/routes.coffee +++ b/packages/rocketchat-api/server/routes.coffee @@ -1,7 +1,6 @@ RocketChat.API.v1.addRoute 'info', authRequired: false, get: -> RocketChat.Info - RocketChat.API.v1.addRoute 'me', authRequired: true, get: -> return _.pick @user, [ @@ -16,7 +15,6 @@ RocketChat.API.v1.addRoute 'me', authRequired: true, 'language' ] - # Send Channel Message RocketChat.API.v1.addRoute 'chat.messageExamples', authRequired: true, get: -> @@ -50,7 +48,6 @@ RocketChat.API.v1.addRoute 'chat.messageExamples', authRequired: true, trigger_word: 'Sample' ] - # Send Channel Message RocketChat.API.v1.addRoute 'chat.postMessage', authRequired: true, post: -> @@ -66,149 +63,3 @@ RocketChat.API.v1.addRoute 'chat.postMessage', authRequired: true, message: messageReturn.message catch e return RocketChat.API.v1.failure e.error - - - - - - - - - - - - - - -# Create user -RocketChat.API.v1.addRoute 'users.create', authRequired: true, - post: -> - try - check @bodyParams, - email: String - name: String - password: String - username: String - role: Match.Maybe(String) - joinDefaultChannels: Match.Maybe(Boolean) - requirePasswordChange: Match.Maybe(Boolean) - sendWelcomeEmail: Match.Maybe(Boolean) - verified: Match.Maybe(Boolean) - customFields: Match.Maybe(Object) - - # check username availability first (to not create an user without a username) - try - nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$' - catch - nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$' - - if not nameValidation.test @bodyParams.username - return RocketChat.API.v1.failure 'Invalid username' - - unless RocketChat.checkUsernameAvailability @bodyParams.username - return RocketChat.API.v1.failure 'Username not available' - - userData = {} - - newUserId = RocketChat.saveUser(@userId, @bodyParams) - - if @bodyParams.customFields? - RocketChat.saveCustomFields(newUserId, @bodyParams.customFields) - - user = RocketChat.models.Users.findOneById(newUserId) - - if typeof @bodyParams.joinDefaultChannels is 'undefined' or @bodyParams.joinDefaultChannels - RocketChat.addUserToDefaultChannels(user) - - return RocketChat.API.v1.success - user: user - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - -# Update user -RocketChat.API.v1.addRoute 'user.update', authRequired: true, - post: -> - try - check @bodyParams, - userId: String - data: - email: Match.Maybe(String) - name: Match.Maybe(String) - password: Match.Maybe(String) - username: Match.Maybe(String) - role: Match.Maybe(String) - joinDefaultChannels: Match.Maybe(Boolean) - requirePasswordChange: Match.Maybe(Boolean) - sendWelcomeEmail: Match.Maybe(Boolean) - verified: Match.Maybe(Boolean) - customFields: Match.Maybe(Object) - - userData = _.extend({ _id: @bodyParams.userId }, @bodyParams.data) - - RocketChat.saveUser(@userId, userData) - - if @bodyParams.data.customFields? - RocketChat.saveCustomFields(@bodyParams.userId, @bodyParams.data.customFields) - - return RocketChat.API.v1.success - user: RocketChat.models.Users.findOneById(@bodyParams.userId) - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - - - - -# Delete User -RocketChat.API.v1.addRoute 'users.delete', authRequired: true, - post: -> - if not @bodyParams.userId? - return RocketChat.API.v1.failure 'Body param "userId" is required' - - if not RocketChat.authz.hasPermission(@userId, 'delete-user') - return RocketChat.API.v1.unauthorized() - - id = undefined - try - Meteor.runAsUser this.userId, => - id = Meteor.call 'deleteUser', @bodyParams.userId, [] - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success - -# Set user's avatar -RocketChat.API.v1.addRoute 'users.setAvatar', authRequired: true, - post: -> - try - check @bodyParams, - avatarUrl: Match.Maybe(String) - - user = Meteor.users.findOne(@userId) - - if @bodyParams.avatarUrl - RocketChat.setUserAvatar(user, @bodyParams.avatarUrl, '', 'url') - else - Busboy = Npm.require('busboy') - busboy = new Busboy headers: @request.headers - Meteor.wrapAsync((callback) => - busboy.on 'file', Meteor.bindEnvironment (fieldname, file, filename, encoding, mimetype) => - if fieldname isnt 'image' - return callback(new Meteor.Error 'invalid-field') - - imageData = [] - file.on 'data', Meteor.bindEnvironment (data) -> - imageData.push data - - file.on 'end', Meteor.bindEnvironment () => - RocketChat.setUserAvatar(user, Buffer.concat(imageData), mimetype, 'rest') - callback() - - @request.pipe busboy - )() - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success() - - diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js new file mode 100644 index 00000000000..0e66fad67c6 --- /dev/null +++ b/packages/rocketchat-api/server/v1/users.js @@ -0,0 +1,218 @@ +RocketChat.API.v1.addRoute('users.create', { authRequired: true }, { + post: function() { + try { + check(this.bodyParams, { + email: String, + name: String, + password: String, + username: String, + active: Match.Maybe(Boolean), + role: Match.Maybe(String), + joinDefaultChannels: Match.Maybe(Boolean), + requirePasswordChange: Match.Maybe(Boolean), + sendWelcomeEmail: Match.Maybe(Boolean), + verified: Match.Maybe(Boolean), + customFields: Match.Maybe(Object) + }); + + //New change made by pull request #5152 + if (typeof this.bodyParams.joinDefaultChannels === 'undefined') { + this.bodyParams.joinDefaultChannels = true; + } + + const newUserId = RocketChat.saveUser(this.userId, this.bodyParams); + + if (this.bodyParams.customFields) { + RocketChat.saveCustomFields(newUserId, this.bodyParams.customFields); + } + + if (this.bodyParams.active === false) { + Meteor.runAsUser(this.userId, () => { + Meteor.call('setUserActiveStatus', newUserId, false); + }); + } + + return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(newUserId) }); + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + } +}); + +RocketChat.API.v1.addRoute('users.delete', { authRequired: true }, { + post: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'delete-user')) { + return RocketChat.API.v1.unauthorized(); + } + + if (!this.bodyParams.userId) { + return RocketChat.API.v1.failure('Body param "userId" is required'); + } + + try { + Meteor.runAsUser(this.userId, () => { + Meteor.call('deleteUser', this.bodyParams.userId); + }); + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('users.getPresence', { authRequired: true }, { + get: function() { + if (this.queryParams.userId && this.userId !== this.queryParams.userId) { + if (RocketChat.models.Users.find({ _id: this.queryParams.userId }).count() !== 1) { + return RocketChat.API.v1.failure(`Failed to find a user with the id of "${this.queryParams.userId}"`); + } + + const user = RocketChat.models.Users.findOneById(this.queryParams.userId, { fields: { status: 1 }}); + return RocketChat.API.v1.success({ + presence: user.status + }); + } + + const user = RocketChat.models.Users.findOneById(this.userId); + return RocketChat.API.v1.success({ + presence: user.status, + connectionStatus: user.statusConnection, + lastLogin: user.lastLogin + }); + } +}); + +RocketChat.API.v1.addRoute('users.info', { authRequired: true }, { + get: function() { + if (!this.queryParams.userId) { + return RocketChat.API.v1.failure('The query parameter "userId" must be supplied.'); + } + + const user = RocketChat.models.Users.findOneById(this.queryParams.userId); + + if (!user) { + return RocketChat.API.v1.failure(`No user was found with the id of "${this.queryParams.userId}"`); + } + + let result = undefined; + try { + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getFullUserData', { filter: user.username, limit: 1 }); + }); + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + + if (!result || result.length !== 1) { + return RocketChat.API.v1.failure(`Failed to get the user data for the userId of "${this.queryParams.userId}".`); + } + + return RocketChat.API.v1.success({ + user: result[0] + }); + } +}); + +RocketChat.API.v1.addRoute('users.list', { authRequired: true }, { + get: function() { + let result = undefined; + try { + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getFullUserData', {}); + }); + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + + if (!result) { + return RocketChat.API.v1.failure('Failed to get the users data.'); + } + + return RocketChat.API.v1.success({ + users: result + }); + } +}); + +RocketChat.API.v1.addRoute('users.setAvatar', { authRequired: true }, { + post: function() { + try { + check(this.bodyParams, { avatarUrl: Match.Maybe(String) }); + + const user = Meteor.users.findOne(this.userId); + + if (this.bodyParams.avatarUrl) { + RocketChat.setUserAvatar(user, this.bodyParams.avatarUrl, '', 'url'); + } else { + const Busboy = Npm.require('busboy'); + const busboy = new Busboy({ headers: this.request.headers }); + + Meteor.wrapAsync((callback) => { + busboy.on('file', Meteor.bindEnvironment((fieldname, file, filename, encoding, mimetype) => { + if (fieldname !== 'image') { + return callback(new Meteor.Error('invalid-field')); + } + + const imageData = []; + file.on('data', Meteor.bindEnvironment((data) => { + imageData.push(data); + })); + + file.on('end', Meteor.bindEnvironment(() => { + RocketChat.setUserAvatar(user, Buffer.concat(imageData), mimetype, 'rest'); + callback(); + })); + + this.request.pipe(busboy); + })); + })(); + } + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('users.update', { authRequired: true }, { + post: function() { + try { + check (this.bodyParams, { + userId: String, + data: { + email: Match.Maybe(String), + name: Match.Maybe(String), + password: Match.Maybe(String), + username: Match.Maybe(String), + active: Match.Maybe(Boolean), + role: Match.Maybe(String), + joinDefaultChannels: Match.Maybe(Boolean), + requirePasswordChange: Match.Maybe(Boolean), + sendWelcomeEmail: Match.Maybe(Boolean), + verified: Match.Maybe(Boolean), + customFields: Match.Maybe(Object) + } + }); + + const userData = _.extend({ _id: this.bodyParams.userId }, this.bodyParams.data); + + RocketChat.saveUser(this.userId, userData); + + if (this.bodyParams.data.customFields) { + RocketChat.saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields); + } + + if (typeof this.bodyParams.data.active !== 'undefined') { + Meteor.runAsUser(this.userId, () => { + Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.data.active); + }); + } + + return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId) }); + } catch (e) { + return RocketChat.API.v1.failure(e.name + ': ' + e.message); + } + } +}); diff --git a/packages/rocketchat-lib/server/functions/getFullUserData.js b/packages/rocketchat-lib/server/functions/getFullUserData.js new file mode 100644 index 00000000000..75a319c171a --- /dev/null +++ b/packages/rocketchat-lib/server/functions/getFullUserData.js @@ -0,0 +1,50 @@ +/* globals RocketChat */ +RocketChat.getFullUserData = function({userId, filter, limit}) { + let fields = { + name: 1, + username: 1, + status: 1, + utcOffset: 1, + type: 1, + active: 1 + }; + + if (RocketChat.authz.hasPermission(userId, 'view-full-other-user-info')) { + fields = _.extend(fields, { + emails: 1, + phone: 1, + statusConnection: 1, + createdAt: 1, + lastLogin: 1, + services: 1, + requirePasswordChange: 1, + requirePasswordChangeReason: 1, + roles: 1 + }); + } else { + limit = 1; + } + + filter = s.trim(filter); + + if (!filter && limit === 1) { + return undefined; + } + + const options = { + fields: fields, + limit: limit, + sort: { username: 1 } + }; + + if (filter) { + if (limit === 1) { + return RocketChat.models.Users.findByUsername(filter, options); + } else { + const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); + return RocketChat.models.Users.findByUsernameNameOrEmailAddress(filterReg, options); + } + } + + return RocketChat.models.Users.find({}, options); +}; diff --git a/packages/rocketchat-lib/server/methods/getFullUserData.js b/packages/rocketchat-lib/server/methods/getFullUserData.js new file mode 100644 index 00000000000..f9caf8b23d7 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/getFullUserData.js @@ -0,0 +1,11 @@ +Meteor.methods({ + getFullUserData({ filter = '', limit }) { + const result = RocketChat.getFullUserData({ userId: Meteor.userId(), filter, limit }); + + if (!result) { + return result; + } + + return result.fetch(); + } +});