nuntius 0.1.2 → 1.0.27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +199 -63
  4. data/Rakefile +62 -1
  5. data/app/controllers/nuntius/admin/campaigns_controller.rb +56 -0
  6. data/app/controllers/nuntius/admin/layouts/attachments_controller.rb +32 -0
  7. data/app/controllers/nuntius/admin/layouts_controller.rb +50 -0
  8. data/app/controllers/nuntius/admin/lists/subscribers_controller.rb +50 -0
  9. data/app/controllers/nuntius/admin/lists_controller.rb +46 -0
  10. data/app/controllers/nuntius/admin/locales_controller.rb +46 -0
  11. data/app/controllers/nuntius/admin/messages_controller.rb +24 -0
  12. data/app/controllers/nuntius/admin/templates_controller.rb +56 -0
  13. data/app/controllers/nuntius/api/events_controller.rb +16 -0
  14. data/app/controllers/nuntius/application_admin_controller.rb +11 -0
  15. data/app/controllers/nuntius/application_controller.rb +12 -0
  16. data/app/controllers/nuntius/callbacks_controller.rb +21 -0
  17. data/app/controllers/nuntius/dashboard_controller.rb +12 -0
  18. data/app/controllers/nuntius/feedback_controller.rb +17 -0
  19. data/app/controllers/nuntius/inbound_messages/twilio_inbound_smses_controller.rb +56 -0
  20. data/app/controllers/nuntius/messages_controller.rb +13 -0
  21. data/app/drops/nuntius/application_drop.rb +17 -0
  22. data/app/drops/nuntius/campaign_drop.rb +7 -0
  23. data/app/drops/nuntius/layout_drop.rb +11 -0
  24. data/app/drops/nuntius/message_drop.rb +11 -0
  25. data/app/drops/nuntius/template_drop.rb +7 -0
  26. data/app/exceptions/nuntius/base_exception.rb +4 -0
  27. data/app/exceptions/nuntius/missing_messenger_exception.rb +4 -0
  28. data/app/helpers/nuntius/application_helper.rb +78 -0
  29. data/app/jobs/nuntius/application_job.rb +9 -0
  30. data/app/jobs/nuntius/deliver_inbound_message_job.rb +10 -0
  31. data/app/jobs/nuntius/messenger_job.rb +17 -0
  32. data/app/jobs/nuntius/process_inbound_message_job.rb +10 -0
  33. data/app/jobs/nuntius/purge_message_job.rb +13 -0
  34. data/app/jobs/nuntius/retrieve_mail_job.rb +18 -0
  35. data/app/jobs/nuntius/transport_delivery_job.rb +32 -0
  36. data/app/jobs/nuntius/transport_refresh_job.rb +22 -0
  37. data/app/message_boxes/nuntius/base_message_box.rb +75 -0
  38. data/app/messengers/nuntius/base_messenger.rb +193 -0
  39. data/app/messengers/nuntius/custom_messenger.rb +6 -0
  40. data/app/models/nuntius/application_record.rb +11 -0
  41. data/app/models/nuntius/attachment.rb +13 -0
  42. data/app/models/nuntius/campaign.rb +75 -0
  43. data/app/models/nuntius/concerns/metadata_scoped.rb +31 -0
  44. data/app/models/nuntius/concerns/yamlify.rb +23 -0
  45. data/app/models/nuntius/inbound_message.rb +14 -0
  46. data/app/models/nuntius/layout.rb +13 -0
  47. data/app/models/nuntius/list.rb +10 -0
  48. data/app/models/nuntius/locale.rb +11 -0
  49. data/app/models/nuntius/message.rb +148 -0
  50. data/app/models/nuntius/subscriber.rb +12 -0
  51. data/app/models/nuntius/template.rb +102 -0
  52. data/app/presenters/application_presenter.rb +28 -0
  53. data/app/presenters/template_presenter.rb +17 -0
  54. data/app/providers/nuntius/apnotic_push_provider.rb +44 -0
  55. data/app/providers/nuntius/base_provider.rb +72 -0
  56. data/app/providers/nuntius/firebase_push_provider.rb +26 -0
  57. data/app/providers/nuntius/houston_push_provider.rb +40 -0
  58. data/app/providers/nuntius/message_bird_sms_provider.rb +39 -0
  59. data/app/providers/nuntius/slack_slack_provider.rb +36 -0
  60. data/app/providers/nuntius/smtp_mail_provider.rb +80 -0
  61. data/app/providers/nuntius/twilio_sms_provider.rb +37 -0
  62. data/app/providers/nuntius/twilio_voice_provider.rb +70 -0
  63. data/app/reportlets/nuntius/application_reportlet.rb +35 -0
  64. data/app/reportlets/nuntius/daily_messages_per_template_reportlet.rb +50 -0
  65. data/app/runners/nuntius/application_runner.rb +6 -0
  66. data/app/runners/nuntius/basic_application_runner.rb +15 -0
  67. data/app/runners/nuntius/timebased_events_runner.rb +15 -0
  68. data/app/services/nuntius/application_service.rb +7 -0
  69. data/app/services/nuntius/aws_sns_processor_service.rb +81 -0
  70. data/app/services/nuntius/deliver_inbound_message_service.rb +14 -0
  71. data/app/services/nuntius/retrieve_inbound_mail_service.rb +36 -0
  72. data/app/tables/nuntius_campaigns_table.rb +21 -0
  73. data/app/tables/nuntius_layouts_table.rb +24 -0
  74. data/app/tables/nuntius_lists_table.rb +19 -0
  75. data/app/tables/nuntius_locales_table.rb +18 -0
  76. data/app/tables/nuntius_messages_table.rb +36 -0
  77. data/app/tables/nuntius_subscribers_table.rb +19 -0
  78. data/app/tables/nuntius_templates_table.rb +30 -0
  79. data/app/transports/nuntius/base_transport.rb +36 -0
  80. data/app/transports/nuntius/mail_transport.rb +35 -0
  81. data/app/transports/nuntius/push_transport.rb +15 -0
  82. data/app/transports/nuntius/slack_transport.rb +6 -0
  83. data/app/transports/nuntius/sms_transport.rb +6 -0
  84. data/app/transports/nuntius/voice_transport.rb +6 -0
  85. data/app/validators/liquid_validator.rb +11 -0
  86. data/app/views/nuntius/admin/campaigns/edit.html.slim +49 -0
  87. data/app/views/nuntius/admin/campaigns/index.html.slim +2 -0
  88. data/app/views/nuntius/admin/layouts/attachments/_attachments.html.slim +28 -0
  89. data/app/views/nuntius/admin/layouts/attachments/_index.html.slim +2 -0
  90. data/app/views/nuntius/admin/layouts/attachments/create.json.jbuilder +5 -0
  91. data/app/views/nuntius/admin/layouts/edit.html.slim +24 -0
  92. data/app/views/nuntius/admin/layouts/index.html.slim +2 -0
  93. data/app/views/nuntius/admin/lists/edit.html.slim +14 -0
  94. data/app/views/nuntius/admin/lists/index.html.slim +2 -0
  95. data/app/views/nuntius/admin/lists/subscribers/edit.html.slim +14 -0
  96. data/app/views/nuntius/admin/locales/edit.html.slim +14 -0
  97. data/app/views/nuntius/admin/locales/index.html.slim +2 -0
  98. data/app/views/nuntius/admin/messages/index.html.slim +2 -0
  99. data/app/views/nuntius/admin/messages/show.html.slim +56 -0
  100. data/app/views/nuntius/admin/templates/edit.html.slim +81 -0
  101. data/app/views/nuntius/admin/templates/index.html.slim +2 -0
  102. data/app/views/nuntius/dashboard/show.html.slim +7 -0
  103. data/app/views/nuntius/messages/show.html.slim +7 -0
  104. data/config/locales/en.yml +22 -0
  105. data/config/locales/nl.yml +22 -0
  106. data/config/routes.rb +38 -0
  107. data/config/webpack/development.js +5 -0
  108. data/config/webpack/environment.js +4 -0
  109. data/config/webpack/production.js +5 -0
  110. data/config/webpack/test.js +5 -0
  111. data/config/webpacker.yml +119 -0
  112. data/db/migrate/20190301201541_create_nuntius_templates.rb +27 -0
  113. data/db/migrate/20190301202436_create_nuntius_messages.rb +30 -0
  114. data/db/migrate/20190322112815_create_nuntius_lists.rb +12 -0
  115. data/db/migrate/20190322114340_create_nuntius_campaigns.rb +18 -0
  116. data/db/migrate/20190322121338_create_nuntius_subscribers.rb +17 -0
  117. data/db/migrate/20190327123535_add_metadata_to_models.rb +8 -0
  118. data/db/migrate/20190327124407_create_nuntius_layouts.rb +24 -0
  119. data/db/migrate/20190327143921_add_campaign_to_message.rb +7 -0
  120. data/db/migrate/20190327155112_add_state_to_campaign.rb +7 -0
  121. data/db/migrate/20190412103335_add_enabled_to_templates.rb +7 -0
  122. data/db/migrate/20190417125153_change_message_status_default.rb +9 -0
  123. data/db/migrate/20190417144554_add_nuntiable_to_subscriber.rb +7 -0
  124. data/db/migrate/20190521135011_add_payload_to_nuntius_messages.rb +7 -0
  125. data/db/migrate/20190522122657_add_metadata_to_nuntius_message.rb +7 -0
  126. data/db/migrate/20190825080757_update_nuntius_message_sending_to_sent.rb +11 -0
  127. data/db/migrate/20200220154927_change_nuntius_templates_payload_type.rb +7 -0
  128. data/db/migrate/20200224132337_create_nuntius_locales.rb +13 -0
  129. data/db/migrate/20200318095339_create_nuntius_attachments.rb +12 -0
  130. data/db/migrate/20200407050646_add_interval_to_nuntius_template.rb +5 -0
  131. data/db/migrate/20200430131219_make_html_processing_optional.rb +6 -0
  132. data/db/migrate/20200430154032_make_html_processing_optional_revert.rb +6 -0
  133. data/db/migrate/20201121185718_create_nuntius_inbound_messages.rb +21 -0
  134. data/db/migrate/20220412114148_add_last_sent_to_message.rb +5 -0
  135. data/lib/nuntius/active_record_helpers.rb +28 -0
  136. data/lib/nuntius/active_storage_helpers.rb +8 -0
  137. data/lib/nuntius/configuration.rb +97 -0
  138. data/lib/nuntius/deprecator.rb +8 -0
  139. data/lib/nuntius/devise.rb +19 -0
  140. data/lib/nuntius/engine.rb +46 -0
  141. data/lib/nuntius/i18n_store.rb +100 -0
  142. data/lib/nuntius/liquid/tags/attach_tag.rb +44 -0
  143. data/lib/nuntius/mail_allow_list.rb +23 -0
  144. data/lib/nuntius/nuntiable.rb +24 -0
  145. data/lib/nuntius/state_machine.rb +19 -0
  146. data/lib/nuntius/transactio.rb +30 -0
  147. data/lib/nuntius/version.rb +3 -1
  148. data/lib/nuntius.rb +74 -8
  149. data/lib/preamble.rb +87 -0
  150. data/lib/tasks/nuntius_tasks.rake +27 -0
  151. metadata +532 -67
  152. data/.gitignore +0 -4
  153. data/Gemfile +0 -4
  154. data/lib/nuntius/encodings/url_safe_base64.rb +0 -26
  155. data/lib/nuntius/encodings.rb +0 -9
  156. data/lib/nuntius/envelope.rb +0 -45
  157. data/lib/nuntius/key.rb +0 -52
  158. data/lib/nuntius/messenger.rb +0 -37
  159. data/nuntius.gemspec +0 -23
  160. data/spec/keys/alice.pem +0 -27
  161. data/spec/keys/alice.pub +0 -9
  162. data/spec/keys/bob.pem +0 -27
  163. data/spec/keys/bob.pub +0 -9
  164. data/spec/nuntius/encodings/url_safe_base64_spec.rb +0 -36
  165. data/spec/nuntius/envelope_spec.rb +0 -6
  166. data/spec/nuntius/key_spec.rb +0 -22
  167. data/spec/nuntius/messenger_spec.rb +0 -26
  168. data/spec/spec_helper.rb +0 -9
  169. data/spec/support/keys.rb +0 -6
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ # http://nithinbekal.com/posts/rails-presenters/
6
+ class ApplicationPresenter < SimpleDelegator
7
+ delegate :t, to: I18n
8
+
9
+ # Returns reference to the object we're decorating
10
+ def model
11
+ __getobj__
12
+ end
13
+
14
+ def route
15
+ Rails.application.routes.url_helpers
16
+ end
17
+
18
+ # Provide access to view/helpers
19
+ def h
20
+ @h ||= ActionView::Base.new
21
+ end
22
+
23
+ class << self
24
+ def model(model_alias)
25
+ alias_method(model_alias, :model)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TemplatePresenter < ApplicationPresenter
4
+ def all_events
5
+ events = []
6
+ Nuntius.config.nuntiable_class_names.each do |class_name|
7
+ next if class_name == 'Custom'
8
+
9
+ messenger = Nuntius::BaseMessenger.messenger_for_class(class_name)
10
+ messenger.instance_methods(false).each do |m|
11
+ events << [m, m, { 'data-chain': class_name,
12
+ 'data-timebased': messenger.timebased_scopes.include?(m) }]
13
+ end
14
+ end
15
+ events.sort_by(&:first)
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apnotic'
4
+
5
+ module Nuntius
6
+ class ApnoticPushProvider < BaseProvider
7
+ transport :push
8
+
9
+ setting_reader :certificate,
10
+ required: true,
11
+ description: 'The contents of a valid APNS push certificate in .pem format'
12
+ setting_reader :passphrase,
13
+ required: false,
14
+ description: 'If the APNS certificate is protected by a passphrase, ' \
15
+ 'provide this variable to use when decrypting it.'
16
+ setting_reader :environment,
17
+ required: false,
18
+ default: :production,
19
+ description: 'Development or production, defaults to production'
20
+
21
+ def deliver
22
+ return message if message.to.size != 64
23
+
24
+ connection = if environment.to_sym == :development
25
+ Apnotic::Connection.development(cert_path: StringIO.new(certificate), cert_pass: passphrase)
26
+ else
27
+ Apnotic::Connection.new(cert_path: StringIO.new(certificate), cert_pass: passphrase)
28
+ end
29
+
30
+ notification = Apnotic::Notification.new(message.to)
31
+ notification.alert = message.text
32
+ notification.custom_payload = message.payload
33
+
34
+ response = connection.push(notification)
35
+
36
+ message.status = if response.ok?
37
+ 'sent'
38
+ else
39
+ 'undelivered'
40
+ end
41
+ message
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class BaseProvider
5
+ attr_reader :message
6
+
7
+ def initialize(message = nil)
8
+ @message = message
9
+ end
10
+
11
+ class << self
12
+ def all_settings
13
+ @all_settings ||= []
14
+ end
15
+
16
+ def setting_reader(name, required: false, default: nil, description: '')
17
+ @all_settings ||= []
18
+ @all_settings.push(name: name, required: required, default: default, description: description)
19
+ define_method(name) { required ? settings.fetch(name) : settings.dig(name) || default }
20
+ end
21
+
22
+ def transport(transport = nil)
23
+ @transport = transport if transport
24
+ @transport
25
+ end
26
+
27
+ def states(mapping = nil)
28
+ @states = mapping if mapping
29
+ @states
30
+ end
31
+
32
+ def class_from_name(name, transport)
33
+ Nuntius.const_get("#{name}_#{transport}_provider".camelize)
34
+ rescue StandardError
35
+ nil
36
+ end
37
+ end
38
+
39
+ # Override this in implementations
40
+ def deliver
41
+ @message
42
+ end
43
+
44
+ # Override this in implementations
45
+ def refresh
46
+ @message
47
+ end
48
+
49
+ # Override this in implementation
50
+ def callback(_params)
51
+ [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['Not found']]
52
+ end
53
+
54
+ def name
55
+ self.class.name.demodulize.underscore.gsub(/_#{self.class.transport}_provider$/, '').to_sym
56
+ end
57
+
58
+ private
59
+
60
+ def translated_status(status)
61
+ self.class.states.find { |key, _value| key.is_a?(Array) ? key.include?(status) : key == status }&.last || 'sent'
62
+ end
63
+
64
+ def settings
65
+ return @settings if @settings
66
+
67
+ @settings = Nuntius.config.providers[self.class.transport].to_a.find { |d| d[:provider] == name }[:settings]
68
+ @settings = instance_exec(@message, &@settings) if @settings.is_a?(Proc)
69
+ @settings
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fcm'
4
+
5
+ module Nuntius
6
+ class FirebasePushProvider < BaseProvider
7
+ transport :push
8
+
9
+ setting_reader :server_key, required: true, description: 'Server key for the project, see Firebase console'
10
+
11
+ def deliver
12
+ fcm = FCM.new(server_key)
13
+
14
+ options = (message.payload || {}).merge(data: { body: message.text })
15
+ response = fcm.send([message.to], options)
16
+
17
+ message.status = if response[:status_code] != 200 || response[:response] != 'success'
18
+ 'undelivered'
19
+ else
20
+ 'sent'
21
+ end
22
+
23
+ message
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'houston'
4
+
5
+ module Nuntius
6
+ class HoustonPushProvider < BaseProvider
7
+ transport :push
8
+
9
+ setting_reader :certificate,
10
+ required: true,
11
+ description: 'The contents of a valid APNS push certificate in .pem format'
12
+ setting_reader :passphrase,
13
+ required: false,
14
+ description: 'If the APNS certificate is protected by a passphrase, ' \
15
+ 'provide this variable to use when decrypting it.'
16
+ setting_reader :environment,
17
+ required: false,
18
+ default: :production,
19
+ description: 'Development or production, defaults to production'
20
+
21
+ def deliver
22
+ return message if message.to.size != 64
23
+
24
+ apn = environment.to_sym == :development ? Houston::Client.development : Houston::Client.production
25
+ apn.certificate = certificate
26
+ apn.passphrase = passphrase
27
+
28
+ notification = Houston::Notification.new((message.payload || {}).merge(device: message.to,
29
+ alert: message.text, sound: 'default'))
30
+ apn.push(notification)
31
+
32
+ message.status = if notification.sent?
33
+ 'sent'
34
+ elsif !notification.valid? || notification.error
35
+ 'undelivered'
36
+ end
37
+ message
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'messagebird'
4
+
5
+ module Nuntius
6
+ # Send SMS messages using messagebird.com
7
+ class MessageBirdSmsProvider < BaseProvider
8
+ transport :sms
9
+
10
+ setting_reader :auth_token, required: true, description: 'Authentication token'
11
+ setting_reader :from, required: true, description: "Phone-number or name (say: 'Nuntius') to send the message from"
12
+
13
+ # Messagebird statusses: scheduled, sent, buffered, delivered, expired, and delivery_failed.
14
+ states %w[expired delivery_failed] => 'undelivered', 'delivered' => 'delivered'
15
+
16
+ def deliver
17
+ response = client.message_create(message.from.present? ? message.from : from, message.to, message.text)
18
+ message.provider_id = response.id
19
+ message.status = translated_status(response.recipients['items'].first.status)
20
+ message
21
+ end
22
+
23
+ def refresh
24
+ response = client.message(message.provider_id)
25
+ message.provider_id = response.id
26
+ message.status = translated_status(response.recipients['items'].first.status)
27
+ Nuntius.logger.info "SMS #{message.to} status: #{message.status}"
28
+ message
29
+ rescue StandardError => _e
30
+ message
31
+ end
32
+
33
+ private
34
+
35
+ def client
36
+ @client ||= ::MessageBird::Client.new(auth_token)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slack'
4
+
5
+ module Nuntius
6
+ class SlackSlackProvider < BaseProvider
7
+ transport :slack
8
+
9
+ setting_reader :api_key, required: true, description: 'API key for the Slack workspace'
10
+
11
+ def deliver
12
+ client = Slack::Web::Client.new(token: api_key)
13
+
14
+ message.attachments.each do |attachment|
15
+ client.files_upload(
16
+ channels: message[:to],
17
+ as_user: true,
18
+ username: message[:from],
19
+ file: Faraday::UploadIO.new(StringIO.new(attachment.download), attachment.content_type),
20
+ filename: attachment.filename.to_s
21
+ )
22
+ end
23
+
24
+ args = (message.payload || {}).merge(channel: message[:to], text: message.text, as_user: true, username: message[:from])
25
+ response = client.chat_postMessage(args.deep_symbolize_keys)
26
+
27
+ message.status = if response['ok']
28
+ 'sent'
29
+ else
30
+ 'undelivered'
31
+ end
32
+
33
+ message
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mail'
4
+
5
+ module Nuntius
6
+ class SmtpMailProvider < BaseProvider
7
+ transport :mail
8
+
9
+ setting_reader :from_header, required: true, description: 'From header (example: Nuntius Messenger <nuntius@entdec.com>)'
10
+ setting_reader :host, required: true, description: 'Host (example: smtp.soverin.net)'
11
+ setting_reader :port, required: true, description: 'Port (example: 578)'
12
+ setting_reader :username, required: true, description: 'Username (nuntius@entdec.com)'
13
+ setting_reader :password, required: true, description: 'Password'
14
+ setting_reader :allow_list, required: false, default: [], description: 'Allow list (example: [boxture.com, tom@degrunt.net])'
15
+
16
+ def deliver
17
+ return block unless MailAllowList.new(settings[:allow_list]).allowed?(message.to)
18
+ return block if Nuntius::Message.where(status: %w[complaint bounced], to: message.to).count >= 1
19
+
20
+ mail = if message.from.present?
21
+ Mail.new(sender: from_header, from: message.from)
22
+ else
23
+ Mail.new(from: from_header)
24
+ end
25
+
26
+ if Rails.env.test?
27
+ mail.delivery_method :test
28
+ else
29
+ mail.delivery_method :smtp,
30
+ address: host,
31
+ port: port,
32
+ user_name: username,
33
+ password: password,
34
+ return_response: true
35
+ end
36
+
37
+ mail.to = message.to
38
+ mail.subject = message.subject
39
+ mail.part content_type: 'multipart/alternative' do |p|
40
+ p.text_part = Mail::Part.new(
41
+ body: message.text,
42
+ content_type: 'text/plain',
43
+ charset: 'UTF-8'
44
+ )
45
+ if message.html.present?
46
+ message.html = message.html.gsub('%7B%7Bmessage_url%7D%7D') { message_url(message) }
47
+ p.html_part = Mail::Part.new(
48
+ body: message.html,
49
+ content_type: 'text/html',
50
+ charset: 'UTF-8'
51
+ )
52
+ end
53
+ end
54
+
55
+ message.attachments.each do |attachment|
56
+ mail.attachments[attachment.filename.to_s] = { mime_type: attachment.content_type, content: attachment.download }
57
+ end
58
+
59
+ response = mail.deliver!
60
+
61
+ message.provider_id = mail.message_id
62
+ message.status = 'undelivered'
63
+ message.status = 'sent' if Rails.env.test? ? true : response.success?
64
+ message.last_sent_at = Time.zone.now if message.sent?
65
+
66
+ message
67
+ end
68
+
69
+ def block
70
+ message.status = 'blocked'
71
+ message
72
+ end
73
+
74
+ private
75
+
76
+ def message_url(message)
77
+ Nuntius::Engine.routes.url_helpers.message_url(message.id, host: Nuntius.config.host(message))
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'twilio-ruby'
4
+
5
+ module Nuntius
6
+ # Send SMS messages using twilio.com
7
+ class TwilioSmsProvider < BaseProvider
8
+ transport :sms
9
+
10
+ setting_reader :auth_token, required: true, description: 'Authentication token'
11
+ setting_reader :sid, required: true, description: 'Application SID, see Twilio console'
12
+ setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
13
+
14
+ # Twilio statusses: queued, failed, sent, delivered, or undelivered
15
+ states %w[failed undelivered] => 'undelivered', 'delivered' => 'delivered'
16
+
17
+ def deliver
18
+ response = client.messages.create(from: message.from.present? ? message.from : from, to: message.to, body: message.text)
19
+ message.provider_id = response.sid
20
+ message.status = translated_status(response.status)
21
+ message
22
+ end
23
+
24
+ def refresh
25
+ response = client.messages(message.provider_id).fetch
26
+ message.provider_id = response.sid
27
+ message.status = translated_status(response.status)
28
+ message
29
+ end
30
+
31
+ private
32
+
33
+ def client
34
+ @client ||= Twilio::REST::Client.new(sid, auth_token)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'twilio-ruby'
4
+
5
+ module Nuntius
6
+ # Send Voice call messages using twilio.com
7
+ class TwilioVoiceProvider < BaseProvider
8
+ transport :voice
9
+
10
+ setting_reader :host, required: true, description: 'Host or base-url for the application'
11
+ setting_reader :auth_token, required: true, description: 'Authentication token'
12
+ setting_reader :sid, required: true, description: 'Application SID, see Twilio console'
13
+ setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
14
+
15
+ # Twilio statusses: queued, failed, sent, delivered, or undelivered
16
+ states %w[failed undelivered] => 'undelivered', %w[delivered completed] => 'delivered'
17
+
18
+ def deliver
19
+ # Need hostname here too
20
+ response = client.calls.create(from: message.from.present? ? message.from : from, to: message.to, method: 'POST', url: callback_url)
21
+ message.provider_id = response.sid
22
+ message.status = translated_status(response.status)
23
+ message
24
+ end
25
+
26
+ def refresh
27
+ response = client.calls(message.provider_id).fetch
28
+ message.provider_id = response.sid
29
+ message.status = translated_status(response.status)
30
+ message
31
+ end
32
+
33
+ def callback(params)
34
+ refresh.save
35
+
36
+ twiml = script_for_path(message, "/#{params[:path]}", params)
37
+
38
+ if twiml
39
+ [200, { 'Content-Type' => 'application/xml' }, [twiml[:body]]]
40
+ else
41
+ [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['Not found']]
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def client
48
+ @client ||= Twilio::REST::Client.new(sid, auth_token)
49
+ end
50
+
51
+ def script_for_path(message, path = '/', _params)
52
+ scripts = message.text.delete("\r").split("\n\n")
53
+
54
+ scripts = scripts.map do |script|
55
+ preamble = Preamble.parse(script)
56
+ payload = preamble.metadata ? payload = preamble.content : script
57
+ payload = payload.gsub('{{url}}', callback_url)
58
+ metadata = preamble.metadata || { path: '/' }
59
+
60
+ { headers: metadata.with_indifferent_access, body: payload }
61
+ end
62
+
63
+ scripts.find { |s| s[:headers][:path] == path }
64
+ end
65
+
66
+ def callback_url
67
+ Nuntius::Engine.routes.url_helpers.callback_url(message.id, host: host)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class ApplicationReportlet < Trado::Reportlet
5
+ attr_reader :template_ids
6
+
7
+ def initialize(params)
8
+ @template_ids = params[:template_ids]
9
+ end
10
+
11
+ private
12
+
13
+ def ymds(_for_display = false)
14
+ results.map { |r| r['ymd'] }.uniq.sort_by { |w| Date.strptime(w, '%F') }
15
+ end
16
+
17
+ def all_templates
18
+ map_items(results, :template_id)
19
+ end
20
+
21
+ # Helper to get item for ymd, template_id
22
+ def ymd_template_id(ymd, template_id, what)
23
+ find_item(results, { ymd: ymd, template_id: template_id }, what)
24
+ end
25
+
26
+ # Arel helpers
27
+ def templates
28
+ Nuntius::Template.arel_table
29
+ end
30
+
31
+ def messages
32
+ Nuntius::Message.arel_table
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'nuntius/application_reportlet'
4
+
5
+ module Nuntius
6
+ class DailyMessagesPerTemplateReportlet < ApplicationReportlet
7
+ title 'Daily messages per template'
8
+ cache 1.minute
9
+
10
+ def table_data
11
+ data = [[''] + ymds(true)]
12
+
13
+ matrix = all_templates.map do |template|
14
+ tmpl = Nuntius::Template.find(template)
15
+ if tmpl
16
+ template_ymd_totals = ymds.map { |ymd| h.link_to(ymd_template_id(ymd, template, :count), Nuntius::Engine.routes.url_helpers.admin_messages_path(template_id: template)) }
17
+ [h.link_to(tmpl.description, Nuntius::Engine.routes.url_helpers.edit_admin_template_path(tmpl))] + template_ymd_totals
18
+ end
19
+ end
20
+
21
+ if matrix.present?
22
+ data += matrix
23
+ else
24
+ data = []
25
+ end
26
+ data
27
+ end
28
+
29
+ private
30
+
31
+ def results
32
+ return @results if @results
33
+
34
+ select_manager = messages.project(
35
+ :template_id,
36
+ Arel.sql("CONCAT(EXTRACT(YEAR FROM created_at :: DATE) :: VARCHAR, '-', EXTRACT(MONTH FROM created_at :: DATE) :: VARCHAR, '-', EXTRACT(DAY FROM created_at :: DATE) :: VARCHAR) AS ymd"),
37
+ Arel.star.count
38
+ )
39
+
40
+ select_manager = select_manager.where(messages[:template_id].in(template_ids))
41
+ select_manager = select_manager.where(messages[:created_at].between(7.days.ago..Time.now))
42
+
43
+ select_manager = select_manager.group(:template_id, :ymd)
44
+ select_manager = select_manager.order(:template_id, :ymd)
45
+
46
+ Rails.logger.info select_manager.to_sql
47
+ @results = ActiveRecord::Base.connection.execute(select_manager.to_sql)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class ApplicationRunner < Nuntius.config.base_runner.constantize
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ # This is the default runner used for runners in Nuntius, you can insert your
5
+ # own in the Nuntius configuration in your Rails Nuntius initializer.
6
+ class BasicApplicationRunner
7
+ def call
8
+ perform
9
+ end
10
+
11
+ class << self
12
+ delegate :call, to: :new
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class TimebasedEventsRunner < ApplicationRunner
5
+ def perform
6
+ Nuntius::Template.where.not(interval: nil).each do |template|
7
+ messenger = Nuntius::BaseMessenger.messenger_for_class(template.klass)
8
+
9
+ messenger.timebased_scope_for(template).each do |object|
10
+ Nuntius.event(template.event, object)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class ApplicationService < Servitium::Service
5
+ transactional true
6
+ end
7
+ end