bmc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|