tolliver 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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +56 -0
  4. data/app/mailers/tolliver/notification_mailer.rb +16 -0
  5. data/app/models/tolliver/notification.rb +17 -0
  6. data/app/models/tolliver/notification_delivery.rb +19 -0
  7. data/app/models/tolliver/notification_receiver.rb +19 -0
  8. data/app/models/tolliver/notification_template.rb +17 -0
  9. data/app/views/notification_mailer/notify.html.erb +1 -0
  10. data/app/views/notification_mailer/notify.text.erb +1 -0
  11. data/config/locales/cs.yml +86 -0
  12. data/config/locales/en.yml +86 -0
  13. data/config/routes.rb +13 -0
  14. data/db/migrate/20160121093817_create_notifications.rb +28 -0
  15. data/db/migrate/20160121094838_create_notification_receivers.rb +21 -0
  16. data/db/migrate/20160509144238_create_notification_templates.rb +21 -0
  17. data/db/migrate/20170630190600_create_notification_deliveries.rb +20 -0
  18. data/lib/tolliver.rb +167 -0
  19. data/lib/tolliver/engine.rb +17 -0
  20. data/lib/tolliver/mailers/notification_mailer.rb +44 -0
  21. data/lib/tolliver/models/notification.rb +121 -0
  22. data/lib/tolliver/models/notification_delivery.rb +112 -0
  23. data/lib/tolliver/models/notification_delivery/batch.rb +77 -0
  24. data/lib/tolliver/models/notification_delivery/instantly.rb +55 -0
  25. data/lib/tolliver/models/notification_receiver.rb +76 -0
  26. data/lib/tolliver/models/notification_receiver/email.rb +46 -0
  27. data/lib/tolliver/models/notification_receiver/sms.rb +45 -0
  28. data/lib/tolliver/models/notification_template.rb +54 -0
  29. data/lib/tolliver/services/delivery.rb +30 -0
  30. data/lib/tolliver/services/notification.rb +234 -0
  31. data/lib/tolliver/services/sms/plivo.rb +49 -0
  32. data/lib/tolliver/utils/enum.rb +133 -0
  33. metadata +89 -0
@@ -0,0 +1,77 @@
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 Models
14
+ module NotificationDelivery
15
+ module Batch
16
+ extend ActiveSupport::Concern
17
+
18
+ module ClassMethods
19
+
20
+ protected
21
+
22
+ def deliver_batch(notification_delivery_id, batch_size = 10)
23
+
24
+ # Find notification delivery object
25
+ notification_delivery = Tolliver.notification_delivery_model.find_by_id(notification_delivery_id)
26
+ return nil if notification_delivery.nil? || notification_delivery.policy != :batch
27
+
28
+ # Nothing to do
29
+ return 0 if notification_delivery.sent_count == notification_delivery.receivers_count
30
+
31
+ # Get batch of receivers prepared for send
32
+ notification_receivers = notification_delivery.notification_receivers.where(sent_at: nil).limit(batch_size)
33
+
34
+ # Send entire batch
35
+ sent_counter = 0
36
+ notification_receivers.each do |notification_receiver|
37
+ sent_counter += 1 if notification_receiver.deliver
38
+ end
39
+
40
+ # Update statistics
41
+ notification_delivery.sent_count += sent_counter
42
+ notification_delivery.sent_at = Time.current if notification_delivery.sent_count == notification_delivery.receivers_count
43
+
44
+ # Save
45
+ notification_delivery.save
46
+
47
+ return (notification_delivery.receivers_count - notification_delivery.sent_count)
48
+
49
+ end
50
+
51
+ def deliver_batch_and_enqueue(notification_delivery_id, batch_size = 10)
52
+
53
+ # Send single batch
54
+ remaining = deliver_batch(notification_delivery_id, batch_size)
55
+ return nil if remaining.nil?
56
+
57
+ # If still some receivers remaining, enqueue next batch
58
+ if remaining > 0
59
+
60
+ # Sleep for a while to prevent SMTP/SMS service overflow
61
+ sleep 5 # seconds
62
+
63
+ # Queue next batch
64
+ QC.enqueue("#{self.to_s}.deliver_batch_and_enqueue", notification_delivery_id, batch_size)
65
+ return false
66
+ else
67
+ return true
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,55 @@
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 Models
14
+ module NotificationDelivery
15
+ module Instantly
16
+ extend ActiveSupport::Concern
17
+
18
+ module ClassMethods
19
+
20
+ protected
21
+
22
+ def deliver_instantly(notification_delivery_id)
23
+
24
+ # Find notification delivery object
25
+ notification_delivery = Tolliver.notification_delivery_model.find_by_id(notification_delivery_id)
26
+ return nil if notification_delivery.nil? || notification_delivery.policy != :instantly
27
+
28
+ # Nothing to do
29
+ return 0 if notification_delivery.sent_count == notification_delivery.receivers_count
30
+
31
+ # Get batch of receivers prepared for send
32
+ notification_receivers = notification_delivery.notification_receivers #.where(sent_at: nil)
33
+
34
+ # Send entire batch
35
+ sent_counter = 0
36
+ notification_receivers.each do |notification_receiver|
37
+ sent_counter += 1 if notification_receiver.deliver
38
+ end
39
+
40
+ # Update statistics
41
+ notification_delivery.sent_count += sent_counter
42
+ notification_delivery.sent_at = Time.current if notification_delivery.sent_count == notification_delivery.receivers_count
43
+
44
+ # Save
45
+ notification_delivery.save
46
+
47
+ return (notification_delivery.receivers_count - notification_delivery.sent_count)
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,76 @@
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 Models
14
+ module NotificationReceiver
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+
19
+ # *********************************************************************
20
+ # Structure
21
+ # *********************************************************************
22
+
23
+ belongs_to :notification_delivery, class_name: Tolliver.notification_delivery_model.to_s
24
+ belongs_to :receiver, polymorphic: true
25
+
26
+ # *********************************************************************
27
+ # Validators
28
+ # *********************************************************************
29
+
30
+ validates_presence_of :notification_delivery_id, :receiver_id, :receiver_type
31
+
32
+ # *********************************************************************
33
+ # State
34
+ # *********************************************************************
35
+
36
+ enum_column :state, [
37
+ :created,
38
+ :sent,
39
+ :received,
40
+ :accepted,
41
+ :error
42
+ ], default: :created
43
+
44
+ end
45
+
46
+ # ***********************************************************************
47
+ # Receiver
48
+ # ***********************************************************************
49
+
50
+ # Get name or email in case name is not set
51
+ def receiver_name_or_email
52
+ if self.receiver
53
+ if self.receiver.respond_to?(:name_formatted) && !self.receiver.name_formatted.blank?
54
+ return self.receiver.name_formatted
55
+ elsif self.receiver.respond_to?(:name) && !self.receiver.name.blank?
56
+ return self.receiver.name
57
+ else
58
+ return self.receiver.email
59
+ end
60
+ else
61
+ return nil
62
+ end
63
+ end
64
+
65
+ # ***********************************************************************
66
+ # Delivery
67
+ # ***********************************************************************
68
+
69
+ # Deliver notification by correct delivery kind
70
+ def deliver
71
+ return self.send("deliver_by_#{self.notification_delivery.kind}")
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,46 @@
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 Models
14
+ module NotificationReceiver
15
+ module Email
16
+ extend ActiveSupport::Concern
17
+
18
+ protected
19
+
20
+ # Send notification by e-mail
21
+ def deliver_by_email
22
+ notification = self.notification_delivery.notification
23
+
24
+ # Send email
25
+ begin
26
+ Tolliver::NotificationMailer.notify(notification, self.receiver).deliver_now
27
+ self.state = "sent"
28
+ #rescue Net::SMTPFatalError, Net::SMTPSyntaxError
29
+ rescue StandardError => e
30
+ self.state = "error"
31
+ self.error_message = e.message
32
+ end
33
+
34
+ # Mark as sent
35
+ self.sent_at = Time.current
36
+
37
+ # Save
38
+ self.save
39
+
40
+ return true
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,45 @@
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 Models
14
+ module NotificationReceiver
15
+ module Sms
16
+ extend ActiveSupport::Concern
17
+
18
+ protected
19
+
20
+ # Send notification by SMS
21
+ def deliver_by_sms
22
+ notification = self.notification_delivery.notification
23
+
24
+ # Send SMS
25
+ begin
26
+ Tolliver.sms_provider_obj.deliver(self.receiver.phone, notification.message.strip_tags)
27
+ self.state = "sent"
28
+ rescue StandardError => e
29
+ self.state = "error"
30
+ self.error_message = e.message
31
+ end
32
+
33
+ # Mark as sent
34
+ self.sent_at = Time.current
35
+
36
+ # Save
37
+ self.save
38
+
39
+ return true
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Notification template
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 9. 5. 2016
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ module Tolliver
13
+ module Models
14
+ module NotificationTemplate
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+
19
+ # *********************************************************************
20
+ # Structure
21
+ # *********************************************************************
22
+
23
+ has_many :notification_templates, class_name: Tolliver.notification_template_model.to_s, dependent: :nullify
24
+
25
+ # *********************************************************************
26
+ # Validators
27
+ # *********************************************************************
28
+
29
+ validates_presence_of :ref
30
+
31
+ # *********************************************************************
32
+ # Ref
33
+ # *********************************************************************
34
+
35
+ enum_column :ref, Tolliver.template_refs
36
+
37
+ end
38
+
39
+ module ClassMethods
40
+
41
+ def permitted_columns
42
+ [
43
+ :subject,
44
+ :message,
45
+ :disabled,
46
+ :dry
47
+ ]
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Common delivery service
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 19. 4. 2017
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ module Tolliver
13
+ module Services
14
+ module Delivery
15
+ extend ActiveSupport::Concern
16
+
17
+ module ClassMethods
18
+
19
+ #
20
+ # Deliver notification (defined by ID) to receivers by all configured methods
21
+ #
22
+ def deliver(notification_id)
23
+ Tolliver.notification_model.deliver(notification_id)
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,234 @@
1
+ # *****************************************************************************
2
+ # * Copyright (c) 2019 Matěj Outlý
3
+ # *****************************************************************************
4
+ # *
5
+ # * Notification service
6
+ # *
7
+ # * Author: Matěj Outlý
8
+ # * Date : 19. 4. 2017
9
+ # *
10
+ # *****************************************************************************
11
+
12
+ module Tolliver
13
+ module Services
14
+ module Notification
15
+ extend ActiveSupport::Concern
16
+
17
+ module ClassMethods
18
+
19
+ def notify(content, receivers, options = {})
20
+
21
+ # Object
22
+ notification = Tolliver.notification_model.new
23
+
24
+ Tolliver.notification_model.transaction do
25
+
26
+ # Get subject, message and params
27
+ subject, message, params, notification_template = parse_content(content)
28
+
29
+ if !message.blank?
30
+
31
+ # Interpret params and store it in DB
32
+ notification.message = interpret_params(message, params)
33
+ notification.subject = interpret_params(subject, params)
34
+
35
+ # Notification template
36
+ notification.notification_template = notification_template
37
+
38
+ # Kind
39
+ if options[:kind]
40
+ notification.kind = options[:kind]
41
+ end
42
+
43
+ # Sender
44
+ if options[:sender]
45
+ notification.sender = options[:sender]
46
+ end
47
+
48
+ # URL
49
+ if options[:url]
50
+ notification.url = options[:url]
51
+ end
52
+
53
+ # Attachment
54
+ if options[:attachment]
55
+ notification.attachment = options[:attachment]
56
+ end
57
+
58
+ # Signature
59
+ if !options[:signature].blank?
60
+ notification.message += options[:signature].to_s
61
+ end
62
+
63
+ # Save to DB
64
+ notification.save
65
+
66
+ if !notification_template || notification_template.dry != true
67
+
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
74
+ end
75
+
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
100
+
101
+ end
102
+
103
+ end
104
+
105
+ else
106
+ notification = nil # Do not create notification with empty message
107
+ end
108
+
109
+ end
110
+
111
+ # Enqueue for delivery
112
+ notification.enqueue_for_delivery if !notification.nil?
113
+
114
+ return notification
115
+ end
116
+
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
157
+
158
+ else
159
+ raise "Notification is incorrectly defined."
160
+ end
161
+
162
+ # First parameter is message (all other parameters are indexed from 1)
163
+ params.unshift(message)
164
+
165
+ return [subject, message, params, notification_template]
166
+ end
167
+
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
203
+ end
204
+ receivers = new_receivers
205
+
206
+ return receivers
207
+ end
208
+
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
227
+ end
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+ end
234
+ end