web47core 0.0.8 → 0.0.9

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -14
  3. data/Gemfile.lock +30 -9
  4. data/README.md +27 -4
  5. data/lib/app/models/concerns/app47_logger.rb +175 -0
  6. data/lib/app/models/concerns/core_account.rb +51 -0
  7. data/lib/app/models/concerns/standard_model.rb +104 -6
  8. data/lib/app/models/email_notification.rb +253 -0
  9. data/lib/app/models/email_template.rb +6 -0
  10. data/lib/app/models/notification.rb +276 -0
  11. data/lib/app/models/notification_template.rb +20 -0
  12. data/lib/app/models/slack_notification.rb +89 -0
  13. data/lib/app/models/sms_notification.rb +56 -0
  14. data/lib/app/models/smtp_configuration.rb +148 -0
  15. data/lib/app/models/template.rb +21 -0
  16. data/lib/templates/email/notification_failure.liquid +10 -0
  17. data/lib/templates/email/notification_failure.subject.liquid +1 -0
  18. data/lib/templates/slack/error_message.liquid +1 -0
  19. data/lib/web47core.rb +10 -2
  20. data/test/factories/account_factories.rb +9 -0
  21. data/test/factories/notification_factories.rb +14 -0
  22. data/test/models/app47_logger_test.rb +88 -0
  23. data/test/models/concerns/{formable_test.rb → standard_model_test.rb} +24 -5
  24. data/test/models/email_notification_test.rb +297 -0
  25. data/test/models/notification_test.rb +127 -0
  26. data/test/models/slack_notification_test.rb +50 -0
  27. data/test/notification_test_helper.rb +146 -0
  28. data/test/rails_setup.rb +4 -0
  29. data/test/test_helper.rb +10 -4
  30. data/test/test_models_helper.rb +14 -0
  31. data/web47core.gemspec +5 -2
  32. metadata +87 -14
  33. data/lib/app/models/concerns/auto_clear_cache.rb +0 -34
  34. data/lib/app/models/concerns/formable.rb +0 -111
  35. data/test/models/concerns/auto_clear_cache_test.rb +0 -27
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # EmailNotification is a concrete class of Notification, sending emails to both members
5
+ # and users, or in the case of eCommerce, customers.
6
+ #
7
+ # This object allows you to create an EmailNotification object, set it's property and
8
+ # tell it send_notification. It'll handle everything from there including
9
+ # 1. Running in the background thread
10
+ # 2. Error recovery
11
+ # 3. Retry on failure
12
+ #
13
+ # The Email object also uses the templating engine to allow the specification of a template
14
+ # and a set of local variables for dynamic content.
15
+ #
16
+ # Usage:
17
+ #
18
+ # email = EmailNotification.new
19
+ # email.to = 'user@abc.com'
20
+ # email.sender = 'me@abc.com'
21
+ # email.subject = 'Today is the day!'
22
+ # email.message = 'Day is today!'
23
+ # email.send_notification
24
+ #
25
+ # You are done! Go about your business of creating awesome software!
26
+ #
27
+ class EmailNotification < Notification
28
+ # The From: "Joe Smith <joe@abc.com>"
29
+ field :from, type: String
30
+ # The Reply-To: "no-reply@abc.com"
31
+ field :reply_to, type: String
32
+ # The sender: "joe@abc.com"
33
+ field :sender, type: String
34
+ # The cc: header
35
+ field :cc, type: String
36
+ # The subject
37
+ field :subject, type: String
38
+ #
39
+ # Validations
40
+ #
41
+ validates :subject, presence: true
42
+ validates :to, presence: true
43
+
44
+ # has_many :attachments, class_name: 'EmailNotificationAttachment', dependent: :destroy
45
+
46
+ def from_template(template_name, locals = {})
47
+ super
48
+ self.subject = subject_from_template(template_name, locals)
49
+ end
50
+
51
+ #
52
+ # Add a file as an attachment to this email notification.
53
+ #
54
+ # Expecting a path to the file, not the file or data itself.
55
+ # TODO consider how to handle a file object or data object.
56
+ #
57
+ # Once you have called this method, the file is stored via PaperClip and
58
+ # does not need to stay persistant through email delivery.
59
+ # You can delete it after telling the EmailNotification to send
60
+ # the notification.
61
+ #
62
+ # def add_file(file)
63
+ # # Make sure we are saved
64
+ # save! unless persisted?
65
+ # attachment = EmailNotificationAttachment.new email_notification: self
66
+ # attachment.file = open(file)
67
+ # attachment.save!
68
+ # end
69
+ def deliver_message!
70
+ mail = Mail.new
71
+ # Set the from line
72
+ address = from_address
73
+ mail.from = address
74
+ self.from = address
75
+ # Set the sender line
76
+ address = sender_address
77
+ mail.sender = address
78
+ self.sender = address
79
+ # Set the reply to
80
+ address = reply_to_address
81
+ if address.present?
82
+ self.reply_to = address
83
+ mail.reply_to = address
84
+ mail.sender = address
85
+ end
86
+
87
+ # Set the to address
88
+ mail.to = to
89
+
90
+ # Set the cc line
91
+ mail.cc = cc unless cc.nil?
92
+
93
+ # Set the subject line
94
+ mail.subject = subject
95
+
96
+ # set the message body and send
97
+ html_message = build_message
98
+ mail.html_part do
99
+ content_type 'text/html; charset=UTF-8'
100
+ body html_message
101
+ end
102
+
103
+ # Add the attachments, if there are any. If not, none are added.
104
+ add_attachments(attachments) if defined?(attachments)
105
+
106
+ # Setup the delivery method for this message only.
107
+ if 'test'.eql?(ENV['RAILS_ENV'])
108
+ mail.delivery_method :test
109
+ else
110
+ mail.delivery_method :smtp, smtp_configuration
111
+ end
112
+
113
+ # Deliver it
114
+ mail.deliver
115
+ rescue StandardError => error
116
+ # We are catching and rethrowing this only to allow the temp directory to be cleaned up in the ensure block
117
+ raise error
118
+ ensure
119
+ FileUtils.remove_entry_secure(tmp_dir) if defined?(tmp_dir) && tmp_dir.present?
120
+ end
121
+
122
+ def add_attachments(attachments = nil)
123
+ tmp_dir = Dir.mktmpdir
124
+ attachments.each do |attachment|
125
+ # Create a temp directory, it'll get cleaned up in the rescue.
126
+ tmp_file_path = "#{tmp_dir}/#{attachment.file_file_name}"
127
+ File.open(tmp_file_path, 'w') do |f|
128
+ f.write(URI.parse(attachment.file_url).open.read.force_encoding('utf-16').encode)
129
+ end
130
+ mail.attachments[attachment.file_file_name] = {
131
+ mime_type: attachment.file_content_type,
132
+ content: File.open(tmp_file_path).read.force_encoding('utf-16').encode
133
+ }
134
+ end
135
+ end
136
+
137
+ #
138
+ # sender address
139
+ #
140
+ def sender_address
141
+ address = SystemConfiguration.smtp_user_name
142
+ unless account.nil?
143
+ account_smtp = account.fetch_smtp_configuration
144
+ address = account_smtp.username if account_smtp.use?
145
+ end
146
+ address
147
+ end
148
+
149
+ #
150
+ # From address
151
+ #
152
+ def from_address
153
+ return from if from.present?
154
+
155
+ address = SystemConfiguration.default_email
156
+ if account.present?
157
+ account_smtp = account.fetch_smtp_configuration
158
+ address = account_smtp.email_address if account_smtp.use?
159
+ end
160
+ address
161
+ end
162
+
163
+ #
164
+ # Reply to address
165
+ #
166
+ def reply_to_address
167
+ return reply_to if reply_to.present?
168
+
169
+ address = nil
170
+ unless account.nil?
171
+ smtp = account.fetch_smtp_configuration
172
+ address = smtp.reply_to_address if smtp.use? && !smtp.reply_to_address.nil? && !smtp.reply_to_address.empty?
173
+ end
174
+ address
175
+ end
176
+
177
+ #
178
+ # SMTP Configuration
179
+ #
180
+ def smtp_configuration
181
+ account.get_smtp_configuration.use? ? account_smtp_configuration : default_smtp_configuration
182
+ rescue StandardError
183
+ default_smtp_configuration
184
+ end
185
+
186
+ def account_smtp_configuration
187
+ smtp = account.fetch_smtp_configuration
188
+
189
+ config = {
190
+ address: smtp.server_name,
191
+ port: smtp.port,
192
+ authentication: smtp.authentication_method.to_sym,
193
+ enable_starttls_auto: smtp.ssl.eql?(true)
194
+ }
195
+ config[:domain] = smtp.domain unless smtp.domain.nil? || smtp.domain.empty?
196
+ config[:user_name] = smtp.username unless smtp.username.nil? || smtp.username.empty?
197
+ config[:password] = smtp.password unless smtp.password.nil? || smtp.password.empty?
198
+ config
199
+ end
200
+
201
+ def default_smtp_configuration
202
+ SystemConfiguration.smtp_configuration
203
+ end
204
+
205
+ #
206
+ # HTMLize messages
207
+ #
208
+ def build_message
209
+ clean_message = message.strip
210
+ clean_message = "<html><body><pre>#{clean_message}</pre></body></html>" unless clean_message.start_with?('<')
211
+ add_notification_tracker(clean_message)
212
+ end
213
+
214
+ def add_notification_tracker(html_message)
215
+ tracker_url = "#{SystemConfiguration.base_url}/notifications/#{id}/img"
216
+ return html_message if html_message.include?(tracker_url)
217
+
218
+ image_tag = "<img style='height:0;width:0;border:none;display:none;' src='#{tracker_url}' alt=''/></body>"
219
+ html_message.sub(%r{<\/body>}, image_tag)
220
+ end
221
+
222
+ def subject_from_haml_text(haml_text, locals)
223
+ locals[:base_url] = SystemConfiguration.base_url
224
+ haml_engine = Haml::Engine.new(haml_text)
225
+ self.subject = haml_engine.render(Object.new, stringify_all(locals))
226
+ end
227
+
228
+ def subject_from_liquid_text(liquid_text, locals)
229
+ self.subject = render_liquid_text(liquid_text, locals)
230
+ end
231
+
232
+ def subject_from_template(template_name, locals)
233
+ subject = account_subject_template(template_name) ||
234
+ default_subject_template(template_name) ||
235
+ template_from_file(template_name, prefix: 'subject')
236
+ return subject_from_liquid_text(subject, locals) if subject.present?
237
+
238
+ subject = template_from_file(template_name, format: 'haml', prefix: 'subject')
239
+ subject_from_haml_text(subject, locals) if subject.present?
240
+ end
241
+
242
+ def account_subject_template(template_name)
243
+ self.account.templates.emails.find_by(name: template_name.to_s).subject
244
+ rescue StandardError
245
+ nil
246
+ end
247
+
248
+ def default_subject_template(template_name)
249
+ EmailTemplate.where(account: nil, name: template_name.to_s).template
250
+ rescue StandardError
251
+ nil
252
+ end
253
+ end
@@ -0,0 +1,6 @@
1
+ class EmailTemplate < Template
2
+
3
+ field :subject, type: String
4
+
5
+ validates_presence_of :subject
6
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ #
5
+ class Notification
6
+ include StandardModel
7
+ #
8
+ # Constants
9
+ #
10
+ STATE_INVALID = 'invalid'.freeze unless defined? STATE_INVALID
11
+ STATE_NEW = 'new'.freeze unless defined? STATE_NEW
12
+ STATE_PROCESSED = 'processed'.freeze unless defined? STATE_PROCESSED
13
+ STATE_PROCESSING = 'processing'.freeze unless defined? STATE_PROCESSING
14
+ STATE_RESUBMITTED = 'resubmitted'.freeze unless defined? STATE_RESUBMITTED
15
+ STATE_RETRYING = 'retrying'.freeze unless defined? STATE_RETRYING
16
+ STATE_SUBMITTED = 'submitted'.freeze unless defined? STATE_SUBMITTED
17
+ STATE_VIEWED = 'viewed'.freeze unless defined? STATE_VIEWED
18
+ unless defined? ALL_STATES
19
+ ALL_STATES = [STATE_INVALID,
20
+ STATE_NEW,
21
+ STATE_PROCESSED,
22
+ STATE_PROCESSING,
23
+ STATE_RESUBMITTED,
24
+ STATE_RETRYING,
25
+ STATE_SUBMITTED,
26
+ STATE_VIEWED].freeze
27
+ end
28
+ #
29
+ # Channels
30
+ #
31
+ DELIVERY_EMAIL = 'email'.freeze unless defined? DELIVERY_EMAIL
32
+ DELIVERY_SLACK = 'slack'.freeze unless defined? DELIVERY_SLACK
33
+ DELIVERY_SMS = 'sms'.freeze unless defined? DELIVERY_SMS
34
+ #
35
+ # Fields
36
+ #
37
+ field :retries, type: Integer, default: 0
38
+ field :state, type: String, default: STATE_NEW
39
+ field :to, type: String
40
+ field :message, type: String
41
+ field :error_message, type: String
42
+ field :last_viewed_at, type: Time
43
+ field :viewed_count, type: Integer, default: 0
44
+ #
45
+ # Relationships
46
+ #
47
+ belongs_to :account, inverse_of: :notifications, optional: true
48
+ belongs_to :notification_template, inverse_of: :notifications, optional: true
49
+ #
50
+ # Validations
51
+ #
52
+ validates :state, presence: true, inclusion: { in: ALL_STATES }
53
+ validates :message, presence: true
54
+
55
+ #
56
+ # Was this notification successful sent
57
+ #
58
+ def successful?
59
+ [STATE_PROCESSED, STATE_VIEWED].include? state
60
+ end
61
+
62
+ #
63
+ # If this notification can be deleted, we don't want to delete one that is currently being processed.
64
+ #
65
+ def deletable?
66
+ [STATE_PROCESSED, STATE_INVALID, STATE_NEW, STATE_VIEWED].include? state
67
+ end
68
+
69
+ #
70
+ # If this notification can be resent, we don't want to resend one that is currently being processed.
71
+ #
72
+ def sendable?
73
+ [STATE_PROCESSED, STATE_INVALID, STATE_NEW, STATE_VIEWED].include? state
74
+ end
75
+
76
+ #
77
+ # Mark this as viewed
78
+ #
79
+ def viewed
80
+ set(state: STATE_VIEWED, last_viewed_at: Time.now.utc, viewed_count: viewed_count + 1)
81
+ end
82
+
83
+ #
84
+ # Start processing the notification
85
+ #
86
+ def start_processing
87
+ set state: STATE_PROCESSING
88
+ end
89
+
90
+ #
91
+ # Set to retrying
92
+ #
93
+ def retry_delivery(message)
94
+ set error_message: message, retries: retries + 1, state: STATE_RETRYING
95
+ end
96
+
97
+ #
98
+ # Finish processing the notification successfully
99
+ #
100
+ def finish_processing(message = nil)
101
+ if message.present?
102
+ set state: STATE_INVALID, error_message: message
103
+ else
104
+ set state: STATE_PROCESSED, error_message: ''
105
+ end
106
+ end
107
+
108
+ #
109
+ # Send the notification
110
+ #
111
+ def send_notification
112
+ if state.eql? STATE_NEW
113
+ self.state = STATE_SUBMITTED
114
+ else
115
+ self.retries = 0
116
+ self.state = STATE_RESUBMITTED
117
+ self.error_message = 'Retrying'
118
+ end
119
+
120
+ begin
121
+ deliver_message if save!
122
+ rescue StandardError => error
123
+ finish_processing error.message
124
+ end
125
+ end
126
+
127
+ def deliver_message!
128
+ raise 'Incomplete class, concrete implementation should implment #deliver_message!'
129
+ end
130
+
131
+ def deliver_message
132
+ start_processing
133
+ deliver_message!
134
+ finish_processing
135
+ rescue StandardError => error
136
+ if retries > 10
137
+ log_error "Unable to process notification id: #{id}, done retrying", error
138
+ finish_processing "Failed final attempt: #{error.message}"
139
+ notify_failure(error)
140
+ else
141
+ log_error "Unable to process notification id: #{id}, retrying!!", error
142
+ retry_delivery("Failed attempt # #{retries}: #{error.message}")
143
+ delay(run_at: 10.minutes.from_now).deliver
144
+ end
145
+ end
146
+
147
+ handle_asynchronously :deliver_message, priority: 100
148
+
149
+ def from_template(template_name, locals = {})
150
+ locals[:base_url] = SystemConfiguration.base_url
151
+ self.message = message_from_template(template_name, locals)
152
+ end
153
+
154
+ #
155
+ # Retrieve the template from the account
156
+ #
157
+ # If the account does exists or the template in the account does exist, we catch the error and return nil
158
+ #
159
+ def account_message_template(template_name)
160
+ account.templates.find_by(name: template_name.to_s, _type: "Account#{delivery_channel.humanize}Template").template
161
+ rescue StandardError
162
+ nil
163
+ end
164
+
165
+ #
166
+ # Get the default template stored in the database that is not associated with an account
167
+ #
168
+ def default_message_template(template_name)
169
+ Template.find_by(account: nil, name: template_name.to_s).template
170
+ rescue StandardError
171
+ nil
172
+ end
173
+
174
+ #
175
+ # Retrieve the template out of the project
176
+ #
177
+ def template_from_file(template_name, format: 'liquid', prefix: nil)
178
+ file_name = [template_name, prefix, format].compact.join('.')
179
+ if File.exist?(Rails.root.join('lib', 'templates', delivery_channel, file_name))
180
+ File.open(Rails.root.join('lib', 'templates', delivery_channel, file_name))
181
+ else
182
+ File.read(File.join(__dir__, '../../lib', 'templates', delivery_channel, file_name))
183
+ end.read
184
+ rescue StandardError
185
+ nil
186
+ end
187
+
188
+ #
189
+ # Default delivery channel is email, override for sms, SMTP or other channels
190
+ #
191
+ def delivery_channel
192
+ DELIVERY_EMAIL
193
+ end
194
+
195
+ def message_from_template(template_name, locals)
196
+ template = account_message_template(template_name) || template_from_file(template_name)
197
+ return message_from_liquid_text(template, locals) if template.present?
198
+
199
+ template = template_from_file(template_name, format: 'haml')
200
+ message_from_haml_text(template, locals) if template.present?
201
+ end
202
+
203
+ def message_from_haml_text(haml_text, locals)
204
+ locals[:base_url] = SystemConfiguration.base_url
205
+ engine = Haml::Engine.new(haml_text)
206
+ self.message = engine.render(Object.new, stringify_all(locals))
207
+ end
208
+
209
+ def message_from_haml_file(file_name, locals)
210
+ message_from_haml_text(File.read(file_name), locals)
211
+ end
212
+
213
+ def message_from_liquid_text(liquid_text, locals)
214
+ self.message = render_liquid_text(liquid_text, locals)
215
+ end
216
+
217
+ #
218
+ # Render the given liquid text
219
+ #
220
+ def render_liquid_text(liquid_text, locals = {})
221
+ locals[:base_url] = SystemConfiguration.base_url
222
+ Liquid::Template.parse(liquid_text).render(stringify_all(locals))
223
+ end
224
+
225
+ #
226
+ # Convert all keys and values to strings for liquid sanity
227
+ #
228
+ def stringify_all(obj)
229
+ case obj
230
+ when Hash
231
+ result = {}
232
+ obj.each { |key, value| result[key.to_s] = stringify_all(value) }
233
+ when Array
234
+ result = []
235
+ obj.each { |value| result << stringify_all(value) }
236
+ when FalseClass
237
+ result = false
238
+ when TrueClass
239
+ result = true
240
+ else
241
+ result = obj.to_s
242
+ end
243
+ result
244
+ end
245
+
246
+ private
247
+
248
+ #
249
+ # Only send a message if this message is associated with an account
250
+ # and that has an active smtp configuration
251
+ # otherwise send it to support for the environment
252
+ #
253
+ def notify_failure(error)
254
+ if account.nil? || !account.fetch_or_build_smtp_configuration.use?
255
+ error_email = EmailNotification.new
256
+ params = { company_name: SystemConfiguration.company_name,
257
+ error_message: error.message,
258
+ notification_url: "#{SystemConfiguration.base_url}/stack/notifications/#{id}" }
259
+ error_email.from_template(AccountEmailTemplate::EMAIL_NOTIFICATION_FAILED, params)
260
+ error_email.to = SystemConfiguration.support_email
261
+ error_email.save!
262
+ error_email.deliver
263
+ else
264
+ account.smtp_admins.each do |admin|
265
+ error_email = EmailNotification.new
266
+ params = { company_name: SystemConfiguration.company_name,
267
+ error_message: error.message,
268
+ notification_url: "#{SystemConfiguration.base_url}/notifications/#{id}" }
269
+ error_email.from_template(AccountEmailTemplate::EMAIL_NOTIFICATION_FAILED, params)
270
+ error_email.to = admin.email
271
+ error_email.save!
272
+ error_email.deliver
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Base class for notification
5
+ #
6
+ class NotificationTemplate < Template
7
+ include StandardModel
8
+ #
9
+ # Fields
10
+ #
11
+ field :draft, type: Boolean, default: true
12
+ #
13
+ # Relationships
14
+ #
15
+ has_many :notifications, dependent: :nullify
16
+ #
17
+ # Validations
18
+ #
19
+ validates_presence_of :draft
20
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # SlackNotification is a concrete class of Notification, sending notifications to the slack
5
+ # system. Initially this will be only used internally, however we could configure this
6
+ # to work with an account's slack configuration.
7
+ #
8
+ # This object allows you to create an EmailNotification object, set it's property and
9
+ # tell it send_notification. It'll handle everything from there including
10
+ # 1. Running in the background thread
11
+ # 2. Error recovery
12
+ # 3. Retry on failure
13
+ #
14
+ # Usage:
15
+ #
16
+ # SlackNotification.say 'Error getting data from resource'
17
+ #
18
+ # or
19
+ # To change the channel it's posted to...
20
+ #
21
+ # SlackNotification.say 'Error getting data from resource', to: 'another_channel'
22
+ #
23
+ # or
24
+ # To who the message is from, the default is the environment name
25
+ #
26
+ # SlackNotification.say 'Error getting data from resource', to: 'another_channel', from: 'someone else'
27
+ #
28
+ # You are done! Go about your business of creating awesome software!
29
+ #
30
+ class SlackNotification < Notification
31
+ # The slack username to use
32
+ field :from, type: String
33
+
34
+ def deliver_message!
35
+ return unless SystemConfiguration.slack_configured?
36
+
37
+ start_processing
38
+ payload = { text: message }
39
+ payload[:channel] = if to.present?
40
+ to
41
+ elsif SystemConfiguration.slack_support_channel.present?
42
+ SystemConfiguration.slack_support_channel
43
+ else
44
+ 'support'
45
+ end
46
+ # Use the environment as the default, otherwise set it as the from
47
+ payload[:username] = from.presence || Rails.env
48
+ # Setup the delivery method for this message only.
49
+ RestClient.post(SystemConfiguration.slack_api_url, payload.to_json)
50
+ finish_processing
51
+ rescue StandardError => error
52
+ App47Logger.log_debug '!!! Error sending SLACK notification !!!'
53
+ App47Logger.log_debug error.message
54
+ App47Logger.log_debug error.backtrace
55
+ App47Logger.log_debug '!!! Error sending SLACK notification !!!'
56
+ finish_processing error.message
57
+ end
58
+
59
+ #
60
+ # Limit the number of times the slack notification is retried
61
+ #
62
+ def max_retries
63
+ 1
64
+ end
65
+
66
+ #
67
+ # Default delivery channel is email, override for sms, SMTP or other channels
68
+ #
69
+ def delivery_channel
70
+ DELIVERY_SLACK
71
+ end
72
+
73
+ #
74
+ # Convenience method to say something when we see something
75
+ #
76
+ def self.say(message, to: nil, from: nil, template: nil)
77
+ return unless SystemConfiguration.slack_configured?
78
+
79
+ notification = SlackNotification.new(to: to, from: from)
80
+ notification.message = if template.present?
81
+ notification.message_from_template template, message
82
+ elsif message.is_a?(Array)
83
+ message.join("\n")
84
+ else
85
+ message
86
+ end
87
+ notification.send_notification
88
+ end
89
+ end
@@ -0,0 +1,56 @@
1
+ #
2
+ # SmsNotification is a concrete class of Notification, sending sms to both members
3
+ # and users, or in the case of eCommerce, customers.
4
+ #
5
+ # This object allows you to create an SmsNotification object, set it's property and
6
+ # tell it send_notification. It'll handle everything from there including
7
+ # 1. Running in the background thread
8
+ # 2. Error recovery
9
+ # 3. Retry on failure
10
+ #
11
+ # The SMS object also uses the templating engine to allow the specification of a template
12
+ # and a set of local variables for dynamic content.
13
+ #
14
+ # Usage:
15
+ #
16
+ # sms = SmsNotification.new
17
+ # sms.to = '5713326267'
18
+ # sms.message = 'Day is today!'
19
+ # sms.send_notification
20
+ #
21
+ # You are done! Go about your business of creating awesome software!
22
+ #
23
+ class SmsNotification < Notification
24
+ #
25
+ # Fields
26
+ #
27
+ field :sid, type: String
28
+
29
+ validates_format_of :to, with: %r{\A\+[1-9]{1}[0-9]{3,14}\z}, message: 'Invalid phone number'
30
+ validates_presence_of :to
31
+ validates_presence_of :account_id
32
+
33
+ def deliver_message!
34
+ return unless SystemConfiguration.twilio_configured?
35
+
36
+ config = SystemConfiguration.configuration
37
+ account_sid = config.twilio_account_id
38
+ auth_token = config.twilio_auth_token
39
+ client = Twilio::REST::Client.new account_sid, auth_token
40
+
41
+ message = client.account.messages.create(
42
+ body: self.message,
43
+ to: self.to,
44
+ from: config.twilio_phone_number
45
+ )
46
+ # We are saved in the calling class, no reason to save again
47
+ set sid: message.sid
48
+ end
49
+
50
+ #
51
+ # set the delivery channel for the templates
52
+ #
53
+ def delivery_channel
54
+ DELIVERY_SMS
55
+ end
56
+ end