From e3dd15a769dc7c1d4fea5bc8305bdf7eace4e772 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Dec 2015 21:25:30 -0200 Subject: [PATCH] First version of integrations working --- .../server/publications/integrations.coffee | 0 .../client/route.coffee | 3 +- .../client/startup.coffee | 2 + .../client/views/integrations.coffee | 3 + .../client/views/integrations.html | 25 +++++- .../client/views/integrationsIncoming.coffee | 62 +++++++++++++++ .../client/views/integrationsIncoming.html | 56 +++++++------ packages/rocketchat-integrations/package.js | 35 +++------ .../server/api/api.coffee | 78 +++++++++++++++++++ .../server/methods/addIntegration.coffee | 57 ++++++++++++++ .../server/methods/deleteIntegration.coffee | 8 ++ .../server/methods/updateIntegration.coffee | 38 +++++++++ .../server/models/Integrations.coffee | 13 ++++ .../server/publications/integrations.coffee | 8 ++ 14 files changed, 330 insertions(+), 58 deletions(-) create mode 100644 packages/rocketchat-authorization/server/publications/integrations.coffee create mode 100644 packages/rocketchat-integrations/server/api/api.coffee create mode 100644 packages/rocketchat-integrations/server/methods/addIntegration.coffee create mode 100644 packages/rocketchat-integrations/server/methods/deleteIntegration.coffee create mode 100644 packages/rocketchat-integrations/server/methods/updateIntegration.coffee create mode 100644 packages/rocketchat-integrations/server/models/Integrations.coffee create mode 100644 packages/rocketchat-integrations/server/publications/integrations.coffee diff --git a/packages/rocketchat-authorization/server/publications/integrations.coffee b/packages/rocketchat-authorization/server/publications/integrations.coffee new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rocketchat-integrations/client/route.coffee b/packages/rocketchat-integrations/client/route.coffee index e32f6d73e8c..62d2b85b1d1 100644 --- a/packages/rocketchat-integrations/client/route.coffee +++ b/packages/rocketchat-integrations/client/route.coffee @@ -16,10 +16,11 @@ FlowRouter.route '/admin/integrations/new', pageTemplate: 'integrationsNew' -FlowRouter.route '/admin/integrations/incoming', +FlowRouter.route '/admin/integrations/incoming/:token?', name: 'admin-integrations-incoming' action: (params) -> BlazeLayout.render 'main', center: 'pageSettingsContainer' pageTitle: t('Integration_Incoming_WebHook') pageTemplate: 'integrationsIncoming' + params: params diff --git a/packages/rocketchat-integrations/client/startup.coffee b/packages/rocketchat-integrations/client/startup.coffee index b1279bb0dfe..64191b37e73 100644 --- a/packages/rocketchat-integrations/client/startup.coffee +++ b/packages/rocketchat-integrations/client/startup.coffee @@ -1,3 +1,5 @@ +Meteor.subscribe 'integrations' + RocketChat.AdminBox.addOption href: 'admin-integrations' i18nLabel: 'Integrations' diff --git a/packages/rocketchat-integrations/client/views/integrations.coffee b/packages/rocketchat-integrations/client/views/integrations.coffee index be6f7ccdc46..1bab31e4dfa 100644 --- a/packages/rocketchat-integrations/client/views/integrations.coffee +++ b/packages/rocketchat-integrations/client/views/integrations.coffee @@ -1,3 +1,6 @@ Template.integrations.helpers hasPermission: -> return RocketChat.authz.hasAllPermission 'manage-integrations' + + integrations: -> + return ChatIntegrations.find() diff --git a/packages/rocketchat-integrations/client/views/integrations.html b/packages/rocketchat-integrations/client/views/integrations.html index 94402e8603d..88470f77269 100644 --- a/packages/rocketchat-integrations/client/views/integrations.html +++ b/packages/rocketchat-integrations/client/views/integrations.html @@ -3,11 +3,28 @@ {{#if hasPermission}} {{_ "New_integration"}} - {{#each integration}} -
- + {{else}} {{_ "Not_authorized"}} {{/if}} diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee index 2ce5018f126..ef008cc8725 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee @@ -4,5 +4,67 @@ Template.integrationsIncoming.helpers return RocketChat.authz.hasAllPermission 'manage-integrations' data: -> + params = Template.instance().data.params() + + if params.token? + data = ChatIntegrations.findOne({token: params.token}) + data.url = Meteor.absoluteUrl("hooks/#{data._id}/#{data.userId}/#{data.token}") + return data + return {} = channelType: 'c' + + +Template.integrationsIncoming.events + "click .submit > .delete": -> + params = Template.instance().data.params() + + swal + title: t('Are_you_sure') + text: t('You_will_not_be_able_to_recover') + type: 'warning' + showCancelButton: true + confirmButtonColor: '#DD6B55' + confirmButtonText: t('Yes_delete_it') + cancelButtonText: t('Cancel') + closeOnConfirm: false + html: false + , -> + Meteor.call "deleteIntegration", params._id, (err, data) -> + swal + title: t('Deleted') + text: t('Your_entry_has_been_deleted') + type: 'success' + timer: 1000 + showConfirmButton: false + + "click .submit > .save": -> + name = $('[name=name]').val().trim() + channel = $('[name=channel]').val().trim() + username = $('[name=username]').val().trim() + + if channel is '' + return toastr.error TAPi18n.__("The_channel_name_is_required") + + if username is '' + return toastr.error TAPi18n.__("The_username_is_required") + + integration = + channel: channel + name: name if name isnt '' + + params = Template.instance().data.params() + if params._id? + Meteor.call "updateIntegration", params._id, integration, (err, data) -> + if err? + toastr.error TAPi18n.__(err.error) + toastr.success TAPi18n.__("Integration_updated") + else + integration.type = 'webhook-incoming' + integration.username = username + + Meteor.call "addIntegration", integration, (err, data) -> + if err? + toastr.error TAPi18n.__(err.error) + toastr.success TAPi18n.__("Integration_added") + FlowRouter.go "admin-integrations-incoming", {token: data.token} diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.html b/packages/rocketchat-integrations/client/views/integrationsIncoming.html index 4b723e36d2e..e8a650585e2 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.html +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.html @@ -6,49 +6,47 @@
- +
-
- - -
- -
Messages that are sent to the incoming webhook will be posted here.
+ +
You can give a name just to manage your integrations.
- +
- -
Choose the username that this integration will post as.
+ +
Messages that are sent to the incoming webhook will be posted here.
+
Start with @ for user or # for channel. Eg: @john or #general.
- + {{/if}}
+ {{#if data.token}} + + {{/if}}
diff --git a/packages/rocketchat-integrations/package.js b/packages/rocketchat-integrations/package.js index 588673a00a6..ae298211d72 100644 --- a/packages/rocketchat-integrations/package.js +++ b/packages/rocketchat-integrations/package.js @@ -33,36 +33,23 @@ Package.onUse(function(api) { api.addAssets('client/stylesheets/integrations.less', 'server'); api.addFiles('client/stylesheets/load.coffee', 'server'); - // api.addFiles('server/models/Permissions.coffee', ['server']); + api.addFiles('server/models/Integrations.coffee', 'server'); - // api.addFiles('server/functions/addUsersToRoles.coffee', ['server']); - // api.addFiles('server/functions/getPermissionsForRole.coffee', ['server']); - // api.addFiles('server/functions/getRoles.coffee', ['server']); - // api.addFiles('server/functions/getRolesForUser.coffee', ['server']); - // api.addFiles('server/functions/getUsersInRole.coffee', ['server']); - // api.addFiles('server/functions/hasPermission.coffee', ['server']); - // api.addFiles('server/functions/hasRole.coffee', ['server']); - // api.addFiles('server/functions/removeUsersFromRoles.coffee', ['server']); + // publications + api.addFiles('server/publications/integrations.coffee', 'server'); - // // publications - // api.addFiles('server/publication.coffee', ['server']); - // api.addFiles('server/publications/roles.coffee', 'server'); - // api.addFiles('server/publications/usersInRole.coffee', 'server'); + // methods + api.addFiles('server/methods/addIntegration.coffee', 'server'); + api.addFiles('server/methods/updateIntegration.coffee', 'server'); + api.addFiles('server/methods/deleteIntegration.coffee', 'server'); - // // methods - // api.addFiles('server/methods/addUserToRole.coffee', 'server'); - // api.addFiles('server/methods/deleteRole.coffee', 'server'); - // api.addFiles('server/methods/removeUserFromRole.coffee', 'server'); - // api.addFiles('server/methods/saveRole.coffee', 'server'); - // api.addFiles('server/methods/addPermissionToRole.coffee', 'server'); - // api.addFiles('server/methods/removeRoleFromPermission.coffee', 'server'); - - // api.addFiles('server/startup.coffee', ['server']); + // api + api.addFiles('server/api/api.coffee', 'server'); var _ = Npm.require('underscore'); var fs = Npm.require('fs'); - tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-authorization/i18n'), function(filename) { - if (fs.statSync('packages/rocketchat-authorization/i18n/' + filename).size > 16) { + tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-integrations/i18n'), function(filename) { + if (fs.statSync('packages/rocketchat-integrations/i18n/' + filename).size > 16) { return 'i18n/' + filename; } })); diff --git a/packages/rocketchat-integrations/server/api/api.coffee b/packages/rocketchat-integrations/server/api/api.coffee new file mode 100644 index 00000000000..ae296cdb2e9 --- /dev/null +++ b/packages/rocketchat-integrations/server/api/api.coffee @@ -0,0 +1,78 @@ +Api = new Restivus + enableCors: false + apiPath: 'hooks/' + auth: + user: -> + user = RocketChat.models.Users.findOne + _id: @request.params.userId + 'services.resume.loginTokens.hashedToken': @request.params.token + + return user: user + + +Api.addRoute ':integrationId/:userId/:token', authRequired: true, + post: -> + integration = RocketChat.models.Integrations.findOne(@urlParams.integrationId) + user = RocketChat.models.Users.findOne(@userId) + + channel = @bodyParams.channel or integration.channel + channelType = channel[0] + channel = channel.substr(1) + + switch channelType + when '#' + room = RocketChat.models.Rooms.findOne + $or: [ + {_id: channel} + {name: channel} + ] + + if not room? + return {} = + statusCode: 400 + body: + success: false + error: 'invalid-channel' + + rid = room._id + Meteor.runAsUser user._id, -> + Meteor.call 'joinRoom', room._id + + when '@' + roomUser = RocketChat.models.Users.findOne + $or: [ + {_id: channel} + {username: channel} + ] + + if not roomUser? + return {} = + statusCode: 400 + body: + success: false + error: 'invalid-channel' + + rid = [user._id, roomUser._id].sort().join('') + room = RocketChat.models.Rooms.findOne(rid) + + if not room + Meteor.runAsUser user._id, -> + Meteor.call 'createDirectMessage', roomUser._id + room = RocketChat.models.Rooms.findOne(rid) + + else + return {} = + statusCode: 400 + body: + success: false + error: 'invalid-channel-type' + + message = + msg: @bodyParams.text + + RocketChat.sendMessage user, message, room, {} + + return {} = + statusCode: 200 + body: + success: true diff --git a/packages/rocketchat-integrations/server/methods/addIntegration.coffee b/packages/rocketchat-integrations/server/methods/addIntegration.coffee new file mode 100644 index 00000000000..8be0557dd5e --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/addIntegration.coffee @@ -0,0 +1,57 @@ +Meteor.methods + addIntegration: (integration) -> + if not _.isString(integration.channel) + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel must be string' + + if integration.channel.trim() is '' + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel can\'t be empty' + + if integration.channel[0] not in ['@', '#'] + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel should start with # or @' + + if not _.isString(integration.username) + throw new Meteor.Error 'invalid_username', '[methods] addIntegration -> username must be string' + + if integration.username.trim() is '' + throw new Meteor.Error 'invalid_username', '[methods] addIntegration -> username can\'t be empty' + + record = undefined + switch integration.channel[0] + when '#' + record = RocketChat.models.Rooms.findOne + $or: [ + {_id: integration.channel} + {name: integration.channel} + ] + when '@' + record = RocketChat.models.Users.findOne + $or: [ + {_id: integration.channel} + {username: integration.channel} + ] + + if record is undefined + throw new Meteor.Error 'channel_does_not_exists', "[methods] addIntegration -> The channel does not exists" + + user = RocketChat.models.Users.findOne({username: integration.username}) + + if not user? + throw new Meteor.Error 'user_does_not_exists', "[methods] addIntegration -> The username does not exists" + + stampedToken = Accounts._generateStampedLoginToken() + hashStampedToken = Accounts._hashStampedToken(stampedToken) + + updateObj = + $push: + 'services.resume.loginTokens': + hashedToken: hashStampedToken.hashedToken + integration: true + + integration.token = hashStampedToken.hashedToken + integration.userId = user._id + + RocketChat.models.Users.update {_id: user._id}, updateObj + + RocketChat.models.Integrations.insert integration + + return integration diff --git a/packages/rocketchat-integrations/server/methods/deleteIntegration.coffee b/packages/rocketchat-integrations/server/methods/deleteIntegration.coffee new file mode 100644 index 00000000000..e257b8e903c --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/deleteIntegration.coffee @@ -0,0 +1,8 @@ +Meteor.methods + deleteIntegration: (integrationId) -> + if not RocketChat.models.Integrations.findOne(integrationId)? + throw new Meteor.Error 'invalid_integration', '[methods] addIntegration -> integration not found' + + RocketChat.models.Integrations.remove _id: integrationId + + return true diff --git a/packages/rocketchat-integrations/server/methods/updateIntegration.coffee b/packages/rocketchat-integrations/server/methods/updateIntegration.coffee new file mode 100644 index 00000000000..5bfa786dd04 --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/updateIntegration.coffee @@ -0,0 +1,38 @@ +Meteor.methods + updateIntegration: (integrationId, integration) -> + if not _.isString(integration.channel) + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel must be string' + + if integration.channel.trim() is '' + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel can\'t be empty' + + if integration.channel[0] not in ['@', '#'] + throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel should start with # or @' + + if not RocketChat.models.Integrations.findOne(integrationId)? + throw new Meteor.Error 'invalid_integration', '[methods] addIntegration -> integration not found' + + record = undefined + switch integration.channel[0] + when '#' + record = RocketChat.models.Rooms.findOne + $or: [ + {_id: integration.channel} + {name: integration.channel} + ] + when '@' + record = RocketChat.models.Users.findOne + $or: [ + {_id: integration.channel} + {username: integration.channel} + ] + + if record is undefined + throw new Meteor.Error 'channel_does_not_exists', "[methods] addIntegration -> The channel does not exists" + + RocketChat.models.Integrations.update integrationId, + $set: + name: integration.name + channel: integration.channel + + return RocketChat.models.Integrations.findOne(integrationId) diff --git a/packages/rocketchat-integrations/server/models/Integrations.coffee b/packages/rocketchat-integrations/server/models/Integrations.coffee new file mode 100644 index 00000000000..8ca464e07ce --- /dev/null +++ b/packages/rocketchat-integrations/server/models/Integrations.coffee @@ -0,0 +1,13 @@ +RocketChat.models.Integrations = new class extends RocketChat.models._Base + constructor: -> + @_initModel 'integrations' + + + # FIND + # findByRole: (role, options) -> + # query = + # roles: role + + # return @find query, options + + # CREATE diff --git a/packages/rocketchat-integrations/server/publications/integrations.coffee b/packages/rocketchat-integrations/server/publications/integrations.coffee new file mode 100644 index 00000000000..1d958bd2922 --- /dev/null +++ b/packages/rocketchat-integrations/server/publications/integrations.coffee @@ -0,0 +1,8 @@ +Meteor.publish 'integrations', -> + unless @userId + return @ready() + + if not RocketChat.authz.hasPermission @userId, 'manage-integrations' + throw new Meteor.Error "not-authorized" + + return RocketChat.models.Integrations.find()