# frozen_string_literal: true module Noticent def self.configure(options = {}, &block) if ENV["NOTICENT_RSPEC"] == "1" options = options.merge( base_module_name: "Noticent::Testing", base_dir: File.expand_path("#{File.dirname(__FILE__)}/../../testing"), halt_on_error: true, ) end @config = Noticent::Config::Builder.new(options, &block).build @config.validate! # construct dynamics @config.create_dynamics @config end def self.configuration @config || (raise Noticent::MissingConfiguration) end def self.notify(alert_name, payload) engine = Noticent::Dispatcher.new(@config, alert_name, payload) return if engine.notifiers.nil? engine.dispatch end # recipient is the recipient object id # entities is an array of all entity ids this recipient needs to opt in based on the alert defaults # scope is the name of the scope these entities belong to def self.setup_recipient(recipient_id:, scope:, entity_ids:) raise ArgumentError, "no scope named '#{scope}' found" if @config.scopes[scope].nil? alerts = @config.alerts_by_scope(scope) alerts.each do |alert| channels = @config.alert_channels(alert.name) channels.each do |channel| next unless alert.default_for(channel.name) entity_ids.each do |entity_id| @config.opt_in_provider.opt_in(recipient_id: recipient_id, scope: scope, entity_id: entity_id, alert_name: alert.name, channel_name: channel.name) end end end end class Config attr_reader :hooks attr_reader :channels attr_reader :scopes attr_reader :alerts attr_reader :products attr_reader :channel_groups def initialize(options = {}) @options = options @products = {} end def channels_by_group(group) return [] if @channels.nil? @channels.values.select { |x| x.group == group } end def alert_channels(alert_name) alert = @alerts[alert_name] raise ArgumentError, "no alert #{alert_name} found" if alert.nil? return [] if alert.notifiers.nil? alert.notifiers.values.collect { |notifier| channels_by_group(notifier.channel_group).uniq }.uniq.flatten end def products_by_alert(alert_name) alert = @alerts[alert_name] raise ArgumentError, "no alert #{alert_name} found" if alert.nil? alert.products end def alerts_by_scope(scope) return [] if @alerts.nil? @alerts.values.select { |x| x.scope.name == scope } end def base_dir @options[:base_dir] end def base_module_name @options[:base_module_name] end def opt_in_provider @options[:opt_in_provider] || Noticent::ActiveRecordOptInProvider.new end def logger @options[:logger] || Logger.new(STDOUT) end def halt_on_error @options[:halt_on_error].nil? ? false : @options[:halt_on_error] end def skip_alert_with_no_subscribers @options[:skip_alert_with_no_subscribers].nil? ? false : @options[:skip_alert_with_no_subscribers] end def default_value @options[:default_value].nil? ? false : @options[:default_value] end def use_sub_modules @options[:use_sub_modules].nil? ? false : @options[:use_sub_modules] end def payload_dir File.join(base_dir, "payloads") end def scope_dir File.join(base_dir, "scopes") end def channel_dir File.join(base_dir, "channels") end def view_dir File.join(base_dir, "views") end def create_dynamics return if alerts.nil? alerts.keys.each do |alert| const_name = "ALERT_#{alert.to_s.upcase}" next if Noticent.const_defined?(const_name) Noticent.const_set(const_name, alert) end end def validate! # check all scopes scopes&.values&.each(&:validate!) alerts&.values&.each(&:validate!) end class Builder def initialize(options = {}, &block) @options = options @config = Noticent::Config.new(options) raise BadConfiguration, "no OptInProvider configured" if @config.opt_in_provider.nil? instance_eval(&block) if block_given? @config.instance_variable_set(:@options, @options) end def build @config end def base_dir=(value) @options[:base_dir] = value end def base_module_name=(value) @options[:base_module_name] = value end def opt_in_provider=(value) @options[:opt_in_provider] = value end def logger=(value) @options[:logger] = value end def halt_on_error=(value) @options[:halt_on_error] = value end def skip_alert_with_no_subscribers=(value) @options[:skip_alert_with_no_subscribers] = value end def use_sub_modules=(value) @options[:use_sub_modules] = value end def hooks if @config.hooks.nil? @config.instance_variable_set(:@hooks, Noticent::Definitions::Hooks.new) else @config.hooks end end def product(name, &block) products = @config.instance_variable_get(:@products) || {} raise BadConfiguration, "product #{name} already defined" if products[name] product = Noticent::Definitions::Product.new(@config, name) hooks.run(:pre_product_registration, product) product.instance_eval(&block) if block_given? hooks.run(:post_product_registration, product) products[name] = product @config.instance_variable_set(:@products, products) product end def channel(name, group: :default, klass: nil, &block) channels = @config.instance_variable_get(:@channels) || {} channel_groups = @config.instance_variable_get(:@channel_groups) || [] raise BadConfiguration, "channel '#{name}' already defined" if channels.include? name raise BadConfiguration, "a channel group named '#{group}' already exists. channels and channel groups cannot have duplicates" if channel_groups.include? name channel_groups << group channel = Noticent::Definitions::Channel.new(@config, name, group: group, klass: klass) hooks.run(:pre_channel_registration, channel) channel.instance_eval(&block) if block_given? hooks.run(:post_channel_registration, channel) channels[name] = channel @config.instance_variable_set(:@channels, channels) @config.instance_variable_set(:@channel_groups, channel_groups.uniq) channel end def scope(name, payload_class: nil, check_constructor: true, &block) scopes = @config.instance_variable_get(:@scopes) || {} raise BadConfiguration, "scope '#{name}' already defined" if scopes.include? name scope = Noticent::Definitions::Scope.new(@config, name, payload_class: payload_class, check_constructor: check_constructor) scope.instance_eval(&block) scopes[name] = scope @config.instance_variable_set(:@scopes, scopes) scope end end end end