tolliver 1.0.0 → 2.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -11
  3. data/app/models/tolliver/notification.rb +0 -1
  4. data/app/models/tolliver/notification_attachment.rb +16 -0
  5. data/app/models/tolliver/notification_delivery.rb +1 -3
  6. data/app/models/tolliver/notification_receiver.rb +0 -2
  7. data/app/models/tolliver/notification_template.rb +0 -1
  8. data/app/views/{notification_mailer → tolliver/notification_mailer}/notify.html.erb +0 -0
  9. data/app/views/tolliver/notification_mailer/notify.text.erb +1 -0
  10. data/config/locales/en.yml +1 -1
  11. data/db/migrate/20160121093817_create_notifications.rb +1 -12
  12. data/db/migrate/20160121094838_create_notification_receivers.rb +6 -4
  13. data/db/migrate/20160509144238_create_notification_templates.rb +4 -4
  14. data/db/migrate/20170630190600_create_notification_deliveries.rb +10 -3
  15. data/db/migrate/20201027150000_create_notification_attachments.rb +17 -0
  16. data/lib/tolliver.rb +44 -47
  17. data/lib/tolliver/errors/bad_request.rb +17 -0
  18. data/lib/tolliver/errors/not_found.rb +17 -0
  19. data/lib/tolliver/errors/standard_error.rb +17 -0
  20. data/lib/tolliver/mailers/notification_mailer.rb +10 -13
  21. data/lib/tolliver/models/notification.rb +11 -64
  22. data/lib/tolliver/models/notification_attachment.rb +35 -0
  23. data/lib/tolliver/models/notification_delivery.rb +32 -40
  24. data/lib/tolliver/models/notification_receiver.rb +3 -25
  25. data/lib/tolliver/models/notification_template.rb +6 -10
  26. data/lib/tolliver/services/delivery.rb +52 -8
  27. data/lib/tolliver/services/methods/email.rb +42 -0
  28. data/lib/tolliver/services/methods/sms.rb +51 -0
  29. data/lib/tolliver/services/methods/sms/plivo.rb +51 -0
  30. data/lib/tolliver/services/notification.rb +109 -178
  31. data/lib/tolliver/services/policies/batch.rb +90 -0
  32. data/lib/tolliver/services/policies/instantly.rb +50 -0
  33. metadata +20 -14
  34. data/app/views/notification_mailer/notify.text.erb +0 -1
  35. data/lib/tolliver/models/notification_delivery/batch.rb +0 -77
  36. data/lib/tolliver/models/notification_delivery/instantly.rb +0 -55
  37. data/lib/tolliver/models/notification_receiver/email.rb +0 -46
  38. data/lib/tolliver/models/notification_receiver/sms.rb +0 -45
  39. data/lib/tolliver/services/sms/plivo.rb +0 -49
@@ -0,0 +1,42 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Notification method - E-mail
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 21. 1. 2016
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ module Tolliver
13
+ module Services
14
+ module Methods
15
+ class Email
16
+
17
+ def deliver(notification_receiver)
18
+ notification = notification_receiver.notification_delivery.notification
19
+
20
+ # Send email
21
+ begin
22
+ Tolliver::NotificationMailer.notify(notification, notification_receiver).deliver_now
23
+ notification_receiver.status = 'sent'
24
+ #rescue Net::SMTPFatalError, Net::SMTPSyntaxError
25
+ rescue StandardError => e
26
+ notification_receiver.status = 'error'
27
+ notification_receiver.error_message = e.message
28
+ end
29
+
30
+ # Mark as sent
31
+ notification_receiver.sent_at = Time.current
32
+
33
+ # Save
34
+ notification_receiver.save
35
+
36
+ true
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Notification receiver
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 21. 1. 2016
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ module Tolliver
13
+ module Services
14
+ module Methods
15
+ class Sms
16
+
17
+ def deliver(notification_receiver)
18
+ notification = notification_receiver.notification_delivery.notification
19
+
20
+ # Send SMS
21
+ begin
22
+ provider.deliver(notification_receiver.receiver_contact, notification.message.strip_tags)
23
+ notification_receiver.status = 'sent'
24
+ rescue StandardError => e
25
+ notification_receiver.status = 'error'
26
+ notification_receiver.error_message = e.message
27
+ end
28
+
29
+ # Mark as sent
30
+ notification_receiver.sent_at = Time.current
31
+
32
+ # Save
33
+ notification_receiver.save
34
+
35
+ true
36
+ end
37
+
38
+ protected
39
+
40
+ def provider
41
+ if @provider.nil?
42
+ provider_class_name = "Tolliver::Services::Methods::Sms::#{Tolliver.sms_provider.to_s.camelize}"
43
+ @provider = provider_class_name.constantize.new(Tolliver.sms_provider_params)
44
+ end
45
+ @provider
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Plivo SMS provider
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 1. 12. 2017
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ require 'plivo'
13
+
14
+ module Tolliver
15
+ module Services
16
+ module Methods
17
+ module Sms
18
+ class Plivo
19
+
20
+ def initialize(params = {})
21
+ if params[:auth_id].blank? || params[:auth_token].blank?
22
+ raise Tolliver::Errors::StandardError.new('Please provide Auth ID and Auth Token in provider params.')
23
+ end
24
+ @auth_id = params[:auth_id]
25
+ @auth_token = params[:auth_token]
26
+ @api = ::Plivo::RestAPI.new(@auth_id, @auth_token)
27
+ end
28
+
29
+ def deliver(receiver, message)
30
+
31
+ # Check message length.
32
+ if message.bytesize > 200
33
+ raise 'Message too long.'
34
+ end
35
+
36
+ # Request API
37
+ response = @api.send_message({
38
+ 'src' => Tolliver.sms_sender, # TODO: This should be improved to take sender from number pool and remember number / message mapping
39
+ 'dst' => receiver.to_s,
40
+ 'text' => message.to_s,
41
+ 'method' => 'POST'
42
+ })
43
+
44
+ true
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -9,226 +9,157 @@
9
9
  # *
10
10
  # *****************************************************************************
11
11
 
12
+ require 'singleton'
13
+
12
14
  module Tolliver
13
15
  module Services
14
- module Notification
15
- extend ActiveSupport::Concern
16
-
17
- module ClassMethods
16
+ class Notification
17
+ include Singleton
18
18
 
19
- def notify(content, receivers, options = {})
19
+ def notify(options)
20
+ options = options.deep_symbolize_keys
20
21
 
21
- # Object
22
- notification = Tolliver.notification_model.new
22
+ # Object
23
+ notification = Tolliver.notification_model.new
23
24
 
24
- Tolliver.notification_model.transaction do
25
+ Tolliver.notification_model.transaction do
25
26
 
26
- # Get subject, message and params
27
- subject, message, params, notification_template = parse_content(content)
27
+ # Get notification template object
28
+ template = options[:template]
29
+ raise Tolliver::Errors::BadRequest.new('Missing template.') if template.blank?
30
+ notification_template = Tolliver.notification_template_model.where(ref: template).first
31
+ notification_template = Tolliver.notification_template_model.where(id: template).first if notification_template.nil?
32
+ raise Tolliver::Errors::NotFound.new("Template #{template.to_s} not found.") if notification_template.nil?
28
33
 
29
- if !message.blank?
34
+ if !notification_template.is_disabled && !notification_template.message.blank?
30
35
 
31
- # Interpret params and store it in DB
32
- notification.message = interpret_params(message, params)
33
- notification.subject = interpret_params(subject, params)
36
+ # Interpret params and store it in DB
37
+ params = map_params(options[:params] || {})
38
+ notification.message = interpret_named_params(notification_template.message, params)
39
+ notification.subject = interpret_named_params(notification_template.subject, params)
34
40
 
35
- # Notification template
36
- notification.notification_template = notification_template
37
-
38
- # Kind
39
- if options[:kind]
40
- notification.kind = options[:kind]
41
- end
41
+ # Notification template
42
+ notification.notification_template = notification_template
42
43
 
43
- # Sender
44
- if options[:sender]
45
- notification.sender = options[:sender]
46
- end
44
+ # Signature
45
+ unless options[:signature].blank?
46
+ notification.message += options[:signature].to_s
47
+ end
47
48
 
48
- # URL
49
- if options[:url]
50
- notification.url = options[:url]
49
+ # Save to DB
50
+ notification.save
51
+
52
+ # Attachments
53
+ unless options[:attachments].blank?
54
+ attachments = options[:attachments]
55
+ attachments = [attachments] unless attachments.is_a?(Array)
56
+ attachments.each do |attachment|
57
+ raise Tolliver::Errors::BadRequest.new('Missing attachment name.') if attachment[:name].blank?
58
+ raise Tolliver::Errors::BadRequest.new('Missing attachment data.') if attachment[:attachment].blank?
59
+ notification.notification_attachments.create(name: attachment[:name], attachment: attachment[:attachment])
51
60
  end
61
+ end
52
62
 
53
- # Attachment
54
- if options[:attachment]
55
- notification.attachment = options[:attachment]
56
- end
63
+ unless notification_template.is_dry
57
64
 
58
- # Signature
59
- if !options[:signature].blank?
60
- notification.message += options[:signature].to_s
65
+ # Delivery methods
66
+ if options[:methods].blank?
67
+ methods = Tolliver.delivery_methods
68
+ else
69
+ # Select only wanted delivery methods, but valid according to module config
70
+ methods = options[:methods]
71
+ methods = [methods] unless methods.is_a?(Array)
72
+ methods = methods.delete_if { |method| !Tolliver.delivery_methods.include?(method.to_sym) }
61
73
  end
62
74
 
63
- # Save to DB
64
- notification.save
75
+ methods.each do |method|
65
76
 
66
- if !notification_template || notification_template.dry != true
77
+ # Delivery object
78
+ notification_delivery = notification.notification_deliveries.create(method: method)
67
79
 
68
- # Delivery kinds
69
- if options[:delivery_kinds] # Select only wanted delivery kinds, but valid according to module config
70
- delivery_kinds = options[:delivery_kinds]
71
- delivery_kinds = delivery_kinds.delete_if { |delivery_kind| !Tolliver.delivery_kinds.include?(delivery_kind.to_sym) }
72
- else
73
- delivery_kinds = Tolliver.delivery_kinds
80
+ # Is postponed
81
+ if options[:is_postponed]
82
+ notification_delivery.is_postponed = options[:is_postponed]
74
83
  end
75
84
 
76
- # Get valid receivers
77
- receivers = parse_receivers(receivers)
78
-
79
- delivery_kinds.each do |delivery_kind|
80
-
81
- # Delivery object
82
- notification_delivery = notification.notification_deliveries.create(kind: delivery_kind)
83
-
84
- # Filter out receivers not valid for this delivery kind
85
- if delivery_kind == :email
86
- filtered_receivers = receivers.delete_if { |receiver| !receiver.respond_to?(:email) }
87
- elsif delivery_kind == :sms
88
- filtered_receivers = receivers.delete_if { |receiver| !receiver.respond_to?(:phone) }
89
- else
90
- filtered_receivers = receivers.dup
91
- end
92
-
93
- # Save to DB
94
- filtered_receivers.each do |receiver|
95
- notification_delivery.notification_receivers.create(receiver: receiver)
96
- end
97
- notification_delivery.sent_count = 0
98
- notification_delivery.receivers_count = filtered_receivers.size
99
- notification_delivery.save
85
+ # Sender
86
+ unless options[:sender].blank?
87
+ raise Tolliver::Errors::BadRequest.new('Missing sender ref.') if options[:sender][:ref].blank?
88
+ raise Tolliver::Errors::BadRequest.new('Missing sender contact.') if options[:sender][:contact].blank?
89
+ notification_delivery.sender_ref = options[:sender][:ref]
90
+ notification_delivery.sender_contact = options[:sender][:contact]
91
+ end
100
92
 
93
+ # Receivers
94
+ receivers = options[:receivers] || []
95
+ raise Tolliver::Errors::BadRequest.new('Missing receivers.') if receivers.blank? || receivers.empty?
96
+ receivers = [receivers] unless receivers.is_a?(Array)
97
+ filtered_receivers = receivers.dup # TODO contact validation based on method and filter
98
+
99
+ # Save to DB
100
+ filtered_receivers.each do |receiver|
101
+ raise Tolliver::Errors::BadRequest.new('Missing sender ref.') if receiver[:ref].blank?
102
+ raise Tolliver::Errors::BadRequest.new('Missing sender contact.') if receiver[:contact].blank?
103
+ notification_delivery.notification_receivers.create(receiver_ref: receiver[:ref], receiver_contact: receiver[:contact])
101
104
  end
105
+ notification_delivery.sent_count = 0
106
+ notification_delivery.receivers_count = filtered_receivers.size
107
+ notification_delivery.save
102
108
 
103
109
  end
104
110
 
105
- else
106
- notification = nil # Do not create notification with empty message
107
111
  end
108
112
 
113
+ else
114
+ notification = nil # Do not create notification with empty message
109
115
  end
110
116
 
111
- # Enqueue for delivery
112
- notification.enqueue_for_delivery if !notification.nil?
113
-
114
- return notification
115
117
  end
116
118
 
117
- def parse_content(content)
118
-
119
- # Arrayize
120
- if !content.is_a?(Array)
121
- content = [content]
122
- end
123
-
124
- # Check for empty
125
- if content.length == 0
126
- raise "Notification is incorrectly defined."
127
- end
128
-
129
- # Extract content definition and params
130
- content_def = content.shift
131
-
132
- # Preset
133
- params = content
134
- subject = nil
135
- message = nil
136
- notification_template = nil
137
-
138
- if content_def.is_a?(Symbol)
139
-
140
- notification_template = Tolliver.notification_template_model.where(ref: content_def.to_s).first
141
- if notification_template.nil?
142
- raise "Notification template #{content_def.to_s} not found."
143
- end
144
- if notification_template.disabled != true
145
- message = notification_template.message
146
- subject = notification_template.subject
147
- end
148
-
149
- elsif content_def.is_a?(Hash) # Defined by hash containing subject and message
150
-
151
- subject = content_def[:subject]
152
- message = content_def[:message]
153
-
154
- elsif content_def.is_a?(String)
155
-
156
- message = content_def
119
+ # Enqueue for delivery
120
+ if !notification.nil? && !options[:is_postponed]
121
+ notification.enqueue_for_delivery
122
+ end
157
123
 
158
- else
159
- raise "Notification is incorrectly defined."
160
- end
124
+ notification
125
+ end
161
126
 
162
- # First parameter is message (all other parameters are indexed from 1)
163
- params.unshift(message)
127
+ protected
164
128
 
165
- return [subject, message, params, notification_template]
129
+ def map_params(params)
130
+ result = {}
131
+ params.each do |param|
132
+ raise Tolliver::Errors::BadRequest.new('Missing param key.') if param[:key].blank?
133
+ raise Tolliver::Errors::BadRequest.new('Missing param value.') if param[:value].nil?
134
+ result[param[:key].to_s] = param[:value]
166
135
  end
136
+ result
137
+ end
167
138
 
168
- def parse_receivers(receivers)
169
-
170
- # Arrayize
171
- if !receivers.is_a?(Array)
172
- receivers = [receivers]
173
- end
174
-
175
- # Automatic receivers defined by string
176
- new_receivers = []
177
- receivers.each do |receiver|
178
- if receiver.is_a?(String) || receiver.is_a?(Symbol)
179
- if Tolliver.people_selector_model && Tolliver.people_selector_model.respond_to?(:decode_value) && Tolliver.people_selector_model.respond_to?(:people)
180
- ref, params = Tolliver.people_selector_model.decode_value(receiver.to_s)
181
- new_receivers.concat(Tolliver.people_selector_model.people(ref, params).to_a) # Use people selector to generate receivers
182
- end
183
- else
184
- new_receivers << receiver
185
- end
186
- end
187
- receivers = new_receivers
188
-
189
- # Receivers responding to email or users
190
- new_receivers = []
191
- receivers.each do |receiver|
192
- if receiver.respond_to?(:email)
193
- new_receivers << receiver
194
- else
195
- if receiver.respond_to?(:user)
196
- new_receivers << receiver.user
197
- else
198
- if receiver.respond_to?(:users)
199
- new_receivers.concat(receiver.users.to_a)
200
- end
201
- end
202
- end
139
+ def interpret_named_params(text, params)
140
+ text.gsub(/%{[^{}]+}/) do |match|
141
+ key = match[2..-2].to_s.strip
142
+ if params.has_key?(key)
143
+ params[key] # return param value if key found
144
+ else
145
+ match # return entire match if key not found
203
146
  end
204
- receivers = new_receivers
205
-
206
- return receivers
207
147
  end
148
+ end
208
149
 
209
- #
210
- # Interpret params into given text
211
- #
212
- def interpret_params(text, params)
213
- return text.gsub(/%{[^{}]+}/) do |match|
214
-
215
- # Substitude all %1, %2, %3, ... to a form which can be evaluated
216
- template_to_eval = match[2..-2].gsub(/%([0-9]+)/, "params[\\1]")
217
-
218
- # Evaluate match
219
- begin
220
- evaluated_match = eval(template_to_eval)
221
- rescue
222
- evaluated_match = ""
223
- end
224
-
225
- # Result
226
- evaluated_match
150
+ def interpret_positional_params(text, params)
151
+ text.gsub(/%{[^{}]+}/) do |match|
152
+ template_to_eval = match[2..-2].gsub(/%([0-9]+)/, "params[\\1]") # substitute all %1, %2, %3, ... to a form which can be evaluated
153
+ begin
154
+ evaluated_match = eval(template_to_eval) # evaluate match against params
155
+ rescue
156
+ evaluated_match = ""
227
157
  end
158
+ evaluated_match
228
159
  end
229
-
230
160
  end
231
161
 
232
162
  end
163
+
233
164
  end
234
- end
165
+ end