First version of integrations working

pull/1613/head
Rodrigo Nascimento 10 years ago
parent 1f74b78351
commit e3dd15a769
  1. 0
      packages/rocketchat-authorization/server/publications/integrations.coffee
  2. 3
      packages/rocketchat-integrations/client/route.coffee
  3. 2
      packages/rocketchat-integrations/client/startup.coffee
  4. 3
      packages/rocketchat-integrations/client/views/integrations.coffee
  5. 25
      packages/rocketchat-integrations/client/views/integrations.html
  6. 62
      packages/rocketchat-integrations/client/views/integrationsIncoming.coffee
  7. 56
      packages/rocketchat-integrations/client/views/integrationsIncoming.html
  8. 35
      packages/rocketchat-integrations/package.js
  9. 78
      packages/rocketchat-integrations/server/api/api.coffee
  10. 57
      packages/rocketchat-integrations/server/methods/addIntegration.coffee
  11. 8
      packages/rocketchat-integrations/server/methods/deleteIntegration.coffee
  12. 38
      packages/rocketchat-integrations/server/methods/updateIntegration.coffee
  13. 13
      packages/rocketchat-integrations/server/models/Integrations.coffee
  14. 8
      packages/rocketchat-integrations/server/publications/integrations.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

@ -1,3 +1,5 @@
Meteor.subscribe 'integrations'
RocketChat.AdminBox.addOption
href: 'admin-integrations'
i18nLabel: 'Integrations'

@ -1,3 +1,6 @@
Template.integrations.helpers
hasPermission: ->
return RocketChat.authz.hasAllPermission 'manage-integrations'
integrations: ->
return ChatIntegrations.find()

@ -3,11 +3,28 @@
{{#if hasPermission}}
<a href="{{pathFor "admin-integrations-new"}}" class="button primary new-role">{{_ "New_integration"}}</a>
{{#each integration}}
<div>
<div class="rocket-form">
<div class="section">
<div class="admin-integrations-new-panel">
{{#each integrations}}
<a href="{{pathFor "admin-integrations-incoming" token=token}}">
<div class="admin-integrations-new-item">
<i class="icon-login"></i>
<div class="admin-integrations-new-item-body">
<div class="admin-integrations-new-item-title">
Incoming WebHook {{#if name}}- {{name}}{{/if}}
</div>
<div class="admin-integrations-new-item-description">
Post to <strong>{{channel}}</strong> as <strong>{{username}}</strong>
</div>
</div>
<i class="icon-angle-right"></i>
</div>
</a>
{{/each}}
</div>
</div>
{{/each}}
</div>
{{else}}
{{_ "Not_authorized"}}
{{/if}}

@ -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}

@ -6,49 +6,47 @@
<div class="section">
<div class="section-content">
<div class="input-line double-col">
<label>Post to Channel</label>
<label>Name (optional)</label>
<div>
<div>
<label><input type="radio" name="channelType" value="c" checked="{{$eq data.channelType 'c'}}" /> {{_ "Channel"}}</label>
<label><input type="radio" name="channelType" value="d" checked="{{$eq data.channelType 'd'}}" /> {{_ "User"}}</label>
</div>
<input type="text" name="{{_id}}" value="{{value}}" placeholder="{{_ 'User_or_channel_name'}}" />
<div class="settings-description">Messages that are sent to the incoming webhook will be posted here.</div>
<input type="text" name="name" value="{{data.name}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">You can give a name just to manage your integrations.</div>
</div>
</div>
<div class="input-line double-col">
<label>Customize Name</label>
<label>Post to Channel</label>
<div>
<input type="text" name="{{_id}}" value="{{value}}" placeholder="{{placeholder}}" />
<div class="settings-description">Choose the username that this integration will post as.</div>
<input type="text" name="channel" value="{{data.channel}}" placeholder="{{_ 'User_or_channel_name'}}" />
<div class="settings-description">Messages that are sent to the incoming webhook will be posted here.</div>
<div class="settings-description">Start with <code class="inline">@</code> for user or <code class="inline">#</code> for channel. Eg: <code class="inline">@john</code> or <code class="inline">#general</code>.</div>
</div>
</div>
<!-- <div class="input-line double-col">
<label>Customize Icon</label>
<div class="input-line double-col">
<label>Post as:</label>
<div>
{{#if value}}
<div class="settings-file-preview">
<div class="preview" style="background-image:url({{value}}?_dc={{random}});"></div>
<div class="action">
<button type="button" class="button red delete-asset"><i class="icon-trash"></i>{{_ 'Delete'}}</button>
</div>
</div>
{{#if data.username}}
<input type="text" name="username" value="{{data.username}}" disabled="disabled" />
{{else}}
<div class="settings-file-preview">
<div class="preview no-file"><i class="icon-upload"></i></div>
<div class="action">
<div class="button primary">{{_ 'Select_file'}}
<input type="file" accept="{{fileConstraints.contentType}}" />
</div>
</div>
</div>
<input type="text" name="username" value="{{data.username}}" />
{{/if}}
<div class="settings-description">Change the icon that is used for messages from this integration.</div>
<div class="settings-description">Choose the username that this integration will post as.</div>
<div class="settings-description">Should exists a user with this username.</div>
</div>
</div>
{{#if data.token}}
<div class="input-line double-col">
<label>Webhook URL</label>
<div>
<input type="text" name="token" value="{{data.url}}" disabled="disabled" />
<div class="settings-description">Send your JSON payloads to this URL. <a href="#" class="clipboard" data-clipboard-target="[name=token]">COPY TO CLIPBOARD</a></div>
</div>
</div>
</div> -->
{{/if}}
</div>
</div>
<div class="submit">
{{#if data.token}}
<button class="button red delete"><i class="icon-trash"></i><span>{{_ "Delete"}}</span></button>
{{/if}}
<button class="button save"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button>
</div>
</div>

@ -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;
}
}));

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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()
Loading…
Cancel
Save