nuntius 1.0.27 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +77 -18
  3. data/Rakefile +16 -39
  4. data/app/controllers/nuntius/admin/campaigns_controller.rb +12 -10
  5. data/app/controllers/nuntius/admin/layouts/attachments_controller.rb +2 -1
  6. data/app/controllers/nuntius/admin/layouts_controller.rb +1 -1
  7. data/app/controllers/nuntius/admin/lists/subscribers_controller.rb +1 -1
  8. data/app/controllers/nuntius/admin/lists_controller.rb +4 -4
  9. data/app/controllers/nuntius/admin/locales_controller.rb +3 -2
  10. data/app/controllers/nuntius/admin/messages_controller.rb +2 -2
  11. data/app/controllers/nuntius/admin/templates_controller.rb +1 -1
  12. data/app/controllers/nuntius/api/events_controller.rb +2 -2
  13. data/app/controllers/nuntius/application_admin_controller.rb +1 -1
  14. data/app/controllers/nuntius/callbacks_controller.rb +4 -4
  15. data/app/controllers/nuntius/dashboard_controller.rb +1 -1
  16. data/app/controllers/nuntius/feedback_controller.rb +1 -2
  17. data/app/controllers/nuntius/inbound_messages/twilio_inbound_smses_controller.rb +7 -7
  18. data/app/controllers/nuntius/messages_controller.rb +1 -1
  19. data/app/controllers/nuntius/subscribers_controller.rb +33 -0
  20. data/app/drops/nuntius/list_drop.rb +7 -0
  21. data/app/drops/nuntius/subscriber_drop.rb +7 -0
  22. data/app/exceptions/nuntius/base_exception.rb +2 -0
  23. data/app/exceptions/nuntius/missing_messenger_exception.rb +2 -0
  24. data/app/helpers/nuntius/application_helper.rb +6 -11
  25. data/app/jobs/nuntius/deliver_inbound_message_job.rb +1 -1
  26. data/app/jobs/nuntius/purge_message_job.rb +2 -2
  27. data/app/jobs/nuntius/retrieve_mail_job.rb +2 -2
  28. data/app/jobs/nuntius/transport_delivery_job.rb +2 -2
  29. data/app/message_boxes/nuntius/base_message_box.rb +46 -23
  30. data/app/messengers/nuntius/base_messenger.rb +13 -12
  31. data/app/models/nuntius/application_record.rb +1 -1
  32. data/app/models/nuntius/attachment.rb +4 -2
  33. data/app/models/nuntius/campaign.rb +4 -51
  34. data/app/models/nuntius/concerns/yamlify.rb +2 -2
  35. data/app/models/nuntius/inbound_message.rb +2 -0
  36. data/app/models/nuntius/list.rb +11 -1
  37. data/app/models/nuntius/message.rb +34 -29
  38. data/app/models/nuntius/subscriber.rb +21 -1
  39. data/app/models/nuntius/template.rb +20 -20
  40. data/app/presenters/application_presenter.rb +1 -1
  41. data/app/presenters/template_presenter.rb +3 -3
  42. data/app/providers/nuntius/apnotic_push_provider.rb +17 -17
  43. data/app/providers/nuntius/base_provider.rb +6 -6
  44. data/app/providers/nuntius/firebase_push_provider.rb +8 -8
  45. data/app/providers/nuntius/houston_push_provider.rb +15 -15
  46. data/app/providers/nuntius/message_bird_sms_provider.rb +7 -7
  47. data/app/providers/nuntius/slack_slack_provider.rb +7 -7
  48. data/app/providers/nuntius/smtp_mail_provider.rb +36 -28
  49. data/app/providers/nuntius/teams_teams_provider.rb +24 -0
  50. data/app/providers/nuntius/twilio_sms_provider.rb +4 -4
  51. data/app/providers/nuntius/twilio_voice_provider.rb +13 -13
  52. data/app/services/nuntius/aws_sns_processor_service.rb +11 -11
  53. data/app/services/nuntius/deliver_campaign_service.rb +65 -0
  54. data/app/services/nuntius/deliver_inbound_message_service.rb +3 -2
  55. data/app/services/nuntius/retrieve_inbound_mail_service.rb +23 -16
  56. data/app/tables/nuntius_campaigns_table.rb +10 -1
  57. data/app/tables/nuntius_layouts_table.rb +4 -4
  58. data/app/tables/nuntius_lists_table.rb +1 -1
  59. data/app/tables/nuntius_locales_table.rb +1 -1
  60. data/app/tables/nuntius_messages_table.rb +5 -5
  61. data/app/tables/nuntius_subscribers_table.rb +3 -2
  62. data/app/tables/nuntius_templates_table.rb +12 -5
  63. data/app/transports/nuntius/base_transport.rb +1 -1
  64. data/app/transports/nuntius/mail_transport.rb +1 -1
  65. data/app/transports/nuntius/push_transport.rb +1 -1
  66. data/app/transports/nuntius/teams_transport.rb +6 -0
  67. data/app/views/nuntius/admin/campaigns/edit.html.slim +12 -16
  68. data/app/views/nuntius/admin/campaigns/index.html.slim +2 -2
  69. data/app/views/nuntius/admin/layouts/attachments/create.json.jbuilder +3 -3
  70. data/app/views/nuntius/admin/layouts/edit.html.slim +18 -19
  71. data/app/views/nuntius/admin/layouts/index.html.slim +2 -2
  72. data/app/views/nuntius/admin/lists/edit.html.slim +19 -9
  73. data/app/views/nuntius/admin/lists/index.html.slim +2 -2
  74. data/app/views/nuntius/admin/lists/subscribers/edit.html.slim +2 -2
  75. data/app/views/nuntius/admin/locales/edit.html.slim +3 -3
  76. data/app/views/nuntius/admin/locales/index.html.slim +2 -2
  77. data/app/views/nuntius/admin/messages/index.html.slim +2 -2
  78. data/app/views/nuntius/admin/messages/show.html.slim +12 -12
  79. data/app/views/nuntius/admin/templates/edit.html.slim +62 -64
  80. data/app/views/nuntius/admin/templates/index.html.slim +2 -2
  81. data/app/views/nuntius/dashboard/show.html.slim +2 -6
  82. data/app/views/nuntius/subscribers/show.html.slim +9 -0
  83. data/config/locales/en.yml +97 -1
  84. data/config/routes.rb +18 -8
  85. data/db/migrate/20190301201541_create_nuntius_templates.rb +5 -5
  86. data/db/migrate/20190301202436_create_nuntius_messages.rb +4 -4
  87. data/db/migrate/20190322114340_create_nuntius_campaigns.rb +2 -2
  88. data/db/migrate/20190322121338_create_nuntius_subscribers.rb +1 -1
  89. data/db/migrate/20190327124407_create_nuntius_layouts.rb +5 -5
  90. data/db/migrate/20190327143921_add_campaign_to_message.rb +1 -1
  91. data/db/migrate/20190417125153_change_message_status_default.rb +2 -2
  92. data/db/migrate/20190825080757_update_nuntius_message_sending_to_sent.rb +2 -2
  93. data/db/migrate/20200220154927_change_nuntius_templates_payload_type.rb +1 -1
  94. data/db/migrate/20200318095339_create_nuntius_attachments.rb +2 -2
  95. data/db/migrate/20201121185718_create_nuntius_inbound_messages.rb +1 -1
  96. data/db/migrate/20240205204719_add_description_to_list.rb +5 -0
  97. data/db/migrate/20240206203019_add_slug_to_nuntius_list.rb +9 -0
  98. data/lib/nuntius/active_record_helpers.rb +4 -4
  99. data/lib/nuntius/configuration.rb +64 -27
  100. data/lib/nuntius/deprecator.rb +2 -0
  101. data/lib/nuntius/devise.rb +1 -1
  102. data/lib/nuntius/engine.rb +13 -27
  103. data/lib/nuntius/i18n_store.rb +6 -5
  104. data/lib/nuntius/liquid/tags/attach_tag.rb +11 -11
  105. data/lib/nuntius/mail_allow_list.rb +2 -2
  106. data/lib/nuntius/nuntiable.rb +3 -1
  107. data/lib/nuntius/transactio.rb +1 -1
  108. data/lib/nuntius/version.rb +1 -1
  109. data/lib/nuntius.rb +13 -27
  110. data/lib/preamble.rb +18 -18
  111. data/lib/tasks/nuntius_tasks.rake +4 -4
  112. metadata +48 -33
  113. data/app/jobs/nuntius/process_inbound_message_job.rb +0 -10
  114. data/app/reportlets/nuntius/application_reportlet.rb +0 -35
  115. data/app/reportlets/nuntius/daily_messages_per_template_reportlet.rb +0 -50
  116. data/config/webpack/development.js +0 -5
  117. data/config/webpack/environment.js +0 -4
  118. data/config/webpack/production.js +0 -5
  119. data/config/webpack/test.js +0 -5
  120. data/config/webpacker.yml +0 -119
@@ -13,10 +13,10 @@ module Nuntius
13
13
  @all_settings ||= []
14
14
  end
15
15
 
16
- def setting_reader(name, required: false, default: nil, description: '')
16
+ def setting_reader(name, required: false, default: nil, description: "")
17
17
  @all_settings ||= []
18
18
  @all_settings.push(name: name, required: required, default: default, description: description)
19
- define_method(name) { required ? settings.fetch(name) : settings.dig(name) || default }
19
+ define_method(name) { required ? settings.fetch(name) : settings[name] || default }
20
20
  end
21
21
 
22
22
  def transport(transport = nil)
@@ -31,7 +31,7 @@ module Nuntius
31
31
 
32
32
  def class_from_name(name, transport)
33
33
  Nuntius.const_get("#{name}_#{transport}_provider".camelize)
34
- rescue StandardError
34
+ rescue
35
35
  nil
36
36
  end
37
37
  end
@@ -48,17 +48,17 @@ module Nuntius
48
48
 
49
49
  # Override this in implementation
50
50
  def callback(_params)
51
- [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['Not found']]
51
+ [404, {"Content-Type" => "text/html; charset=utf-8"}, ["Not found"]]
52
52
  end
53
53
 
54
54
  def name
55
- self.class.name.demodulize.underscore.gsub(/_#{self.class.transport}_provider$/, '').to_sym
55
+ self.class.name.demodulize.underscore.gsub(/_#{self.class.transport}_provider$/, "").to_sym
56
56
  end
57
57
 
58
58
  private
59
59
 
60
60
  def translated_status(status)
61
- self.class.states.find { |key, _value| key.is_a?(Array) ? key.include?(status) : key == status }&.last || 'sent'
61
+ self.class.states.find { |key, _value| key.is_a?(Array) ? key.include?(status) : key == status }&.last || "sent"
62
62
  end
63
63
 
64
64
  def settings
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fcm'
3
+ require "fcm"
4
4
 
5
5
  module Nuntius
6
6
  class FirebasePushProvider < BaseProvider
7
7
  transport :push
8
8
 
9
- setting_reader :server_key, required: true, description: 'Server key for the project, see Firebase console'
9
+ setting_reader :server_key, required: true, description: "Server key for the project, see Firebase console"
10
10
 
11
11
  def deliver
12
12
  fcm = FCM.new(server_key)
13
13
 
14
- options = (message.payload || {}).merge(data: { body: message.text })
14
+ options = (message.payload || {}).merge(data: {body: message.text})
15
15
  response = fcm.send([message.to], options)
16
16
 
17
- message.status = if response[:status_code] != 200 || response[:response] != 'success'
18
- 'undelivered'
19
- else
20
- 'sent'
21
- end
17
+ message.status = if response[:status_code] != 200 || response[:response] != "success"
18
+ "undelivered"
19
+ else
20
+ "sent"
21
+ end
22
22
 
23
23
  message
24
24
  end
@@ -1,39 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'houston'
3
+ require "houston"
4
4
 
5
5
  module Nuntius
6
6
  class HoustonPushProvider < BaseProvider
7
7
  transport :push
8
8
 
9
9
  setting_reader :certificate,
10
- required: true,
11
- description: 'The contents of a valid APNS push certificate in .pem format'
10
+ required: true,
11
+ description: "The contents of a valid APNS push certificate in .pem format"
12
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.'
13
+ required: false,
14
+ description: "If the APNS certificate is protected by a passphrase, " \
15
+ "provide this variable to use when decrypting it."
16
16
  setting_reader :environment,
17
- required: false,
18
- default: :production,
19
- description: 'Development or production, defaults to production'
17
+ required: false,
18
+ default: :production,
19
+ description: "Development or production, defaults to production"
20
20
 
21
21
  def deliver
22
22
  return message if message.to.size != 64
23
23
 
24
- apn = environment.to_sym == :development ? Houston::Client.development : Houston::Client.production
24
+ apn = (environment.to_sym == :development) ? Houston::Client.development : Houston::Client.production
25
25
  apn.certificate = certificate
26
26
  apn.passphrase = passphrase
27
27
 
28
28
  notification = Houston::Notification.new((message.payload || {}).merge(device: message.to,
29
- alert: message.text, sound: 'default'))
29
+ alert: message.text, sound: "default"))
30
30
  apn.push(notification)
31
31
 
32
32
  message.status = if notification.sent?
33
- 'sent'
34
- elsif !notification.valid? || notification.error
35
- 'undelivered'
36
- end
33
+ "sent"
34
+ elsif !notification.valid? || notification.error
35
+ "undelivered"
36
+ end
37
37
  message
38
38
  end
39
39
  end
@@ -1,32 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'messagebird'
3
+ require "messagebird"
4
4
 
5
5
  module Nuntius
6
6
  # Send SMS messages using messagebird.com
7
7
  class MessageBirdSmsProvider < BaseProvider
8
8
  transport :sms
9
9
 
10
- setting_reader :auth_token, required: true, description: 'Authentication token'
10
+ setting_reader :auth_token, required: true, description: "Authentication token"
11
11
  setting_reader :from, required: true, description: "Phone-number or name (say: 'Nuntius') to send the message from"
12
12
 
13
13
  # Messagebird statusses: scheduled, sent, buffered, delivered, expired, and delivery_failed.
14
- states %w[expired delivery_failed] => 'undelivered', 'delivered' => 'delivered'
14
+ states %w[expired delivery_failed] => "undelivered", "delivered" => "delivered"
15
15
 
16
16
  def deliver
17
17
  response = client.message_create(message.from.present? ? message.from : from, message.to, message.text)
18
18
  message.provider_id = response.id
19
- message.status = translated_status(response.recipients['items'].first.status)
19
+ message.status = translated_status(response.recipients["items"].first.status)
20
20
  message
21
21
  end
22
22
 
23
23
  def refresh
24
24
  response = client.message(message.provider_id)
25
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}"
26
+ message.status = translated_status(response.recipients["items"].first.status)
27
+ Nuntius.config.logger.call.info "SMS #{message.to} status: #{message.status}"
28
28
  message
29
- rescue StandardError => _e
29
+ rescue => _e
30
30
  message
31
31
  end
32
32
 
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'slack'
3
+ require "slack"
4
4
 
5
5
  module Nuntius
6
6
  class SlackSlackProvider < BaseProvider
7
7
  transport :slack
8
8
 
9
- setting_reader :api_key, required: true, description: 'API key for the Slack workspace'
9
+ setting_reader :api_key, required: true, description: "API key for the Slack workspace"
10
10
 
11
11
  def deliver
12
12
  client = Slack::Web::Client.new(token: api_key)
@@ -24,11 +24,11 @@ module Nuntius
24
24
  args = (message.payload || {}).merge(channel: message[:to], text: message.text, as_user: true, username: message[:from])
25
25
  response = client.chat_postMessage(args.deep_symbolize_keys)
26
26
 
27
- message.status = if response['ok']
28
- 'sent'
29
- else
30
- 'undelivered'
31
- end
27
+ message.status = if response["ok"]
28
+ "sent"
29
+ else
30
+ "undelivered"
31
+ end
32
32
 
33
33
  message
34
34
  end
@@ -1,80 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mail'
3
+ require "mail"
4
4
 
5
5
  module Nuntius
6
6
  class SmtpMailProvider < BaseProvider
7
7
  transport :mail
8
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])'
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
15
 
16
16
  def deliver
17
17
  return block unless MailAllowList.new(settings[:allow_list]).allowed?(message.to)
18
18
  return block if Nuntius::Message.where(status: %w[complaint bounced], to: message.to).count >= 1
19
19
 
20
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
21
+ Mail.new(sender: from_header, from: message.from)
22
+ else
23
+ Mail.new(from: from_header)
24
+ end
25
25
 
26
26
  if Rails.env.test?
27
27
  mail.delivery_method :test
28
28
  else
29
29
  mail.delivery_method :smtp,
30
- address: host,
31
- port: port,
32
- user_name: username,
33
- password: password,
34
- return_response: true
30
+ address: host,
31
+ port: port,
32
+ user_name: username,
33
+ password: password,
34
+ return_response: true
35
35
  end
36
36
 
37
37
  mail.to = message.to
38
38
  mail.subject = message.subject
39
- mail.part content_type: 'multipart/alternative' do |p|
39
+ mail.part content_type: "multipart/alternative" do |p|
40
40
  p.text_part = Mail::Part.new(
41
41
  body: message.text,
42
- content_type: 'text/plain',
43
- charset: 'UTF-8'
42
+ content_type: "text/plain",
43
+ charset: "UTF-8"
44
44
  )
45
45
  if message.html.present?
46
- message.html = message.html.gsub('%7B%7Bmessage_url%7D%7D') { message_url(message) }
46
+ message.html = message.html.gsub("%7B%7Bmessage_url%7D%7D") { message_url(message) }
47
47
  p.html_part = Mail::Part.new(
48
48
  body: message.html,
49
- content_type: 'text/html',
50
- charset: 'UTF-8'
49
+ content_type: "text/html",
50
+ charset: "UTF-8"
51
51
  )
52
52
  end
53
53
  end
54
54
 
55
55
  message.attachments.each do |attachment|
56
- mail.attachments[attachment.filename.to_s] = { mime_type: attachment.content_type, content: attachment.download }
56
+ mail.attachments[attachment.filename.to_s] = {mime_type: attachment.content_type, content: attachment.download}
57
57
  end
58
58
 
59
- response = mail.deliver!
59
+ begin
60
+ response = mail.deliver!
61
+ rescue Net::SMTPFatalError
62
+ message.status = "rejected"
63
+ return message
64
+ rescue Net::SMTPServerBusy, Net::ReadTimeout
65
+ message.status = "undelivered"
66
+ return message
67
+ end
60
68
 
61
69
  message.provider_id = mail.message_id
62
- message.status = 'undelivered'
63
- message.status = 'sent' if Rails.env.test? ? true : response.success?
70
+ message.status = "undelivered"
71
+ message.status = "sent" if Rails.env.test? ? true : response.success?
64
72
  message.last_sent_at = Time.zone.now if message.sent?
65
73
 
66
74
  message
67
75
  end
68
76
 
69
77
  def block
70
- message.status = 'blocked'
78
+ message.status = "blocked"
71
79
  message
72
80
  end
73
81
 
74
82
  private
75
83
 
76
84
  def message_url(message)
77
- Nuntius::Engine.routes.url_helpers.message_url(message.id, host: Nuntius.config.host(message))
85
+ Nuntius::Engine.routes.url_helpers.message_url(message.id, host: Nuntius.config.host.call(message))
78
86
  end
79
87
  end
80
88
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class TeamsTeamsProvider < BaseProvider
5
+ transport :teams
6
+
7
+ def deliver
8
+ # NOTE: Attachments are not supported
9
+ # https://learn.microsoft.com/en-us/power-automate/overview-adaptive-cards
10
+ # https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet
11
+
12
+ args = (message.payload || {}).merge(text: message.text)
13
+ response = Faraday.post(message[:to], JSON.dump(args), {"Content-Type": "application/json"})
14
+
15
+ message.status = if response.status == 200
16
+ "sent"
17
+ else
18
+ "undelivered"
19
+ end
20
+
21
+ message
22
+ end
23
+ end
24
+ end
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'twilio-ruby'
3
+ require "twilio-ruby"
4
4
 
5
5
  module Nuntius
6
6
  # Send SMS messages using twilio.com
7
7
  class TwilioSmsProvider < BaseProvider
8
8
  transport :sms
9
9
 
10
- setting_reader :auth_token, required: true, description: 'Authentication token'
11
- setting_reader :sid, required: true, description: 'Application SID, see Twilio console'
10
+ setting_reader :auth_token, required: true, description: "Authentication token"
11
+ setting_reader :sid, required: true, description: "Application SID, see Twilio console"
12
12
  setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
13
13
 
14
14
  # Twilio statusses: queued, failed, sent, delivered, or undelivered
15
- states %w[failed undelivered] => 'undelivered', 'delivered' => 'delivered'
15
+ states %w[failed undelivered] => "undelivered", "delivered" => "delivered"
16
16
 
17
17
  def deliver
18
18
  response = client.messages.create(from: message.from.present? ? message.from : from, to: message.to, body: message.text)
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'twilio-ruby'
3
+ require "twilio-ruby"
4
4
 
5
5
  module Nuntius
6
6
  # Send Voice call messages using twilio.com
7
7
  class TwilioVoiceProvider < BaseProvider
8
8
  transport :voice
9
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'
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
13
  setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
14
14
 
15
15
  # Twilio statusses: queued, failed, sent, delivered, or undelivered
16
- states %w[failed undelivered] => 'undelivered', %w[delivered completed] => 'delivered'
16
+ states %w[failed undelivered] => "undelivered", %w[delivered completed] => "delivered"
17
17
 
18
18
  def deliver
19
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)
20
+ response = client.calls.create(from: message.from.present? ? message.from : from, to: message.to, method: "POST", url: callback_url)
21
21
  message.provider_id = response.sid
22
22
  message.status = translated_status(response.status)
23
23
  message
@@ -36,9 +36,9 @@ module Nuntius
36
36
  twiml = script_for_path(message, "/#{params[:path]}", params)
37
37
 
38
38
  if twiml
39
- [200, { 'Content-Type' => 'application/xml' }, [twiml[:body]]]
39
+ [200, {"Content-Type" => "application/xml"}, [twiml[:body]]]
40
40
  else
41
- [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['Not found']]
41
+ [404, {"Content-Type" => "text/html; charset=utf-8"}, ["Not found"]]
42
42
  end
43
43
  end
44
44
 
@@ -48,16 +48,16 @@ module Nuntius
48
48
  @client ||= Twilio::REST::Client.new(sid, auth_token)
49
49
  end
50
50
 
51
- def script_for_path(message, path = '/', _params)
51
+ def script_for_path(message, path = "/", _params)
52
52
  scripts = message.text.delete("\r").split("\n\n")
53
53
 
54
54
  scripts = scripts.map do |script|
55
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: '/' }
56
+ payload = preamble.metadata ? preamble.content : script
57
+ payload = payload.gsub("{{url}}", callback_url)
58
+ metadata = preamble.metadata || {path: "/"}
59
59
 
60
- { headers: metadata.with_indifferent_access, body: payload }
60
+ {headers: metadata.with_indifferent_access, body: payload}
61
61
  end
62
62
 
63
63
  scripts.find { |s| s[:headers][:path] == path }
@@ -20,13 +20,13 @@ module Nuntius
20
20
  end
21
21
 
22
22
  def perform
23
- if context.notification['Type'] == 'SubscriptionConfirmation'
24
- Nuntius.config.logger.info('Confirming SNS subscription')
25
- HTTPClient.get(body['SubscribeURL'])
23
+ if context.notification["Type"] == "SubscriptionConfirmation"
24
+ Nuntius.config.logger.info("Confirming SNS subscription")
25
+ Faraday.get(body["SubscribeURL"])
26
26
  return
27
27
  end
28
28
 
29
- type = context.notification['notificationType']
29
+ type = context.notification["notificationType"]
30
30
 
31
31
  unless message_id
32
32
  Nuntius.config.logger.warn("SNS / SES message could not determine message id: #{context.notification}")
@@ -39,11 +39,11 @@ module Nuntius
39
39
  Nuntius.config.logger.info("SNS /SES updating message #{message.id} for #{type}")
40
40
 
41
41
  case type
42
- when 'Delivery'
42
+ when "Delivery"
43
43
  process_delivery
44
- when 'Bounce'
44
+ when "Bounce"
45
45
  process_bounce
46
- when 'Complaint'
46
+ when "Complaint"
47
47
  process_complaint
48
48
  else
49
49
  false
@@ -54,24 +54,24 @@ module Nuntius
54
54
 
55
55
  def process_delivery
56
56
  message.status = :delivered
57
- message.metadata[:feedback] = { 'type': 'delivery', 'info': context.notification['delivery'] }
57
+ message.metadata[:feedback] = {type: "delivery", info: context.notification["delivery"]}
58
58
  message.save!
59
59
  end
60
60
 
61
61
  def process_bounce
62
62
  message.status = :bounced
63
- message.metadata[:feedback] = { 'type': 'bounce', 'info': context.notification['bounce'] }
63
+ message.metadata[:feedback] = {type: "bounce", info: context.notification["bounce"]}
64
64
  message.save!
65
65
  end
66
66
 
67
67
  def process_complaint
68
68
  message.status = :complaint
69
- message.metadata[:feedback] = { 'type': 'complaint', 'info': context.notification['complaint'] }
69
+ message.metadata[:feedback] = {type: "complaint", info: context.notification["complaint"]}
70
70
  message.save!
71
71
  end
72
72
 
73
73
  def message_id
74
- @message_id ||= context.notification.dig('mail', 'commonHeaders', 'messageId')&.[](1...-1)
74
+ @message_id ||= context.notification.dig("mail", "commonHeaders", "messageId")&.[](1...-1)
75
75
  end
76
76
 
77
77
  def message
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nuntius
4
+ class DeliverCampaignService < ApplicationService
5
+ context do
6
+ attribute :campaign
7
+ validates :campaign, presence: true
8
+ end
9
+
10
+ delegate :campaign, to: :context
11
+
12
+ def perform
13
+ deliver
14
+ campaign.sent!
15
+ end
16
+
17
+ def deliver
18
+ transporter = BaseTransport.class_from_name(campaign.transport).new
19
+ campaign.list.subscribers.subscribed.each do |subscriber|
20
+ transporter.deliver(new_message(subscriber))
21
+ end
22
+ end
23
+
24
+ def new_message(subscriber, assigns = {})
25
+ assigns["subscriber"] = subscriber
26
+ if subscriber.nuntiable
27
+ name = Nuntius::BaseMessenger.liquid_variable_name_for(subscriber.nuntiable)
28
+ assigns[name] = subscriber.nuntiable
29
+ end
30
+ message = Nuntius::Message.new(transport: campaign.transport, campaign: campaign, nuntiable: subscriber.nuntiable, metadata: campaign.metadata)
31
+
32
+ locale_proc = Nuntius::BaseMessenger.messenger_for_obj(subscriber.nuntiable).locale
33
+ locale = instance_exec(subscriber.nuntiable, &locale_proc) if subscriber.nuntiable && locale_proc
34
+
35
+ message.from = render(:from, assigns, locale)
36
+ message.to = case campaign.transport
37
+ when "mail"
38
+ subscriber.email
39
+ when "sms"
40
+ subscriber.phone_number
41
+ when "voice"
42
+ subscriber.phone_number
43
+ end
44
+
45
+ message.subject = render(:subject, assigns, locale)
46
+ message.html = render(:html, assigns, locale, layout: campaign.layout&.data)
47
+
48
+ message
49
+ end
50
+
51
+ def translation_scope
52
+ scope = %w[nuntius]
53
+ scope << campaign.layout.name.underscore.tr(" ", "_") if layout
54
+ scope.join(".")
55
+ end
56
+
57
+ private
58
+
59
+ def render(attr, assigns, locale, options = {})
60
+ I18n.with_locale(locale) do
61
+ ::Liquidum.render(campaign.public_send(attr), {assigns: assigns.merge("campaign" => campaign), registers: {"campaign" => campaign}}.merge(options))
62
+ end
63
+ end
64
+ end
65
+ end
@@ -7,8 +7,9 @@ module Nuntius
7
7
  end
8
8
 
9
9
  def perform
10
- context.inbound_message.update(status: 'delivered')
11
- Nuntius::BaseMessageBox.deliver(context.inbound_message)
10
+ context.inbound_message.update!(status: "delivered")
11
+ result = Nuntius::BaseMessageBox.deliver(context.inbound_message)
12
+ context.inbound_message.update!(status: "processed") if result
12
13
  end
13
14
  end
14
15
  end
@@ -8,27 +8,34 @@ module Nuntius
8
8
 
9
9
  def perform
10
10
  Mail::IMAP.new(context.settings).all do |message, imap, uid|
11
- inbound_message = Nuntius::InboundMessage.find_or_create_by!(transport: 'mail', provider: 'imap', provider_id: message.message_id,
12
- digest: Digest::SHA256.hexdigest(message.to_s), status: 'pending')
13
- inbound_message.from = message.from
14
- inbound_message.to = message.to
15
- inbound_message.text = message.body
16
- # inbound_message.metadata = params
17
- inbound_message.save!
11
+ inbound_message = Nuntius::InboundMessage.find_or_initialize_by(transport: "mail", provider: "imap", provider_id: message.message_id)
12
+ if inbound_message.new_record?
13
+ inbound_message.digest = Digest::SHA256.hexdigest(message.to_s)
14
+ inbound_message.from = message.from
15
+ inbound_message.to = message.to
16
+ inbound_message.text = message.body
17
+ inbound_message.save!
18
18
 
19
- if inbound_message.raw_message.attached?
20
- if Digest::SHA256.hexdigest(message.to_s) == Digest::SHA256.hexdigest(inbound_message.raw_message.download)
21
- # Only if we have an attachment and it's digest is the same as we find in the mailbox, delete!
22
- # This never happens on the first round of fetching it.
23
- imap.store(uid, '+FLAGS', [Net::IMAP::DELETED])
24
- imap.expunge
25
- end
26
- else
27
19
  si = StringIO.new
28
20
  si.write(message.to_s)
29
21
  si.rewind
30
22
  inbound_message.raw_message.attach(io: si, filename: message.message_id)
31
- Nuntius::ProcessInboundMessageJob.perform_later(inbound_message)
23
+
24
+ elsif inbound_message.status == "processed" && Digest::SHA256.hexdigest(message.to_s) == Digest::SHA256.hexdigest(inbound_message.raw_message.download)
25
+
26
+ if context.settings[:delete_after_processing]
27
+ imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
28
+ imap.expunge
29
+ else
30
+ existing_mailboxes = imap.list("", "*").map(&:name)
31
+ unless existing_mailboxes.include?("Archive")
32
+ imap.create("Archive")
33
+ end
34
+ imap.uid_copy(uid, "Archive")
35
+
36
+ imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
37
+ imap.expunge
38
+ end
32
39
  end
33
40
  end
34
41
  end
@@ -7,7 +7,15 @@ class NuntiusCampaignsTable < ActionTable::ActionTable
7
7
  column(:metadata) { |campaign| Nuntius.config.metadata_humanize(campaign.metadata) }
8
8
  column(:transport)
9
9
  column(:state)
10
- column(:list) { |campaign| campaign.list.name }
10
+ column(:list, sort_field: "nuntius_lists.name") { |campaign| campaign.list.name }
11
+
12
+ column :actions, title: "", sortable: false do |campaign|
13
+ next unless campaign.can_publish?
14
+
15
+ content_tag(:span, class: "btn-group btn-group-xs") do
16
+ concat link_to(content_tag(:i, nil, class: "fa fa-paper-plane"), nuntius.publish_admin_campaign_path(campaign), data: {turbo_confirm: "Are you sure you want to send out this campaign?", turbo_method: :post}, class: "btn btn-xs btn-danger")
17
+ end
18
+ end
11
19
 
12
20
  initial_order :name, :asc
13
21
 
@@ -17,5 +25,6 @@ class NuntiusCampaignsTable < ActionTable::ActionTable
17
25
 
18
26
  def scope
19
27
  @scope = Nuntius::Campaign.visible
28
+ @scope = Nuntius::Campaign.joins(:list)
20
29
  end
21
30
  end