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,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
###
|
4
|
+
# Process feedback from the amazon mail delivery service. Feedback looks like this:
|
5
|
+
# {"Type"=>"Notification", "MessageId"=>"a00966b6-51a5-53f7-a966-35d0e8f878ce", "TopicArn"=>"arn:aws:sns:eu-west-1:218893591873:localexpress-email-feedback",
|
6
|
+
# "Message"=>"{\"notificationType\":\"Delivery\",\"mail\":{\"timestamp\":\"2018-05-04T19:40:12.637Z\",\"source\":\"do_not_reply@localexpress.nl\",\
|
7
|
+
# "sourceArn\":\"arn:aws:ses:eu-west-1:218893591873:identity/localexpress.nl\",\"sourceIp\":\"52.59.100.253\",\"sendingAccountId\":\"218893591873\",
|
8
|
+
# \"messageId\":\"010201632cab47dd-924f80f8-4ae2-4ba0-9c8b-050db003711a-000000\",\"destination\":[\"mercedes.tuin@gmail.com\"],
|
9
|
+
# \"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from localhost.localdomain (ec2-52-59-100-253.eu-central-1.compute.amazonaws.com [52.59.100.253]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-2762311919) id 4AtiQr5QXc6CZ3uJYW4D for mercedes.tuin@gmail.com; Fri, 04 May 2018 19:40:12 +0000 (UTC)\"},{\"nam
|
10
|
+
#
|
11
|
+
#
|
12
|
+
|
13
|
+
# See: https://docs.aws.amazon.com/ses/latest/dg/notification-examples.html
|
14
|
+
module Nuntius
|
15
|
+
class AwsSnsProcessorService < ApplicationService
|
16
|
+
context do
|
17
|
+
attribute :notification, typecaster: lambda { |value|
|
18
|
+
value.with_indifferent_access
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
if context.notification['Type'] == 'SubscriptionConfirmation'
|
24
|
+
Nuntius.config.logger.info('Confirming SNS subscription')
|
25
|
+
HTTPClient.get(body['SubscribeURL'])
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
type = context.notification['notificationType']
|
30
|
+
|
31
|
+
unless message_id
|
32
|
+
Nuntius.config.logger.warn("SNS / SES message could not determine message id: #{context.notification}")
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
unless message
|
36
|
+
Nuntius.config.logger.warn("SNS / SES message for unknown message with message id: #{message_id}")
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
Nuntius.config.logger.info("SNS /SES updating message #{message.id} for #{type}")
|
40
|
+
|
41
|
+
case type
|
42
|
+
when 'Delivery'
|
43
|
+
process_delivery
|
44
|
+
when 'Bounce'
|
45
|
+
process_bounce
|
46
|
+
when 'Complaint'
|
47
|
+
process_complaint
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def process_delivery
|
56
|
+
message.status = :delivered
|
57
|
+
message.metadata[:feedback] = { 'type': 'delivery', 'info': context.notification['delivery'] }
|
58
|
+
message.save!
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_bounce
|
62
|
+
message.status = :bounced
|
63
|
+
message.metadata[:feedback] = { 'type': 'bounce', 'info': context.notification['bounce'] }
|
64
|
+
message.save!
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_complaint
|
68
|
+
message.status = :complaint
|
69
|
+
message.metadata[:feedback] = { 'type': 'complaint', 'info': context.notification['complaint'] }
|
70
|
+
message.save!
|
71
|
+
end
|
72
|
+
|
73
|
+
def message_id
|
74
|
+
@message_id ||= context.notification.dig('mail', 'commonHeaders', 'messageId')&.[](1...-1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def message
|
78
|
+
@message ||= Nuntius::Message.find_by(provider_id: message_id)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class DeliverInboundMessageService < ApplicationService
|
5
|
+
context do
|
6
|
+
attribute :inbound_message
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
context.inbound_message.update(status: 'delivered')
|
11
|
+
Nuntius::BaseMessageBox.deliver(context.inbound_message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class RetrieveInboundMailService < ApplicationService
|
5
|
+
context do
|
6
|
+
attribute :settings
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
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!
|
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
|
+
si = StringIO.new
|
28
|
+
si.write(message.to_s)
|
29
|
+
si.rewind
|
30
|
+
inbound_message.raw_message.attach(io: si, filename: message.message_id)
|
31
|
+
Nuntius::ProcessInboundMessageJob.perform_later(inbound_message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusCampaignsTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Campaign
|
5
|
+
|
6
|
+
column(:name)
|
7
|
+
column(:metadata) { |campaign| Nuntius.config.metadata_humanize(campaign.metadata) }
|
8
|
+
column(:transport)
|
9
|
+
column(:state)
|
10
|
+
column(:list) { |campaign| campaign.list.name }
|
11
|
+
|
12
|
+
initial_order :name, :asc
|
13
|
+
|
14
|
+
row_link { |campaign| nuntius.edit_admin_campaign_path(campaign) }
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def scope
|
19
|
+
@scope = Nuntius::Campaign.visible
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusLayoutsTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Layout
|
5
|
+
|
6
|
+
column(:name) { |layout| layout.name }
|
7
|
+
column(:metadata) { |layout| Nuntius.config.metadata_humanize(layout.metadata) }
|
8
|
+
|
9
|
+
column :actions, title: '', sortable: false do |layout|
|
10
|
+
content_tag(:span, class: 'btn-group btn-group-xs') do
|
11
|
+
concat link_to(content_tag(:i, nil, class: 'fa fa-trash'), nuntius.admin_layout_url(layout), data: { turbo_confirm: 'Are you sure you want to delete the layout?', turbo_method: :delete }, class: 'btn btn-xs btn-danger')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initial_order :name, :asc
|
16
|
+
|
17
|
+
row_link { |layout| nuntius.edit_admin_layout_path(layout) }
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def scope
|
22
|
+
@scope = Nuntius::Layout.visible
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusListsTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::List
|
5
|
+
|
6
|
+
column(:name)
|
7
|
+
column(:metadata) { |list| Nuntius.config.metadata_humanize(list.metadata) }
|
8
|
+
column(:subscribers) { |list| list.subscribers.count || '-' }
|
9
|
+
|
10
|
+
initial_order :name, :asc
|
11
|
+
|
12
|
+
row_link { |list| nuntius.edit_admin_list_path(list) }
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def scope
|
17
|
+
@scope = Nuntius::List.visible
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusLocalesTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Locale
|
5
|
+
|
6
|
+
column(:key)
|
7
|
+
column(:metadata) { |locale| Nuntius.config.metadata_humanize(locale.metadata) }
|
8
|
+
|
9
|
+
initial_order :mkey, :asc
|
10
|
+
|
11
|
+
row_link { |locale| nuntius.edit_admin_locale_path(locale) }
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def scope
|
16
|
+
@scope = Nuntius::Locale.visible
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusMessagesTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Message
|
5
|
+
|
6
|
+
column(:to)
|
7
|
+
column(:created_at) { |message| ln(message.created_at) }
|
8
|
+
column(:last_sent_at) { |message| ln(message.last_sent_at) }
|
9
|
+
column(:origin) do |message|
|
10
|
+
link_to message.campaign&.name, nuntius.edit_admin_campaign_path(message.campaign) if message.campaign
|
11
|
+
link_to message.template&.description, nuntius.edit_admin_template_path(message.template) if message.template
|
12
|
+
end
|
13
|
+
column(:subject) do |message|
|
14
|
+
if message.nuntiable
|
15
|
+
link_to "#{message.nuntiable_type} [#{message.nuntiable}]", begin
|
16
|
+
url_for(message.nuntiable)
|
17
|
+
rescue StandardError
|
18
|
+
'#'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
column(:status)
|
23
|
+
|
24
|
+
initial_order :created_at, :desc
|
25
|
+
|
26
|
+
row_link { |message| nuntius.admin_message_path(message) }
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def scope
|
31
|
+
@scope = Nuntius::Message.visible
|
32
|
+
@scope = @scope.where(nuntiable_id: params[:nuntiable_id], nuntiable_type: params[:nuntiable_type]) if params[:nuntiable_id]
|
33
|
+
@scope = @scope.where(template_id: params[:template_id]) if params[:template_id]
|
34
|
+
@scope
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusSubscribersTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Subscriber
|
5
|
+
|
6
|
+
column(:name, sortable: false) { |subscriber| subscriber.name }
|
7
|
+
column(:email)
|
8
|
+
column(:phonenumber)
|
9
|
+
|
10
|
+
initial_order :name, :asc
|
11
|
+
|
12
|
+
row_link { |subscriber| nuntius.edit_admin_list_subscriber_path(params[:list_id], subscriber) }
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def scope
|
17
|
+
@scope = Nuntius::Subscriber.where(list_id: params[:list_id])
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NuntiusTemplatesTable < ActionTable::ActionTable
|
4
|
+
model Nuntius::Template
|
5
|
+
|
6
|
+
column(:description)
|
7
|
+
column(:enabled, as: :boolean)
|
8
|
+
column(:klass)
|
9
|
+
column(:event)
|
10
|
+
column(:"# messages") { |template| link_to template.messages.count, nuntius.admin_messages_path(template_id: template.id) }
|
11
|
+
|
12
|
+
column(:metadata) { |template| Nuntius.config.metadata_humanize(template.metadata) }
|
13
|
+
column(:created_at) { |flow| ln(flow.created_at) }
|
14
|
+
|
15
|
+
column :actions, title: '', sortable: false do |template|
|
16
|
+
content_tag(:span, class: 'btn-group btn-group-xs') do
|
17
|
+
concat link_to(content_tag(:i, nil, class: 'fa fa-trash'), nuntius.admin_template_path(template), data: { turbo_confirm: 'Are you sure you want to delete the template?', turbo_method: :delete }, class: 'btn btn-xs btn-danger')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initial_order :description, :asc
|
22
|
+
|
23
|
+
row_link { |template| nuntius.edit_admin_template_path(template) }
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def scope
|
28
|
+
@scope = Nuntius::Template.visible
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class BaseTransport
|
5
|
+
def kind
|
6
|
+
self.class.name.demodulize.gsub(/Transport$/, '').underscore.to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
def deliver(message)
|
10
|
+
priority = 1
|
11
|
+
wait_time = 0
|
12
|
+
message.update(transport: kind.to_s) if message.transport.blank?
|
13
|
+
while (providers_for_priority = providers(priority)).present?
|
14
|
+
time_out = 0
|
15
|
+
providers_for_priority.each do |hash|
|
16
|
+
message.update(provider: hash[:provider].to_s) if message.provider.blank?
|
17
|
+
|
18
|
+
Nuntius::TransportDeliveryJob.set(wait: !Rails.env.development? && wait_time).perform_later(hash[:provider].to_s, message)
|
19
|
+
time_out += hash[:timeout].seconds if hash[:timeout].positive?
|
20
|
+
end
|
21
|
+
wait_time += time_out
|
22
|
+
priority += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def providers(priority = nil)
|
27
|
+
results = Nuntius.config.providers[kind].to_a
|
28
|
+
results = results.select { |provider| provider[:priority] == priority } if priority
|
29
|
+
results
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.class_from_name(name)
|
33
|
+
Nuntius.const_get("#{name}_transport".camelize)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class MailTransport < BaseTransport
|
5
|
+
# We split per email address, to allow easy resends
|
6
|
+
def deliver(message)
|
7
|
+
message.html = Inky::Core.new.release_the_kraken(message.html)
|
8
|
+
|
9
|
+
premailer = Premailer.new(message.html, with_html_string: true)
|
10
|
+
message.html = premailer.to_inline_css
|
11
|
+
message.text = premailer.to_plain_text
|
12
|
+
|
13
|
+
message.request_id = SecureRandom.uuid
|
14
|
+
|
15
|
+
tos = message.to.split(/[\s;,]+/)
|
16
|
+
|
17
|
+
messages = []
|
18
|
+
message.to = tos.first
|
19
|
+
messages << message
|
20
|
+
|
21
|
+
tos[1..-1].each do |to|
|
22
|
+
# FIXME: Sadly this also duplicates the attachments
|
23
|
+
new_message = message.deep_dup
|
24
|
+
new_message.to = to
|
25
|
+
new_message.attachments = message.attachments if message.attachments.present?
|
26
|
+
|
27
|
+
messages << new_message
|
28
|
+
end
|
29
|
+
|
30
|
+
messages.each { |m| super(m) }
|
31
|
+
|
32
|
+
messages.first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class PushTransport < BaseTransport
|
5
|
+
# We split per email address, to allow easy resends
|
6
|
+
def deliver(message)
|
7
|
+
message.request_id = SecureRandom.uuid
|
8
|
+
message.to.split(',').each do |to|
|
9
|
+
new_message = message.dup
|
10
|
+
new_message.to = to
|
11
|
+
super(new_message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LiquidValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
return if value.blank?
|
6
|
+
|
7
|
+
Liquid::Template.parse(value)
|
8
|
+
rescue Liquid::SyntaxError => e
|
9
|
+
record.errors[attribute] << (options[:message] || "is not valid liquid: #{e.message}")
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
- if @campaign.draft?
|
2
|
+
= sts.form_for [:admin, @campaign] do |f|
|
3
|
+
= sts.card title: t('.campaign'), icon: 'fal fa-megaphone' do |card|
|
4
|
+
- card.action
|
5
|
+
= f.submit
|
6
|
+
|
7
|
+
.grid.grid-cols-12.gap-4 data-controller="toggle"
|
8
|
+
.col-span-12
|
9
|
+
= f.input :name
|
10
|
+
|
11
|
+
.col-span-4
|
12
|
+
= f.input :transport, collection: Nuntius.config.transports, include_blank: false, input_html: {data: { 'toggle-target' => 'input'}}
|
13
|
+
.col-span-4
|
14
|
+
= f.association :layout, collection: @layouts
|
15
|
+
.col-span-4
|
16
|
+
= f.association :list, collection: @lists
|
17
|
+
|
18
|
+
.col-span-12
|
19
|
+
= f.input :from, hint: 'You can leave this blank, only fill this in if you want to override the default'
|
20
|
+
|
21
|
+
.col-span-12
|
22
|
+
.grid.grid-cols-12.gap-4 data-toggle-target="insertion"
|
23
|
+
|
24
|
+
template data-toggle-target='toggleable' data-toggle-value='mail'
|
25
|
+
.col-span-12
|
26
|
+
= f.input :subject
|
27
|
+
.col-span-12
|
28
|
+
= f.input :html, as: :hidden
|
29
|
+
trix-editor input="campaign_html" style="background-color: #fff; height: 400px; margin-bottom: 20px;"
|
30
|
+
|
31
|
+
template data-toggle-target='toggleable' data-toggle-value='voice'
|
32
|
+
.col-span-12
|
33
|
+
= f.input :text, as: :editor, mode: 'text/plain'
|
34
|
+
|
35
|
+
|
36
|
+
- else
|
37
|
+
h2 = @campaign.name
|
38
|
+
p We're sending this campaign, you can no longer make any changes. This is the audience we're sending it to:
|
39
|
+
|
40
|
+
table.table
|
41
|
+
thead
|
42
|
+
tr
|
43
|
+
th Status
|
44
|
+
th To
|
45
|
+
tbody
|
46
|
+
- for message in @messages
|
47
|
+
tr
|
48
|
+
td = link_to message.status, admin_message_path(message)
|
49
|
+
td = message.to
|
@@ -0,0 +1,28 @@
|
|
1
|
+
- attachments.each do |attachment|
|
2
|
+
li.relative
|
3
|
+
/! Current: "ring-2 ring-offset-2 ring-indigo-500", Default: "focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-gray-100 focus-within:ring-indigo-500"
|
4
|
+
.group.block.w-full.aspect-w-10.aspect-h-7.rounded-lg.bg-gray-100.overflow-hidden
|
5
|
+
/! Current: "", Default: "group-hover:opacity-75"
|
6
|
+
- if attachment.previewable?
|
7
|
+
= image_tag attachment.preview(resize_to_limit: [500, 500]).url, class: 'object-cover pointer-events-none'
|
8
|
+
- elsif attachment.image?
|
9
|
+
= image_tag main_app.url_for(attachment.variant(resize_to_limit: [500, 500])), class: 'object-cover pointer-events-none'
|
10
|
+
p.mt-2.block.text-sm.font-medium.text-gray-900.truncate.pointer-events-none = attachment.filename
|
11
|
+
p.block.text-sm.font-medium.text-gray-500
|
12
|
+
= (attachment.blob.byte_size / 1048576.0).round(2)
|
13
|
+
' MB
|
14
|
+
= link_to admin_layout_attachment_path(@layout, attachment.id), data: { controller: 'attachment-delete', 'action': 'attachment-delete#delete' } do
|
15
|
+
i.fal.fa-xmark
|
16
|
+
=< link_to(main_app.rails_blob_path(attachment, disposition: 'attachment'),
|
17
|
+
title: attachment.filename)
|
18
|
+
i.fal.fa-download
|
19
|
+
|
20
|
+
/ This controller comes from papyrus
|
21
|
+
li.relative.attachment-upload data-controller="attachment-upload" data-attachment-upload-url="#{upload_url}" data-attachment-upload-param-name="attachments[]" data-attachment-upload-extra-data='{}'
|
22
|
+
.group.block.w-full.aspect-w-10.aspect-h-7.rounded-lg.bg-gray-100.overflow-hidden.text-center.align-middle style="height: 200px;"
|
23
|
+
span.icon.upload
|
24
|
+
i.fal.fa-4x.fa-upload.pt-16
|
25
|
+
span.icon.uploading
|
26
|
+
i.fal.fa-4x.fa-circle-notch.fa-spin.pt-16
|
27
|
+
|
28
|
+
p.mt-2.block.text-sm.font-medium.text-gray-500.truncate.pointer-events-none Drag or click to attach files
|
@@ -0,0 +1,2 @@
|
|
1
|
+
ul.attachments.grid.grid-cols-2.gap-x-4.gap-y-8.sm:grid-cols-3.sm:gap-x-6.md:grid-cols-4.lg:grid-cols-3.xl:grid-cols-4.xl:gap-x-8 role="list"
|
2
|
+
= render partial: 'nuntius/admin/layouts/attachments/attachments', locals: { attachments: @layout.attachments, upload_url: admin_layout_attachments_path(@layout) }
|
@@ -0,0 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
json.selector 'ul.attachments'
|
4
|
+
json.html render partial: 'nuntius/admin/layouts/attachments/attachments', layout: false, formats: [:html],
|
5
|
+
locals: { attachments: @layout.attachments, upload_url: admin_layout_attachments_path(@layout) }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
= sts.form_for([:admin, @layout]) do |f|
|
2
|
+
= sts.card title: t('.layout'), icon: 'fal fa-table-layout' do |card|
|
3
|
+
- card.action
|
4
|
+
= f.submit
|
5
|
+
|
6
|
+
= sts.tabs do |tabs|
|
7
|
+
- tabs.tab :edit
|
8
|
+
.grid.grid-cols-12.gap-4
|
9
|
+
.col-span-12
|
10
|
+
= f.input :name
|
11
|
+
.col-span-12
|
12
|
+
= f.input :data, as: :editor, mode: 'text/html', height: '400px'
|
13
|
+
- tabs.tab :metadata
|
14
|
+
.grid.grid-cols-12.gap-4
|
15
|
+
.col-span-12
|
16
|
+
= f.input :metadata_yaml, as: :editor, mode: 'application/yaml', label: t('.metadata')
|
17
|
+
|
18
|
+
- if @layout.persisted?
|
19
|
+
- tabs.tab :media
|
20
|
+
= render partial: 'nuntius/admin/layouts/attachments/index', locals: { attachments: @layout.attachments, upload_url: nuntius.admin_layout_attachments_path(@layout) }
|
21
|
+
|
22
|
+
- if @layout.templates.size.positive?
|
23
|
+
- tabs.tab :templates
|
24
|
+
= sts.table :nuntius_templates, parameters: { layout_id: @layout.id }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
= sts.form_for([:admin, @list]) do |f|
|
2
|
+
= sts.card title: t('.list'), icon: 'fad fa-address-book', menu: nuntius_list_menu do |card|
|
3
|
+
- card.action
|
4
|
+
= f.continue
|
5
|
+
- card.action
|
6
|
+
= f.submit
|
7
|
+
|
8
|
+
.grid.grid-cols-12.gap-4
|
9
|
+
.col-span-12
|
10
|
+
= f.input :name
|
11
|
+
|
12
|
+
br
|
13
|
+
- if @list.subscribers.present?
|
14
|
+
= sts.table :nuntius_subscribers, parameters: { list_id: @list.id }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
= sts.form_for(@subscriber, url: @subscriber.new_record? ? admin_list_subscribers_path(@list) : admin_list_subscriber_path(@list, @subscriber), html: {multipart: true}) do |f|
|
2
|
+
= sts.card title: t('.subscribers'), icon: 'fad fa-address-card' do |card|
|
3
|
+
- card.action
|
4
|
+
= f.submit
|
5
|
+
|
6
|
+
.grid.grid-cols-12.gap-4
|
7
|
+
.col-span-6
|
8
|
+
= f.input :first_name
|
9
|
+
.col-span-6
|
10
|
+
= f.input :last_name
|
11
|
+
.col-span-12
|
12
|
+
= f.input :phone_number
|
13
|
+
.col-span-12
|
14
|
+
= f.input :email
|
@@ -0,0 +1,14 @@
|
|
1
|
+
= sts.form_for([:admin, @locale]) do |f|
|
2
|
+
= sts.card title: t('.locale'), icon: 'fal fa-language' do |card|
|
3
|
+
- card.action
|
4
|
+
= f.continue
|
5
|
+
- card.action
|
6
|
+
= f.submit
|
7
|
+
|
8
|
+
.grid.grid-cols-12.gap-4
|
9
|
+
.col-span-12
|
10
|
+
= f.input :key
|
11
|
+
.col-span-12
|
12
|
+
= f.input :data_yaml, as: :editor, mode: 'application/yaml', label: t('.data')
|
13
|
+
.col-span-12
|
14
|
+
= f.input :metadata_yaml, as: :editor, mode: 'application/yaml', label: t('.metadata')
|