bmc 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/README.md +28 -0
- data/Rakefile +41 -0
- data/app/assets/config/bmc_manifest.js +2 -0
- data/app/controllers/bmc/application_controller.rb +3 -0
- data/app/controllers/bmc/filters_controller.rb +38 -0
- data/app/controllers/concerns/bmc/api_controller_concern.rb +69 -0
- data/app/controllers/concerns/bmc/back_url_concern.rb +26 -0
- data/app/emails/bmc/email.rb +113 -0
- data/app/filters/bmc/filter/by_date.rb +6 -0
- data/app/filters/bmc/filter/by_date_begin.rb +7 -0
- data/app/filters/bmc/filter/by_date_end.rb +7 -0
- data/app/filters/bmc/filter/by_date_or_datetime_period.rb +56 -0
- data/app/filters/bmc/filter/by_date_period.rb +5 -0
- data/app/filters/bmc/filter/by_datetime_period.rb +5 -0
- data/app/filters/bmc/filter/by_key_value.rb +27 -0
- data/app/filters/bmc/filter/by_key_values.rb +13 -0
- data/app/filters/bmc/filter.rb +86 -0
- data/app/forms/bmc/mini_form_object.rb +29 -0
- data/app/helpers/bmc/all_helpers.rb +15 -0
- data/app/helpers/bmc/bootstrap_helper.rb +63 -0
- data/app/helpers/bmc/button_helper.rb +150 -0
- data/app/helpers/bmc/filters_helper.rb +16 -0
- data/app/helpers/bmc/font_awesome_helper.rb +67 -0
- data/app/helpers/bmc/form_helper.rb +24 -0
- data/app/helpers/bmc/i18n_helper.rb +5 -0
- data/app/helpers/bmc/link_helper.rb +42 -0
- data/app/helpers/bmc/pagination_helper.rb +22 -0
- data/app/helpers/bmc/routes_helper.rb +24 -0
- data/app/helpers/bmc/sorting_helper.rb +55 -0
- data/app/helpers/bmc/text_helper.rb +127 -0
- data/app/helpers/h.rb +3 -0
- data/app/jobs/concerns/bmc/setup_job_concern.rb +35 -0
- data/app/libs/bmc/collection_update.rb +38 -0
- data/app/libs/bmc/fcm/notifier.rb +32 -0
- data/app/libs/bmc/fcm/request.rb +54 -0
- data/app/libs/bmc/mini_model_serializer/serialize.rb +30 -0
- data/app/libs/bmc/mini_model_serializer/serializer.rb +23 -0
- data/app/libs/bmc/monkey.rb +39 -0
- data/app/libs/bmc/sortable_uuid_generator.rb +11 -0
- data/app/libs/bmc/token_generator.rb +37 -0
- data/app/mailers/bmc/application_mailer.rb +2 -0
- data/app/mailers/bmc/generic_mailer.rb +9 -0
- data/app/models/concerns/bmc/active_record_uuid_concern.rb +17 -0
- data/app/models/concerns/bmc/default_values_concern.rb +14 -0
- data/app/models/concerns/bmc/model_i18n.rb +56 -0
- data/app/models/concerns/bmc/model_to_s.rb +9 -0
- data/app/models/concerns/bmc/pluck_distinct.rb +13 -0
- data/app/models/concerns/bmc/pluck_to_hash.rb +9 -0
- data/app/models/concerns/bmc/polymorphic_id.rb +37 -0
- data/app/models/concerns/bmc/search.rb +28 -0
- data/app/models/concerns/bmc/timestamp_helpers.rb +17 -0
- data/app/serializers/bmc/serializers/base.rb +63 -0
- data/app/serializers/bmc/serializers/xlsx.rb +38 -0
- data/app/serializers/bmc/serializers.rb +2 -0
- data/app/sms/bmc/sms/application_sms.rb +42 -0
- data/app/sms/bmc/sms/message.rb +25 -0
- data/app/sms/bmc/sms/strategies/amazon_sns.rb +48 -0
- data/app/sms/bmc/sms/strategies/base.rb +15 -0
- data/app/sms/bmc/sms/strategies/test.rb +10 -0
- data/app/sms/bmc/sms.rb +35 -0
- data/app/sorters/bmc/sorter.rb +26 -0
- data/app/views/bmc/search/_form.html.slim +1 -0
- data/app/views/bmc/search/_form_bs3.html.slim +13 -0
- data/app/views/bmc/search/_form_bs4.html.slim +9 -0
- data/app/views/bmc/search/_form_bs5.html.slim +8 -0
- data/app/views/kaminari/bootstrap4/_first_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_gap.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_last_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_next_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_page.html.slim +6 -0
- data/app/views/kaminari/bootstrap4/_paginator.html.slim +12 -0
- data/app/views/kaminari/bootstrap4/_prev_page.html.slim +2 -0
- data/config/locales/actions.en.yml +76 -0
- data/config/locales/actions.fr.yml +76 -0
- data/config/locales/attributes.en.yml +108 -0
- data/config/locales/attributes.fr.yml +108 -0
- data/config/locales/common.en.yml +39 -0
- data/config/locales/common.fr.yml +39 -0
- data/config/locales/dates.fr.yml +8 -0
- data/config/locales/errors.en.yml +5 -0
- data/config/locales/errors.fr.yml +5 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20000101000000_enable_bmc_extensions.rb +7 -0
- data/lib/bmc/active_model_custom_error_messages.rb +23 -0
- data/lib/bmc/active_model_type_cast.rb +52 -0
- data/lib/bmc/config.rb +19 -0
- data/lib/bmc/core_and_rails_ext.rb +3 -0
- data/lib/bmc/engine.rb +36 -0
- data/lib/bmc/engine_file.rb +10 -0
- data/lib/bmc/errors_middleware.rb +50 -0
- data/lib/bmc/form_back_url.rb +18 -0
- data/lib/bmc/version.rb +3 -0
- data/lib/bmc.rb +5 -0
- data/lib/tasks/bmc_tasks.rake +4 -0
- metadata +236 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
class BMC::MiniModelSerializer::Serialize
|
2
|
+
attr_reader :object, :options
|
3
|
+
|
4
|
+
def initialize(object, options = {})
|
5
|
+
@object = object
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
if object.is_a?(Hash)
|
11
|
+
object.map { |k, v| [k.to_s, serialize(v)] }.to_h
|
12
|
+
elsif object.is_a?(Enumerable)
|
13
|
+
object.map { |e| serialize(e) }
|
14
|
+
elsif (serializer = "#{object.class}Serializer".safe_constantize)
|
15
|
+
serializer.call(object, options)
|
16
|
+
else
|
17
|
+
object.as_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.call(...)
|
22
|
+
new(...).call
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def serialize(object)
|
28
|
+
BMC::MiniModelSerializer::Serialize.call(object, options)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class BMC::MiniModelSerializer::Serializer < BMC::MiniModelSerializer::Serialize
|
2
|
+
def attributes
|
3
|
+
raise NotImplementedError
|
4
|
+
end
|
5
|
+
|
6
|
+
def call
|
7
|
+
serialize attributes.map { |k| [k, send(k)] }.to_h
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def method_missing(m, *args)
|
13
|
+
if respond_to_missing?(m)
|
14
|
+
object.send(m, *args)
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(m, *)
|
21
|
+
super || object.respond_to?(m, true)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module BMC::Monkey
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def prepend_instances(&block)
|
6
|
+
m = Module.new(&block)
|
7
|
+
send(:prepend, m)
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepend_class(&block)
|
11
|
+
m = Module.new(&block)
|
12
|
+
singleton_class.send(:prepend, m)
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepend_instance_method(name, &block)
|
16
|
+
check_instance_method_exist!(name)
|
17
|
+
|
18
|
+
m = Module.new
|
19
|
+
m.send(:define_method, name, &block)
|
20
|
+
send(:prepend, m)
|
21
|
+
end
|
22
|
+
|
23
|
+
def prepend_class_method(name, &block)
|
24
|
+
check_class_method_exist!(name)
|
25
|
+
|
26
|
+
m = Module.new
|
27
|
+
m.send(:define_method, name, &block)
|
28
|
+
singleton_class.send(:prepend, m)
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_instance_method_exist!(name)
|
32
|
+
raise "instance method `#{name}` does not exist" unless instance_methods.include?(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_class_method_exist!(name)
|
36
|
+
raise "class method `#{name}` does not exist" unless methods.include?(name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Microtime based uuids to be sortable
|
2
|
+
class BMC::SortableUUIDGenerator
|
3
|
+
REGEX_WITH_DASHES = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/
|
4
|
+
REGEX_WITHOUT_DASHES = /^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/
|
5
|
+
|
6
|
+
def self.call
|
7
|
+
prefix = Time.zone.now.strftime("%s%9N").to_i.to_s(16)
|
8
|
+
suffix = SecureRandom.hex(8)
|
9
|
+
(prefix + suffix).gsub(REGEX_WITHOUT_DASHES, '\1-\2-\3-\4-\5')
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class BMC::TokenGenerator
|
2
|
+
attr_reader :options
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def size
|
9
|
+
options[:size] || self.class.default_size
|
10
|
+
end
|
11
|
+
|
12
|
+
def alphabet
|
13
|
+
options[:alphabet] || self.class.default_alphabet
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
(alphabet * size).sample(size).join
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def call(*args)
|
22
|
+
new(*args).call
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_writer :default_size
|
26
|
+
|
27
|
+
def default_size
|
28
|
+
@default_size ||= 64
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_writer :default_alphabet
|
32
|
+
|
33
|
+
def default_alphabet
|
34
|
+
@default_alphabet ||= (0..9).to_a + ("a".."z").to_a + ("A".."Z").to_a
|
35
|
+
end
|
36
|
+
end # class << self
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BMC::ActiveRecordUUIDConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def assign_default_uuid
|
7
|
+
unless self.class.columns_hash["id"].type == :uuid
|
8
|
+
raise "invalid id type, please change to uuid"
|
9
|
+
end
|
10
|
+
|
11
|
+
self.id ||= BMC::SortableUUIDGenerator.call
|
12
|
+
end
|
13
|
+
|
14
|
+
included do
|
15
|
+
before_save :assign_default_uuid
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module BMC::DefaultValuesConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def assign_default_values
|
5
|
+
end
|
6
|
+
|
7
|
+
def assign_default(attribute, value)
|
8
|
+
send("#{attribute}=", value) if send(attribute).nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
included do
|
12
|
+
after_initialize :assign_default_values, if: :new_record?
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module BMC::ModelI18n
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
MissingTranslationError = Class.new(StandardError)
|
5
|
+
class << self
|
6
|
+
attr_accessor :raise_on_missing_translations
|
7
|
+
end
|
8
|
+
self.raise_on_missing_translations = false
|
9
|
+
|
10
|
+
def t(*args)
|
11
|
+
self.class.t(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ts
|
15
|
+
self.class.ts
|
16
|
+
end
|
17
|
+
|
18
|
+
def tv(attribute)
|
19
|
+
value = public_send(attribute)
|
20
|
+
t("#{attribute}.#{value}") if value.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
class_methods do
|
24
|
+
def t(attribute = nil, options = {})
|
25
|
+
should_raise = BMC::ModelI18n.raise_on_missing_translations
|
26
|
+
exception_class = BMC::ModelI18n::MissingTranslationError
|
27
|
+
|
28
|
+
if should_raise
|
29
|
+
options = options.merge(default: "")
|
30
|
+
end
|
31
|
+
|
32
|
+
if should_raise && attribute.nil?
|
33
|
+
human = model_name.human(options)
|
34
|
+
type = options[:count].to_i > 1 ? "plural" : "singular"
|
35
|
+
raise exception_class, "translation missing: #{self} #{type} model name" if human.blank?
|
36
|
+
return human
|
37
|
+
end
|
38
|
+
|
39
|
+
if should_raise && attribute
|
40
|
+
human = human_attribute_name(attribute, options)
|
41
|
+
raise exception_class, "translation missing: #{self}##{attribute}" if human.blank?
|
42
|
+
return human
|
43
|
+
end
|
44
|
+
|
45
|
+
if attribute.nil?
|
46
|
+
return model_name.human(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
human_attribute_name(attribute, options) if attribute
|
50
|
+
end
|
51
|
+
|
52
|
+
def ts
|
53
|
+
t(nil, count: 2)
|
54
|
+
end
|
55
|
+
end # class_methods
|
56
|
+
end # BMC::ModelI18n
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BMC::PolymorphicId
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
def self.polymorphic_id_for(relation_name)
|
6
|
+
module_src = File.read(__FILE__).split("__END__").last
|
7
|
+
module_src = module_src.gsub("relation", relation_name.to_s)
|
8
|
+
send :include, eval(module_src)
|
9
|
+
end
|
10
|
+
|
11
|
+
def guid
|
12
|
+
return nil if new_record?
|
13
|
+
|
14
|
+
"#{self.class.base_class}-#{id}"
|
15
|
+
end
|
16
|
+
end # included
|
17
|
+
end # module
|
18
|
+
|
19
|
+
__END__
|
20
|
+
|
21
|
+
Module.new do
|
22
|
+
def relation_guid
|
23
|
+
return nil if relation_type.blank? || relation_id.blank?
|
24
|
+
|
25
|
+
"#{relation_type}-#{relation_id}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def relation_guid=(guid)
|
29
|
+
if guid.blank?
|
30
|
+
self.relation = nil
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
type, id = guid.split("-", 2)
|
35
|
+
self.relation = type.constantize.find(id)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BMC::Search
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def default_search_fields
|
6
|
+
columns
|
7
|
+
.select { |column| column.type.in?([:string, :text]) }
|
8
|
+
.map { |column| "#{table_name}.#{column.name}" }
|
9
|
+
end # def default_search_fields
|
10
|
+
|
11
|
+
def search(q, *fields)
|
12
|
+
words = q.to_s.parameterize.split("-")
|
13
|
+
fields = default_search_fields if fields.empty?
|
14
|
+
|
15
|
+
return all if words.empty?
|
16
|
+
|
17
|
+
sql_query = words.map.with_index { |_word, index|
|
18
|
+
fields.map { |field|
|
19
|
+
"(UNACCENT(CAST(#{field} AS TEXT)) ILIKE :w#{index})"
|
20
|
+
}.join(" OR ")
|
21
|
+
}.map { |e| "(#{e})" }.join(" AND ")
|
22
|
+
|
23
|
+
sql_params = words.map.with_index { |word, index| ["w#{index}".to_sym, "%#{word}%"] }.to_h
|
24
|
+
|
25
|
+
where(sql_query, sql_params)
|
26
|
+
end # def search
|
27
|
+
end # class_methods
|
28
|
+
end # class BMC::Search
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BMC::TimestampHelpers
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def first_created
|
6
|
+
reorder(:created_at, :id).first
|
7
|
+
end
|
8
|
+
|
9
|
+
def last_created
|
10
|
+
reorder(:created_at, :id).last
|
11
|
+
end
|
12
|
+
|
13
|
+
def last_updated
|
14
|
+
reorder(:updated_at, :id).last
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class BMC::Serializers::Base
|
2
|
+
attr_reader :data, :options
|
3
|
+
|
4
|
+
def initialize(data, options = {})
|
5
|
+
@data = data
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def formatted_data
|
10
|
+
data.map do |line|
|
11
|
+
line.map { |value| self.class.format(value) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def render_inline
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_file(_file_path)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def decimals_precision
|
25
|
+
@decimals_precision ||= 2
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_writer :decimals_precision
|
29
|
+
|
30
|
+
def format_integer(value)
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_decimal(value)
|
35
|
+
value.to_f.round(decimals_precision) # Fix BigDecimal and 0.1 + 0.2
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_boolean(value)
|
39
|
+
I18n.t(value.to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
def format_date_or_time(value)
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_default(value)
|
47
|
+
value.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def formatter_for(value)
|
51
|
+
return :integer if value.is_a?(Integer)
|
52
|
+
return :decimal if value.is_a?(Numeric)
|
53
|
+
return :boolean if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
54
|
+
return :date_or_time if value.is_a?(Date) || value.is_a?(Time)
|
55
|
+
return :default
|
56
|
+
end
|
57
|
+
|
58
|
+
def format(value)
|
59
|
+
formatter = "format_" + formatter_for(value).to_s
|
60
|
+
public_send(formatter, value)
|
61
|
+
end
|
62
|
+
end # class << self
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class BMC::Serializers::XLSX < BMC::Serializers::Base
|
2
|
+
def render_inline
|
3
|
+
headers, *data = formatted_data
|
4
|
+
|
5
|
+
SpreadsheetArchitect.to_xlsx(
|
6
|
+
headers: headers,
|
7
|
+
data: data,
|
8
|
+
freeze_headers: true,
|
9
|
+
range_styles: range_styles(data[0]),
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_file(file_path)
|
14
|
+
File.open(file_path, "w+b") do |f|
|
15
|
+
f.write(render_inline)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def range_styles(row)
|
22
|
+
return [] if row.nil?
|
23
|
+
|
24
|
+
date_range_styles(row) + time_range_styles(row)
|
25
|
+
end
|
26
|
+
|
27
|
+
def date_range_styles(row)
|
28
|
+
row.each_index.select { row[_1].is_a?(Date) }.map do
|
29
|
+
{range: {rows: :all, columns: _1}, styles: {format_code: "dd/mm/yyyy"}}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def time_range_styles(row)
|
34
|
+
row.each_index.select { row[_1].is_a?(Time) }.map do
|
35
|
+
{range: {rows: :all, columns: _1}, styles: {format_code: "dd/mm/yyyy hh:mm:ss"}}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class BMC::SMS::ApplicationSMS
|
2
|
+
attr_reader :options
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def action_name
|
11
|
+
options[:action_name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def t(key, **args)
|
15
|
+
if key.start_with?(".")
|
16
|
+
path = self.class.to_s.underscore.tr("/", ".")
|
17
|
+
key = "#{path}.#{action_name}#{key}"
|
18
|
+
end
|
19
|
+
|
20
|
+
I18n.t(key, **args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sms(data)
|
24
|
+
BMC::SMS::Message.new(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
private :new
|
29
|
+
|
30
|
+
def method_missing(m, *args)
|
31
|
+
if respond_to_missing?(m)
|
32
|
+
new(action_name: m).public_send(m, *args)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond_to_missing?(m, *)
|
39
|
+
super || public_instance_methods.include?(m)
|
40
|
+
end
|
41
|
+
end # class << self
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class BMC::SMS::Message
|
2
|
+
attr_reader :data
|
3
|
+
|
4
|
+
def initialize(data)
|
5
|
+
@data = data
|
6
|
+
end
|
7
|
+
|
8
|
+
def strategy=(value)
|
9
|
+
@strategy = BMC::SMS.parse_strategy(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def strategy(value = :no_argument)
|
13
|
+
self.strategy = value unless value == :no_argument
|
14
|
+
@strategy || BMC::SMS.strategy
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver_now
|
18
|
+
strategy.call(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO : Delay
|
22
|
+
def deliver_later
|
23
|
+
deliver_now
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class BMC::SMS::Strategies::AmazonSNS < BMC::SMS::Strategies::Base
|
2
|
+
class << self
|
3
|
+
attr_writer :sns_region
|
4
|
+
|
5
|
+
def sns_region
|
6
|
+
@sns_region ||= (ENV["SNS_REGION"] || ENV["AWS_REGION"])
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_writer :sns_access_key_id
|
10
|
+
|
11
|
+
def sns_access_key_id
|
12
|
+
@sns_access_key_id ||= (ENV["SNS_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"])
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_writer :sns_secret_access_key
|
16
|
+
|
17
|
+
def sns_secret_access_key
|
18
|
+
@sns_secret_access_key ||= (ENV["SNS_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"])
|
19
|
+
end
|
20
|
+
end # class << self
|
21
|
+
|
22
|
+
def client
|
23
|
+
@client ||= Aws::SNS::Client.new(
|
24
|
+
:region => self.class.sns_region,
|
25
|
+
:access_key_id => self.class.sns_access_key_id,
|
26
|
+
:secret_access_key => self.class.sns_secret_access_key,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
from = data[:from] || BMC::SMS.default_from
|
32
|
+
|
33
|
+
client.publish(
|
34
|
+
:phone_number => data[:to],
|
35
|
+
:message => data[:body],
|
36
|
+
:message_attributes => {
|
37
|
+
"AWS.SNS.SMS.SenderID" => {
|
38
|
+
:data_type => "String",
|
39
|
+
:string_value => from,
|
40
|
+
},
|
41
|
+
"AWS.SNS.SMS.SMSType" => {
|
42
|
+
:data_type => "String",
|
43
|
+
:string_value => "Transactional",
|
44
|
+
},
|
45
|
+
},
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
data/app/sms/bmc/sms.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module BMC::SMS
|
2
|
+
class << self
|
3
|
+
def strategy=(value)
|
4
|
+
@strategy = parse_strategy(value)
|
5
|
+
end
|
6
|
+
|
7
|
+
def strategy
|
8
|
+
@strategy ||= default_strategy
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_writer :default_from
|
12
|
+
|
13
|
+
def default_from
|
14
|
+
@default_from ||= Rails.application.class.to_s.chomp("::Application")
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_strategy(value)
|
18
|
+
if value.is_a?(Symbol)
|
19
|
+
"BMC::SMS::Strategies::#{value.to_s.camelcase}".constantize
|
20
|
+
else
|
21
|
+
value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def default_strategy
|
28
|
+
if Rails.env.development? || Rails.env.test?
|
29
|
+
BMC::SMS::Strategies::Test
|
30
|
+
else
|
31
|
+
BMC::SMS::Strategies::AmazonSNS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end # class << self
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class BMC::Sorter
|
2
|
+
include BMC::SortingHelper
|
3
|
+
|
4
|
+
attr_reader :collection, :sort_param, :column, :direction
|
5
|
+
|
6
|
+
def initialize(collection, sort_param = nil)
|
7
|
+
@collection = collection
|
8
|
+
@sort_param = sort_param
|
9
|
+
@column, @direction = sortable_column_order(sort_param.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sort
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
# Don't replace by `collection.reorder(sort)`
|
18
|
+
# #sort can change #collection and must be called before #collection
|
19
|
+
order = sort
|
20
|
+
collection.reorder(order)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.call(...)
|
24
|
+
new(...).call
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
= render "bmc/search/form_bs#{Bootstrap::VERSION[0]}", action: action
|