nuntius 0.1.1 → 1.0.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +199 -63
- data/Rakefile +62 -1
- data/app/controllers/nuntius/admin/campaigns_controller.rb +56 -0
- data/app/controllers/nuntius/admin/layouts/attachments_controller.rb +32 -0
- data/app/controllers/nuntius/admin/layouts_controller.rb +50 -0
- data/app/controllers/nuntius/admin/lists/subscribers_controller.rb +50 -0
- data/app/controllers/nuntius/admin/lists_controller.rb +46 -0
- data/app/controllers/nuntius/admin/locales_controller.rb +46 -0
- data/app/controllers/nuntius/admin/messages_controller.rb +24 -0
- data/app/controllers/nuntius/admin/templates_controller.rb +56 -0
- data/app/controllers/nuntius/api/events_controller.rb +16 -0
- data/app/controllers/nuntius/application_admin_controller.rb +11 -0
- data/app/controllers/nuntius/application_controller.rb +12 -0
- data/app/controllers/nuntius/callbacks_controller.rb +21 -0
- data/app/controllers/nuntius/dashboard_controller.rb +12 -0
- data/app/controllers/nuntius/feedback_controller.rb +17 -0
- data/app/controllers/nuntius/inbound_messages/twilio_inbound_smses_controller.rb +56 -0
- data/app/controllers/nuntius/messages_controller.rb +13 -0
- data/app/drops/nuntius/application_drop.rb +17 -0
- data/app/drops/nuntius/campaign_drop.rb +7 -0
- data/app/drops/nuntius/layout_drop.rb +11 -0
- data/app/drops/nuntius/message_drop.rb +11 -0
- data/app/drops/nuntius/template_drop.rb +7 -0
- data/app/exceptions/nuntius/base_exception.rb +4 -0
- data/app/exceptions/nuntius/missing_messenger_exception.rb +4 -0
- data/app/helpers/nuntius/application_helper.rb +78 -0
- data/app/jobs/nuntius/application_job.rb +9 -0
- data/app/jobs/nuntius/deliver_inbound_message_job.rb +10 -0
- data/app/jobs/nuntius/messenger_job.rb +17 -0
- data/app/jobs/nuntius/process_inbound_message_job.rb +10 -0
- data/app/jobs/nuntius/purge_message_job.rb +13 -0
- data/app/jobs/nuntius/retrieve_mail_job.rb +18 -0
- data/app/jobs/nuntius/transport_delivery_job.rb +32 -0
- data/app/jobs/nuntius/transport_refresh_job.rb +22 -0
- data/app/message_boxes/nuntius/base_message_box.rb +75 -0
- data/app/messengers/nuntius/base_messenger.rb +193 -0
- data/app/messengers/nuntius/custom_messenger.rb +6 -0
- data/app/models/nuntius/application_record.rb +11 -0
- data/app/models/nuntius/attachment.rb +13 -0
- data/app/models/nuntius/campaign.rb +75 -0
- data/app/models/nuntius/concerns/metadata_scoped.rb +31 -0
- data/app/models/nuntius/concerns/yamlify.rb +23 -0
- data/app/models/nuntius/inbound_message.rb +14 -0
- data/app/models/nuntius/layout.rb +13 -0
- data/app/models/nuntius/list.rb +10 -0
- data/app/models/nuntius/locale.rb +11 -0
- data/app/models/nuntius/message.rb +148 -0
- data/app/models/nuntius/subscriber.rb +12 -0
- data/app/models/nuntius/template.rb +102 -0
- data/app/presenters/application_presenter.rb +28 -0
- data/app/presenters/template_presenter.rb +17 -0
- data/app/providers/nuntius/apnotic_push_provider.rb +44 -0
- data/app/providers/nuntius/base_provider.rb +72 -0
- data/app/providers/nuntius/firebase_push_provider.rb +26 -0
- data/app/providers/nuntius/houston_push_provider.rb +40 -0
- data/app/providers/nuntius/message_bird_sms_provider.rb +39 -0
- data/app/providers/nuntius/slack_slack_provider.rb +36 -0
- data/app/providers/nuntius/smtp_mail_provider.rb +80 -0
- data/app/providers/nuntius/twilio_sms_provider.rb +37 -0
- data/app/providers/nuntius/twilio_voice_provider.rb +70 -0
- data/app/reportlets/nuntius/application_reportlet.rb +35 -0
- data/app/reportlets/nuntius/daily_messages_per_template_reportlet.rb +50 -0
- data/app/runners/nuntius/application_runner.rb +6 -0
- data/app/runners/nuntius/basic_application_runner.rb +15 -0
- data/app/runners/nuntius/timebased_events_runner.rb +15 -0
- data/app/services/nuntius/application_service.rb +7 -0
- data/app/services/nuntius/aws_sns_processor_service.rb +81 -0
- data/app/services/nuntius/deliver_inbound_message_service.rb +14 -0
- data/app/services/nuntius/retrieve_inbound_mail_service.rb +36 -0
- data/app/tables/nuntius_campaigns_table.rb +21 -0
- data/app/tables/nuntius_layouts_table.rb +24 -0
- data/app/tables/nuntius_lists_table.rb +19 -0
- data/app/tables/nuntius_locales_table.rb +18 -0
- data/app/tables/nuntius_messages_table.rb +36 -0
- data/app/tables/nuntius_subscribers_table.rb +19 -0
- data/app/tables/nuntius_templates_table.rb +30 -0
- data/app/transports/nuntius/base_transport.rb +36 -0
- data/app/transports/nuntius/mail_transport.rb +35 -0
- data/app/transports/nuntius/push_transport.rb +15 -0
- data/app/transports/nuntius/slack_transport.rb +6 -0
- data/app/transports/nuntius/sms_transport.rb +6 -0
- data/app/transports/nuntius/voice_transport.rb +6 -0
- data/app/validators/liquid_validator.rb +11 -0
- data/app/views/nuntius/admin/campaigns/edit.html.slim +49 -0
- data/app/views/nuntius/admin/campaigns/index.html.slim +2 -0
- data/app/views/nuntius/admin/layouts/attachments/_attachments.html.slim +28 -0
- data/app/views/nuntius/admin/layouts/attachments/_index.html.slim +2 -0
- data/app/views/nuntius/admin/layouts/attachments/create.json.jbuilder +5 -0
- data/app/views/nuntius/admin/layouts/edit.html.slim +24 -0
- data/app/views/nuntius/admin/layouts/index.html.slim +2 -0
- data/app/views/nuntius/admin/lists/edit.html.slim +14 -0
- data/app/views/nuntius/admin/lists/index.html.slim +2 -0
- data/app/views/nuntius/admin/lists/subscribers/edit.html.slim +14 -0
- data/app/views/nuntius/admin/locales/edit.html.slim +14 -0
- data/app/views/nuntius/admin/locales/index.html.slim +2 -0
- data/app/views/nuntius/admin/messages/index.html.slim +2 -0
- data/app/views/nuntius/admin/messages/show.html.slim +56 -0
- data/app/views/nuntius/admin/templates/edit.html.slim +81 -0
- data/app/views/nuntius/admin/templates/index.html.slim +2 -0
- data/app/views/nuntius/dashboard/show.html.slim +7 -0
- data/app/views/nuntius/messages/show.html.slim +7 -0
- data/config/locales/en.yml +22 -0
- data/config/locales/nl.yml +22 -0
- data/config/routes.rb +38 -0
- data/config/webpack/development.js +5 -0
- data/config/webpack/environment.js +4 -0
- data/config/webpack/production.js +5 -0
- data/config/webpack/test.js +5 -0
- data/config/webpacker.yml +119 -0
- data/db/migrate/20190301201541_create_nuntius_templates.rb +27 -0
- data/db/migrate/20190301202436_create_nuntius_messages.rb +30 -0
- data/db/migrate/20190322112815_create_nuntius_lists.rb +12 -0
- data/db/migrate/20190322114340_create_nuntius_campaigns.rb +18 -0
- data/db/migrate/20190322121338_create_nuntius_subscribers.rb +17 -0
- data/db/migrate/20190327123535_add_metadata_to_models.rb +8 -0
- data/db/migrate/20190327124407_create_nuntius_layouts.rb +24 -0
- data/db/migrate/20190327143921_add_campaign_to_message.rb +7 -0
- data/db/migrate/20190327155112_add_state_to_campaign.rb +7 -0
- data/db/migrate/20190412103335_add_enabled_to_templates.rb +7 -0
- data/db/migrate/20190417125153_change_message_status_default.rb +9 -0
- data/db/migrate/20190417144554_add_nuntiable_to_subscriber.rb +7 -0
- data/db/migrate/20190521135011_add_payload_to_nuntius_messages.rb +7 -0
- data/db/migrate/20190522122657_add_metadata_to_nuntius_message.rb +7 -0
- data/db/migrate/20190825080757_update_nuntius_message_sending_to_sent.rb +11 -0
- data/db/migrate/20200220154927_change_nuntius_templates_payload_type.rb +7 -0
- data/db/migrate/20200224132337_create_nuntius_locales.rb +13 -0
- data/db/migrate/20200318095339_create_nuntius_attachments.rb +12 -0
- data/db/migrate/20200407050646_add_interval_to_nuntius_template.rb +5 -0
- data/db/migrate/20200430131219_make_html_processing_optional.rb +6 -0
- data/db/migrate/20200430154032_make_html_processing_optional_revert.rb +6 -0
- data/db/migrate/20201121185718_create_nuntius_inbound_messages.rb +21 -0
- data/db/migrate/20220412114148_add_last_sent_to_message.rb +5 -0
- data/lib/nuntius/active_record_helpers.rb +28 -0
- data/lib/nuntius/active_storage_helpers.rb +8 -0
- data/lib/nuntius/configuration.rb +97 -0
- data/lib/nuntius/deprecator.rb +8 -0
- data/lib/nuntius/devise.rb +19 -0
- data/lib/nuntius/engine.rb +46 -0
- data/lib/nuntius/i18n_store.rb +100 -0
- data/lib/nuntius/liquid/tags/attach_tag.rb +44 -0
- data/lib/nuntius/mail_allow_list.rb +23 -0
- data/lib/nuntius/nuntiable.rb +24 -0
- data/lib/nuntius/state_machine.rb +19 -0
- data/lib/nuntius/transactio.rb +30 -0
- data/lib/nuntius/version.rb +3 -1
- data/lib/nuntius.rb +74 -8
- data/lib/preamble.rb +87 -0
- data/lib/tasks/nuntius_tasks.rake +27 -0
- metadata +532 -67
- data/.gitignore +0 -4
- data/Gemfile +0 -4
- data/lib/nuntius/encodings/url_safe_base64.rb +0 -26
- data/lib/nuntius/encodings.rb +0 -9
- data/lib/nuntius/envelope.rb +0 -45
- data/lib/nuntius/key.rb +0 -47
- data/lib/nuntius/messenger.rb +0 -37
- data/nuntius.gemspec +0 -23
- data/spec/keys/alice.pem +0 -27
- data/spec/keys/alice.pub +0 -9
- data/spec/keys/bob.pem +0 -27
- data/spec/keys/bob.pub +0 -9
- data/spec/nuntius/encodings/url_safe_base64_spec.rb +0 -36
- data/spec/nuntius/envelope_spec.rb +0 -6
- data/spec/nuntius/key_spec.rb +0 -12
- data/spec/nuntius/messenger_spec.rb +0 -26
- data/spec/spec_helper.rb +0 -9
- data/spec/support/keys.rb +0 -3
@@ -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,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
|