diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index a692a30..43fbace 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -15,23 +15,19 @@ class DiscourseChat::ChatController < ApplicationController render json:providers, root: 'providers' end - def test_provider + def test begin - requested_provider = params[:provider] - channel = params[:channel] - topic_id = params[:topic_id] + channel_id = params[:channel_id].to_i + topic_id = params[:topic_id].to_i - provider = ::DiscourseChat::Provider.get_by_name(requested_provider) + channel = DiscourseChat::Channel.find(channel_id) - if provider.nil? or not ::DiscourseChat::Provider.is_enabled(provider) + provider = ::DiscourseChat::Provider.get_by_name(channel.provider) + + if not ::DiscourseChat::Provider.is_enabled(provider) raise Discourse::NotFound end - if defined? provider::PROVIDER_CHANNEL_REGEX - channel_regex = Regexp.new provider::PROVIDER_CHANNEL_REGEX - raise Discourse::InvalidParameters, 'Channel is not valid' if not channel_regex.match?(channel) - end - post = Topic.find(topic_id.to_i).posts.first provider.trigger_notification(post, channel) @@ -48,23 +44,91 @@ class DiscourseChat::ChatController < ApplicationController end end + def list_channels + providers = ::DiscourseChat::Provider.enabled_providers.map {|x| x::PROVIDER_NAME} + + requested_provider = params[:provider] + + if not providers.include? requested_provider + raise Discourse::NotFound + end + + channels = DiscourseChat::Channel.with_provider(requested_provider) + + render_serialized channels, DiscourseChat::ChannelSerializer, root: 'channels' + end + + def create_channel + begin + providers = ::DiscourseChat::Provider.enabled_providers.map {|x| x::PROVIDER_NAME} + + requested_provider = params[:channel][:provider] + + if not providers.include? requested_provider + raise Discourse::InvalidParameters, 'Provider is not valid' + end + + allowed_keys = DiscourseChat::Provider.get_by_name(requested_provider)::CHANNEL_PARAMETERS.map{|p| p[:key].to_sym} + + hash = params.require(:channel).permit(:provider, data:allowed_keys) + + channel = DiscourseChat::Channel.new(hash) + + if not channel.save(hash) + raise Discourse::InvalidParameters, 'Channel is not valid' + end + + render_serialized channel, DiscourseChat::ChannelSerializer, root: 'channel' + rescue Discourse::InvalidParameters => e + render json: {errors: [e.message]}, status: 422 + end + end + + def update_channel + begin + channel = DiscourseChat::Channel.find(params[:id].to_i) + # rule.error_key = nil # Reset any error on the rule + + allowed_keys = DiscourseChat::Provider.get_by_name(channel.provider)::CHANNEL_PARAMETERS.map{|p| p[:key].to_sym} + + hash = params.require(:channel).permit(data:allowed_keys) + + if not channel.update(hash) + raise Discourse::InvalidParameters, 'Channel is not valid' + end + + render_serialized channel, DiscourseChat::ChannelSerializer, root: 'channel' + rescue Discourse::InvalidParameters => e + render json: {errors: [e.message]}, status: 422 + end + end + + def destroy_channel + rule = DiscourseChat::Channel.find(params[:id].to_i) + + rule.destroy + + render json: success_json + end + + def list_rules providers = ::DiscourseChat::Provider.enabled_providers.map {|x| x::PROVIDER_NAME} requested_provider = params[:provider] - if providers.include? requested_provider - rules = DiscourseChat::Rule.with_provider(requested_provider) - else + if not providers.include? requested_provider raise Discourse::NotFound end + rules = DiscourseChat::Rule.with_provider(requested_provider) + render_serialized rules, DiscourseChat::RuleSerializer, root: 'rules' end def create_rule begin - hash = params.require(:rule).permit(:provider, :channel, :filter, :category_id, tags:[]) + hash = params.require(:rule).permit(:channel_id, :filter, :category_id, tags:[]) rule = DiscourseChat::Rule.new(hash) @@ -82,7 +146,7 @@ class DiscourseChat::ChatController < ApplicationController begin rule = DiscourseChat::Rule.find(params[:id].to_i) rule.error_key = nil # Reset any error on the rule - hash = params.require(:rule).permit(:provider, :channel, :filter, :category_id, tags:[]) + hash = params.require(:rule).permit(:filter, :category_id, tags:[]) if not rule.update(hash) raise Discourse::InvalidParameters, 'Rule is not valid' diff --git a/app/models/rule.rb b/app/models/rule.rb index 20623d9..3300c4e 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -17,7 +17,7 @@ class DiscourseChat::Rule < DiscourseChat::PluginModel def channel_valid? # Validate category - if not (channel_id.nil? or DiscourseChat::Channel.where(id: channel_id).exists?) + if not (DiscourseChat::Channel.where(id: channel_id).exists?) errors.add(:channel_id, "#{channel_id} is not a valid channel id") end end diff --git a/app/routes/discourse_chat.rb b/app/routes/discourse_chat.rb index 207b4e1..cf137e6 100644 --- a/app/routes/discourse_chat.rb +++ b/app/routes/discourse_chat.rb @@ -4,10 +4,15 @@ module DiscourseChat AdminEngine.routes.draw do get "" => "chat#respond" get '/providers' => "chat#list_providers" - post '/test' => "chat#test_provider" + post '/test' => "chat#test" + get '/channels' => "chat#list_channels" + post '/channels' => "chat#create_channel" + put '/channels/:id' => "chat#update_channel" + delete '/channels/:id' => "chat#destroy_channel" + get '/rules' => "chat#list_rules" - put '/rules' => "chat#create_rule" + post '/rules' => "chat#create_rule" put '/rules/:id' => "chat#update_rule" delete '/rules/:id' => "chat#destroy_rule" diff --git a/app/serializers/channel_serializer.rb b/app/serializers/channel_serializer.rb new file mode 100644 index 0000000..4022976 --- /dev/null +++ b/app/serializers/channel_serializer.rb @@ -0,0 +1,11 @@ +require_relative './rule_serializer' + +class DiscourseChat::ChannelSerializer < ApplicationSerializer + attributes :id, :provider, :data, :rules + + def rules + object.rules.map do |rule| + DiscourseChat::RuleSerializer.new(rule, root:false) + end + end +end \ No newline at end of file diff --git a/app/serializers/rule_serializer.rb b/app/serializers/rule_serializer.rb index 7ff023b..d8b3f77 100644 --- a/app/serializers/rule_serializer.rb +++ b/app/serializers/rule_serializer.rb @@ -1,3 +1,3 @@ -class DiscourseChat::RuleSerializer < ActiveModel::Serializer - attributes :id, :provider, :channel, :category_id, :tags, :filter, :error_key +class DiscourseChat::RuleSerializer < ApplicationSerializer + attributes :id, :channel_id, :category_id, :tags, :filter, :error_key end \ No newline at end of file diff --git a/assets/javascripts/admin/adapters/channel.js.es6 b/assets/javascripts/admin/adapters/channel.js.es6 new file mode 100644 index 0000000..8287b75 --- /dev/null +++ b/assets/javascripts/admin/adapters/channel.js.es6 @@ -0,0 +1,7 @@ +import buildPluginAdapter from 'admin/adapters/build-plugin'; +import Rule from 'discourse/plugins/discourse-chat-integration/admin/models/rule' + +export default buildPluginAdapter('chat').extend({ + + +}); \ No newline at end of file diff --git a/assets/javascripts/admin/components/channel-details.js.es6 b/assets/javascripts/admin/components/channel-details.js.es6 new file mode 100644 index 0000000..6e3be76 --- /dev/null +++ b/assets/javascripts/admin/components/channel-details.js.es6 @@ -0,0 +1,36 @@ +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Component.extend({ + classNames: ['channel-details'], + actions: { + refresh: function(){ + this.sendAction('refresh'); + }, + + delete(channel){ + bootbox.confirm(I18n.t("chat_integration.channel_delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => { + if (result) { + channel.destroyRecord().then(() => { + this.send('refresh'); + }).catch(popupAjaxError) + } + }); + }, + + edit(channel){ + this.sendAction('edit', channel) + }, + + test(channel){ + this.sendAction('test', channel) + }, + + createRule(channel){ + var newRule = this.get('store').createRecord('rule',{channel_id: channel.id}); + channel.rules.pushObject(newRule) + } + + + + } +}); \ No newline at end of file diff --git a/assets/javascripts/admin/components/rule-row.js.es6 b/assets/javascripts/admin/components/rule-row.js.es6 new file mode 100644 index 0000000..7622fa1 --- /dev/null +++ b/assets/javascripts/admin/components/rule-row.js.es6 @@ -0,0 +1,38 @@ +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Component.extend({ + tagName: 'tr', + editing: false, + + autoEdit: function(){ + if(!this.get('rule').id){ + this.set('editing', true); + } + }.on('init'), + + actions: { + edit: function(){ + this.set('editing', true); + }, + + cancel: function(){ + this.send('refresh'); + }, + + save: function(){ + this.get('rule').save().then(result => { + this.send('refresh'); + }).catch(popupAjaxError); + }, + + delete(rule){ + rule.destroyRecord().then(() => { + this.send('refresh'); + }).catch(popupAjaxError) + }, + + refresh: function(){ + this.sendAction('refresh'); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 b/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 index 5fd9341..335d400 100644 --- a/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 +++ b/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 @@ -7,42 +7,38 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; export default Ember.Controller.extend({ modalShowing: false, - + anyErrors: function(){ var anyErrors = false; - this.get('model.rules').forEach(function(rule){ - if(rule.error_key){ + this.get('model.channels').forEach(function(channel){ + if(channel.error_key){ anyErrors = true; } }); return anyErrors; - }.property('model.rules'), + }.property('model.channels'), actions:{ - create(){ + createChannel(){ this.set('modalShowing', true); - var model = {rule: this.store.createRecord('rule',{provider: this.get('model.provider').id}), provider:this.get('model.provider')}; - showModal('admin-plugins-chat-edit-rule', { model: model, admin: true }); + var model = {channel: this.store.createRecord('channel',{provider: this.get('model.provider').id, data:{}},), provider:this.get('model.provider')}; + showModal('admin-plugins-chat-edit-channel', { model: model, admin: true }); }, - edit(rule){ + editChannel(channel){ this.set('modalShowing', true); - var model = {rule: rule, provider:this.get('model.provider')}; - showModal('admin-plugins-chat-edit-rule', { model: model, admin: true }); - }, - delete(rule){ - const self = this; - rule.destroyRecord().then(function() { - self.send('refresh'); - }).catch(popupAjaxError) + var model = {channel: channel, provider: this.get('model.provider')}; + showModal('admin-plugins-chat-edit-channel', { model: model, admin: true }); }, + testChannel(channel){ + this.set('modalShowing', true); + var model = {channel:channel} + showModal('admin-plugins-chat-test', { model: model, admin: true }); + }, showError(error_key){ bootbox.alert(I18n.t(error_key)); }, - test(){ - this.set('modalShowing', true); - var model = {provider:this.get('model.provider'), channel:''} - showModal('admin-plugins-chat-test', { model: model, admin: true }); - } + + } diff --git a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-channel.js.es6 b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-channel.js.es6 new file mode 100644 index 0000000..b215fba --- /dev/null +++ b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-channel.js.es6 @@ -0,0 +1,94 @@ +import Rule from 'discourse/plugins/discourse-chat-integration/admin/models/rule' +import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import { ajax } from 'discourse/lib/ajax'; +import { extractError } from 'discourse/lib/ajax-error'; +import InputValidation from 'discourse/models/input-validation'; + +export default Ember.Controller.extend(ModalFunctionality, { + + initThing: function(){ + console.log("Initialising controller"); + console.log(this.get('model.data')); + }.on('init'), + + // The validation property must be defined at runtime since the possible parameters vary by provider + setupValidations: function(){ + if(this.get('model.provider')){ + var theKeys = this.get('model.provider.channel_parameters').map( ( param ) => param['key'] ); + Ember.defineProperty(this,'paramValidation',Ember.computed('model.channel.data.{' + theKeys.join(',') + '}',this._paramValidation)); + } + }.observes('model'), + + validate(parameter){ + var regString = parameter.regex; + var regex = new RegExp(regString); + var val = this.get('model.channel.data.'+parameter.key); + + if(val == ""){ // Fail silently if field blank + return InputValidation.create({ + failed: true, + }); + }else if(!regString){ // Pass silently if no regex available for provider + return InputValidation.create({ + ok: true, + }); + }else if(regex.test(val)){ // Test against regex + return InputValidation.create({ + ok: true, + reason: I18n.t('chat_integration.edit_channel_modal.channel_validation.ok') + }); + }else{ // Failed regex + return InputValidation.create({ + failed: true, + reason: I18n.t('chat_integration.edit_channel_modal.channel_validation.fail') + }); + } + + }, + + _paramValidation: function(){ + var response = {} + var parameters = this.get('model.provider.channel_parameters'); + parameters.forEach(parameter => { + response[parameter.key] = this.validate(parameter); + }); + return response; + }, + + saveDisabled: function(){ + var validations = this.get('paramValidation'); + + if(!validations){ return true } + + var invalid = false; + + Object.keys(validations).forEach(key =>{ + if(!validations[key]){ + invalid = true; + } + if(!validations[key]['ok']){ + invalid = true; + } + }); + + return invalid; + }.property('paramValidation'), + + actions: { + cancel: function(){ + this.send('closeModal'); + }, + + save: function(){ + + const self = this; + + this.get('model.channel').save().then(function(result) { + self.send('closeModal'); + }).catch(function(error) { + self.flash(extractError(error), 'error'); + }); + + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 deleted file mode 100644 index 2093a03..0000000 --- a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 +++ /dev/null @@ -1,61 +0,0 @@ -import Rule from 'discourse/plugins/discourse-chat-integration/admin/models/rule' -import ModalFunctionality from 'discourse/mixins/modal-functionality'; -import { ajax } from 'discourse/lib/ajax'; -import { extractError } from 'discourse/lib/ajax-error'; -import InputValidation from 'discourse/models/input-validation'; - -export default Ember.Controller.extend(ModalFunctionality, { - - model: Rule.create({}), - - channelValidation: function(){ - - var regString = this.get('model.provider.channel_regex'); - var regex = new RegExp(regString); - var val = this.get('model.rule.channel'); - - if(val == ""){ // Fail silently if field blank - return InputValidation.create({ - failed: true, - }); - }else if(!regString){ // Pass silently if no regex available for provider - return InputValidation.create({ - ok: true, - }); - }else if(regex.test(val)){ // Test against regex - return InputValidation.create({ - ok: true, - reason: I18n.t('chat_integration.edit_rule_modal.channel_validation.ok') - }); - }else{ // Failed regex - return InputValidation.create({ - failed: true, - reason: I18n.t('chat_integration.edit_rule_modal.channel_validation.fail') - }); - } - }.property('model.rule.channel'), - - saveDisabled: function(){ - if(this.get('channelValidation.failed')){ return true } - - return false; - }.property('channelValidation.failed'), - - actions: { - cancel: function(){ - this.send('closeModal'); - }, - - save: function(){ - - const self = this; - - this.get('model.rule').update().then(function(result) { - self.send('closeModal'); - }).catch(function(error) { - self.flash(extractError(error), 'error'); - }); - - } - } -}); \ No newline at end of file diff --git a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-test.js.es6 b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-test.js.es6 index 1eacb71..b6a5ba5 100644 --- a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-test.js.es6 +++ b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-test.js.es6 @@ -3,11 +3,11 @@ import { ajax } from 'discourse/lib/ajax'; export default Ember.Controller.extend(ModalFunctionality, { sendDisabled: function(){ - if(this.get('model').topic_id && this.get('model').channel){ + if(this.get('model').topic_id){ return false } return true - }.property('model.topic_id', 'model.channel'), + }.property('model.topic_id'), actions: { @@ -15,8 +15,7 @@ export default Ember.Controller.extend(ModalFunctionality, { self = this; this.set('loading', true); ajax("/admin/plugins/chat/test", { - data: { provider: this.get('model.provider.name'), - channel: this.get('model.channel'), + data: { channel_id: this.get('model.channel.id'), topic_id: this.get('model.topic_id') }, type: 'POST' diff --git a/assets/javascripts/admin/models/channel.js.es6 b/assets/javascripts/admin/models/channel.js.es6 new file mode 100644 index 0000000..b21399c --- /dev/null +++ b/assets/javascripts/admin/models/channel.js.es6 @@ -0,0 +1,14 @@ +import RestModel from 'discourse/models/rest'; + +export default RestModel.extend({ + + updateProperties() { + var prop_names = ['data']; + return this.getProperties(prop_names); + }, + + createProperties() { + var prop_names = ['provider','data']; + return this.getProperties(prop_names); + } +}); diff --git a/assets/javascripts/admin/models/rule.js.es6 b/assets/javascripts/admin/models/rule.js.es6 index 2ee1523..44fe2c8 100644 --- a/assets/javascripts/admin/models/rule.js.es6 +++ b/assets/javascripts/admin/models/rule.js.es6 @@ -11,9 +11,8 @@ export default RestModel.extend({ category_id: null, tags: null, - provider: '', - channel: '', - filter: null, + channel_id: null, + filter: 'watch', error_key: null, @computed('category_id') @@ -31,12 +30,13 @@ export default RestModel.extend({ }, updateProperties() { - var prop_names = ['category_id','provider','channel', 'tags','filter']; + var prop_names = ['category_id','tags','filter']; return this.getProperties(prop_names); }, createProperties() { - return this.updateProperties(); + var prop_names = ['channel_id', 'category_id','tags','filter']; + return this.getProperties(prop_names); } }); diff --git a/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 b/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 index 07b7b0a..7889816 100644 --- a/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 +++ b/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 @@ -1,12 +1,20 @@ -import Rule from 'discourse/plugins/discourse-chat-integration/admin/models/rule' import { ajax } from 'discourse/lib/ajax'; export default Discourse.Route.extend({ model(params, transition) { return Ember.RSVP.hash({ - rules: this.store.find('rule', {provider: params.provider}), + channels: this.store.findAll('channel', {provider: params.provider}), provider: this.modelFor("admin-plugins-chat").findBy('id',params.provider) + }).then(value => { + value.channels.forEach(channel => { + channel.set('rules', channel.rules.map(rule => { + rule = this.store.createRecord('rule', rule); + rule.channel = channel; + return rule; + })); + }); + return value; }); }, diff --git a/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-channel.hbs b/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-channel.hbs new file mode 100644 index 0000000..9c3399d --- /dev/null +++ b/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-channel.hbs @@ -0,0 +1,49 @@ +{{#d-modal-body id="chat_integration_edit_channel_modal" title="chat_integration.edit_channel_modal.title"}} +