From bfb499d4cf4b1f097af31a9acb270587e5f49802 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 13 Jul 2017 13:32:11 +0100 Subject: [PATCH] Refactor into /app directory, move everything out of plugin.rb --- .travis.yml | 4 +- app/controllers/chat_controller.rb | 104 ++++++++++ {lib/discourse_chat => app/helpers}/helper.rb | 0 app/initializers/discourse_chat.rb | 24 +++ app/jobs/regular/notify_chats.rb | 10 + app/models/channel.rb | 7 + app/models/plugin_model.rb | 35 ++++ {lib/discourse_chat => app/models}/rule.rb | 36 ---- app/routes/discourse.rb | 4 + app/routes/discourse_chat.rb | 16 ++ app/serializers/rule_serializer.rb | 3 + .../services}/manager.rb | 0 .../slack/slack_enabled_setting_validator.rb} | 0 plugin.rb | 187 ++---------------- 14 files changed, 223 insertions(+), 207 deletions(-) create mode 100644 app/controllers/chat_controller.rb rename {lib/discourse_chat => app/helpers}/helper.rb (100%) create mode 100644 app/initializers/discourse_chat.rb create mode 100644 app/jobs/regular/notify_chats.rb create mode 100644 app/models/channel.rb create mode 100644 app/models/plugin_model.rb rename {lib/discourse_chat => app/models}/rule.rb (69%) create mode 100644 app/routes/discourse.rb create mode 100644 app/routes/discourse_chat.rb create mode 100644 app/serializers/rule_serializer.rb rename {lib/discourse_chat => app/services}/manager.rb (100%) rename lib/{validators/chat_integration_slack_enabled_setting_validator.rb => discourse_chat/provider/slack/slack_enabled_setting_validator.rb} (100%) diff --git a/.travis.yml b/.travis.yml index bd68d43..f6bcf5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,11 @@ services: - docker before_install: - - plugin_name=${PWD##*/} && echo $plugin_name # Get the plugin's name + - plugin_name=${PWD##*/} && echo $plugin_name script: - > - docker run + docker run -e "COMMIT_HASH=origin/tests-passed" -e "LOAD_PLUGINS=1" -e SINGLE_PLUGIN=$plugin_name diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb new file mode 100644 index 0000000..21e3cd1 --- /dev/null +++ b/app/controllers/chat_controller.rb @@ -0,0 +1,104 @@ +class DiscourseChat::ChatController < ApplicationController + requires_plugin DiscourseChat::PLUGIN_NAME + + def respond + render + end + + def list_providers + providers = ::DiscourseChat::Provider.enabled_providers.map {|x| { + name: x::PROVIDER_NAME, + id: x::PROVIDER_NAME, + channel_regex: (defined? x::PROVIDER_CHANNEL_REGEX) ? x::PROVIDER_CHANNEL_REGEX : nil + }} + + render json:providers, root: 'providers' + end + + def test_provider + begin + requested_provider = params[:provider] + channel = params[:channel] + topic_id = params[:topic_id] + + provider = ::DiscourseChat::Provider.get_by_name(requested_provider) + + if provider.nil? or 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) + + render json:success_json + rescue Discourse::InvalidParameters, ActiveRecord::RecordNotFound => e + render json: {errors: [e.message]}, status: 422 + rescue DiscourseChat::ProviderError => e + if e.info.key?(:error_key) and !e.info[:error_key].nil? + render json: {error_key: e.info[:error_key]}, status: 422 + else + render json: {errors: [e.message]}, status: 422 + end + end + 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 + raise Discourse::NotFound + end + + render_serialized rules, DiscourseChat::RuleSerializer, root: 'rules' + end + + def create_rule + begin + hash = params.require(:rule).permit(:provider, :channel, :filter, :category_id, tags:[]) + + rule = DiscourseChat::Rule.new(hash) + + if not rule.save(hash) + raise Discourse::InvalidParameters, 'Rule is not valid' + end + + render_serialized rule, DiscourseChat::RuleSerializer, root: 'rule' + rescue Discourse::InvalidParameters => e + render json: {errors: [e.message]}, status: 422 + end + end + + def update_rule + 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:[]) + + if not rule.update(hash) + raise Discourse::InvalidParameters, 'Rule is not valid' + end + + render_serialized rule, DiscourseChat::RuleSerializer, root: 'rule' + rescue Discourse::InvalidParameters => e + render json: {errors: [e.message]}, status: 422 + end + end + + def destroy_rule + rule = DiscourseChat::Rule.find(params[:id].to_i) + + rule.destroy + + render json: success_json + end +end \ No newline at end of file diff --git a/lib/discourse_chat/helper.rb b/app/helpers/helper.rb similarity index 100% rename from lib/discourse_chat/helper.rb rename to app/helpers/helper.rb diff --git a/app/initializers/discourse_chat.rb b/app/initializers/discourse_chat.rb new file mode 100644 index 0000000..4c77bed --- /dev/null +++ b/app/initializers/discourse_chat.rb @@ -0,0 +1,24 @@ +module ::DiscourseChat + PLUGIN_NAME = "discourse-chat-integration".freeze + + class AdminEngine < ::Rails::Engine + engine_name DiscourseChat::PLUGIN_NAME+"-admin" + isolate_namespace DiscourseChat + end + + def self.plugin_name + DiscourseChat::PLUGIN_NAME + end + + def self.pstore_get(key) + PluginStore.get(self.plugin_name, key) + end + + def self.pstore_set(key, value) + PluginStore.set(self.plugin_name, key, value) + end + + def self.pstore_delete(key) + PluginStore.remove(self.plugin_name, key) + end +end \ No newline at end of file diff --git a/app/jobs/regular/notify_chats.rb b/app/jobs/regular/notify_chats.rb new file mode 100644 index 0000000..7d76448 --- /dev/null +++ b/app/jobs/regular/notify_chats.rb @@ -0,0 +1,10 @@ +module Jobs + class NotifyChats < Jobs::Base + sidekiq_options retry: false # Don't retry, could result in duplicate notifications for some providers + def execute(args) + return if not SiteSetting.chat_integration_enabled? # Plugin may have been disabled since job triggered + + ::DiscourseChat::Manager.trigger_notifications(args[:post_id]) + end + end +end \ No newline at end of file diff --git a/app/models/channel.rb b/app/models/channel.rb new file mode 100644 index 0000000..5077a77 --- /dev/null +++ b/app/models/channel.rb @@ -0,0 +1,7 @@ +class DiscourseChat::Channel < DiscourseChat::PluginModel + KEY_PREFIX = 'channel:' + + # Setup ActiveRecord::Store to use the JSON field to read/write these values + store :value, accessors: [ :name ], coder: JSON + +end \ No newline at end of file diff --git a/app/models/plugin_model.rb b/app/models/plugin_model.rb new file mode 100644 index 0000000..c9af6a0 --- /dev/null +++ b/app/models/plugin_model.rb @@ -0,0 +1,35 @@ +class DiscourseChat::PluginModel < PluginStoreRow + PLUGIN_NAME = 'discourse-chat-integration' + KEY_PREFIX = 'unimplemented' + + after_initialize :init_plugin_model + + def init_plugin_model + self.type_name ||= 'JSON' + self.plugin_name ||= PLUGIN_NAME + end + + # Restrict the scope to JSON PluginStoreRows which are for this plugin, and this model + def self.default_scope + where(type_name: 'JSON') + .where(plugin_name: self::PLUGIN_NAME) + .where("key like?", "#{self::KEY_PREFIX}%") + end + + before_save :set_key + private + def set_key + self.key ||= self.class.alloc_key + end + + def self.alloc_key + raise "KEY_PREFIX must be defined" if self::KEY_PREFIX == 'unimplemented' + DistributedMutex.synchronize("#{self::PLUGIN_NAME}_#{self::KEY_PREFIX}_id") do + max_id = PluginStore.get(self::PLUGIN_NAME, "#{self::KEY_PREFIX}_id") + max_id = 1 unless max_id + PluginStore.set(self::PLUGIN_NAME, "#{self::KEY_PREFIX}_id", max_id + 1) + "#{self::KEY_PREFIX}#{max_id}" + end + end + +end diff --git a/lib/discourse_chat/rule.rb b/app/models/rule.rb similarity index 69% rename from lib/discourse_chat/rule.rb rename to app/models/rule.rb index 6ff07e3..b2f3707 100644 --- a/lib/discourse_chat/rule.rb +++ b/app/models/rule.rb @@ -1,39 +1,3 @@ -class DiscourseChat::PluginModel < PluginStoreRow - PLUGIN_NAME = 'discourse-chat-integration' - KEY_PREFIX = 'unimplemented' - - after_initialize :init_plugin_model - - def init_plugin_model - self.type_name ||= 'JSON' - self.plugin_name ||= PLUGIN_NAME - end - - # Restrict the scope to JSON PluginStoreRows which are for this plugin, and this model - def self.default_scope - where(type_name: 'JSON') - .where(plugin_name: self::PLUGIN_NAME) - .where("key like?", "#{self::KEY_PREFIX}%") - end - - before_save :set_key - private - def set_key - self.key ||= self.class.alloc_key - end - - def self.alloc_key - raise "KEY_PREFIX must be defined" if self::KEY_PREFIX == 'unimplemented' - DistributedMutex.synchronize("#{self::PLUGIN_NAME}_#{self::KEY_PREFIX}_id") do - max_id = PluginStore.get(self::PLUGIN_NAME, "#{self::KEY_PREFIX}_id") - max_id = 1 unless max_id - PluginStore.set(self::PLUGIN_NAME, "#{self::KEY_PREFIX}_id", max_id + 1) - "#{self::KEY_PREFIX}#{max_id}" - end - end - -end - class DiscourseChat::Rule < DiscourseChat::PluginModel KEY_PREFIX = 'rule:' diff --git a/app/routes/discourse.rb b/app/routes/discourse.rb new file mode 100644 index 0000000..01b0685 --- /dev/null +++ b/app/routes/discourse.rb @@ -0,0 +1,4 @@ +Discourse::Application.routes.append do + mount ::DiscourseChat::AdminEngine, at: '/admin/plugins/chat', constraints: AdminConstraint.new + mount ::DiscourseChat::Provider::HookEngine, at: '/chat-integration/' +end \ No newline at end of file diff --git a/app/routes/discourse_chat.rb b/app/routes/discourse_chat.rb new file mode 100644 index 0000000..207b4e1 --- /dev/null +++ b/app/routes/discourse_chat.rb @@ -0,0 +1,16 @@ +require_dependency 'admin_constraint' + +module DiscourseChat + AdminEngine.routes.draw do + get "" => "chat#respond" + get '/providers' => "chat#list_providers" + post '/test' => "chat#test_provider" + + get '/rules' => "chat#list_rules" + put '/rules' => "chat#create_rule" + put '/rules/:id' => "chat#update_rule" + delete '/rules/:id' => "chat#destroy_rule" + + get "/:provider" => "chat#respond" + end +end \ No newline at end of file diff --git a/app/serializers/rule_serializer.rb b/app/serializers/rule_serializer.rb new file mode 100644 index 0000000..7ff023b --- /dev/null +++ b/app/serializers/rule_serializer.rb @@ -0,0 +1,3 @@ +class DiscourseChat::RuleSerializer < ActiveModel::Serializer + attributes :id, :provider, :channel, :category_id, :tags, :filter, :error_key +end \ No newline at end of file diff --git a/lib/discourse_chat/manager.rb b/app/services/manager.rb similarity index 100% rename from lib/discourse_chat/manager.rb rename to app/services/manager.rb diff --git a/lib/validators/chat_integration_slack_enabled_setting_validator.rb b/lib/discourse_chat/provider/slack/slack_enabled_setting_validator.rb similarity index 100% rename from lib/validators/chat_integration_slack_enabled_setting_validator.rb rename to lib/discourse_chat/provider/slack/slack_enabled_setting_validator.rb diff --git a/plugin.rb b/plugin.rb index fbbe0e6..19f6225 100644 --- a/plugin.rb +++ b/plugin.rb @@ -2,199 +2,48 @@ # about: This plugin integrates discourse with a number of chat providers # version: 0.1 # url: https://github.com/discourse/discourse-chat-integration +# author: David Taylor enabled_site_setting :chat_integration_enabled register_asset "stylesheets/chat-integration-admin.scss" # Site setting validators must be loaded before initialize -require_relative "lib/validators/chat_integration_slack_enabled_setting_validator" +require_relative "lib/discourse_chat/provider/slack/slack_enabled_setting_validator" after_initialize do - module ::DiscourseChat - PLUGIN_NAME = "discourse-chat-integration".freeze + require_relative "app/initializers/discourse_chat" - class AdminEngine < ::Rails::Engine - engine_name DiscourseChat::PLUGIN_NAME+"-admin" - isolate_namespace DiscourseChat - end + require_relative "app/models/plugin_model" + require_relative "app/models/rule" + require_relative "app/models/channel" - def self.plugin_name - DiscourseChat::PLUGIN_NAME - end + require_relative "app/serializers/rule_serializer" + + require_relative "app/controllers/chat_controller" - def self.pstore_get(key) - PluginStore.get(self.plugin_name, key) - end + require_relative "app/routes/discourse_chat" + require_relative "app/routes/discourse" - def self.pstore_set(key, value) - PluginStore.set(self.plugin_name, key, value) - end + require_relative "app/helpers/helper" + + require_relative "app/services/manager" - def self.pstore_delete(key) - PluginStore.remove(self.plugin_name, key) - end - end + require_relative "app/jobs/regular/notify_chats" require_relative "lib/discourse_chat/provider" - require_relative "lib/discourse_chat/manager" - require_relative "lib/discourse_chat/rule" - require_relative "lib/discourse_chat/helper" - - module ::Jobs - class NotifyChats < Jobs::Base - sidekiq_options retry: false # Don't retry, could result in duplicate notifications for some providers - def execute(args) - return if not SiteSetting.chat_integration_enabled? # Plugin may have been disabled since job triggered - - ::DiscourseChat::Manager.trigger_notifications(args[:post_id]) - end - end - end - + DiscourseEvent.on(:post_created) do |post| if SiteSetting.chat_integration_enabled? # This will run for every post, even PMs. Don't worry, they're filtered out later. - Jobs.enqueue_in(SiteSetting.chat_integration_delay_seconds.seconds, - :notify_chats, - post_id: post.id - ) + time = SiteSetting.chat_integration_delay_seconds.seconds + Jobs.enqueue_in(time, :notify_chats, post_id: post.id) end end - class ::DiscourseChat::ChatController < ::ApplicationController - requires_plugin DiscourseChat::PLUGIN_NAME - - def respond - render - end - - def list_providers - providers = ::DiscourseChat::Provider.enabled_providers.map {|x| { - name: x::PROVIDER_NAME, - id: x::PROVIDER_NAME, - channel_regex: (defined? x::PROVIDER_CHANNEL_REGEX) ? x::PROVIDER_CHANNEL_REGEX : nil - }} - - render json:providers, root: 'providers' - end - - def test_provider - begin - requested_provider = params[:provider] - channel = params[:channel] - topic_id = params[:topic_id] - - provider = ::DiscourseChat::Provider.get_by_name(requested_provider) - - if provider.nil? or 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) - - render json:success_json - rescue Discourse::InvalidParameters, ActiveRecord::RecordNotFound => e - render json: {errors: [e.message]}, status: 422 - rescue DiscourseChat::ProviderError => e - if e.info.key?(:error_key) and !e.info[:error_key].nil? - render json: {error_key: e.info[:error_key]}, status: 422 - else - render json: {errors: [e.message]}, status: 422 - end - end - 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 - raise Discourse::NotFound - end - - render_serialized rules, DiscourseChat::RuleSerializer, root: 'rules' - end - - def create_rule - begin - hash = params.require(:rule).permit(:provider, :channel, :filter, :category_id, tags:[]) - - rule = DiscourseChat::Rule.new(hash) - - if not rule.save(hash) - raise Discourse::InvalidParameters, 'Rule is not valid' - end - - render_serialized rule, DiscourseChat::RuleSerializer, root: 'rule' - rescue Discourse::InvalidParameters => e - render json: {errors: [e.message]}, status: 422 - end - end - - def update_rule - 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:[]) - - if not rule.update(hash) - raise Discourse::InvalidParameters, 'Rule is not valid' - end - - render_serialized rule, DiscourseChat::RuleSerializer, root: 'rule' - rescue Discourse::InvalidParameters => e - render json: {errors: [e.message]}, status: 422 - end - end - - def destroy_rule - rule = DiscourseChat::Rule.find(params[:id].to_i) - - rule.destroy - - render json: success_json - end - end - - class DiscourseChat::RuleSerializer < ActiveModel::Serializer - attributes :id, :provider, :channel, :category_id, :tags, :filter, :error_key - end - - require_dependency 'admin_constraint' - - add_admin_route 'chat_integration.menu_title', 'chat' - DiscourseChat::AdminEngine.routes.draw do - get "" => "chat#respond" - get '/providers' => "chat#list_providers" - post '/test' => "chat#test_provider" - - get '/rules' => "chat#list_rules" - put '/rules' => "chat#create_rule" - put '/rules/:id' => "chat#update_rule" - delete '/rules/:id' => "chat#destroy_rule" - - get "/:provider" => "chat#respond" - end - - Discourse::Application.routes.append do - mount ::DiscourseChat::AdminEngine, at: '/admin/plugins/chat', constraints: AdminConstraint.new - mount ::DiscourseChat::Provider::HookEngine, at: '/chat-integration/' - end - DiscourseChat::Provider.mount_engines end