noticed 1.6.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +269 -237
- data/app/jobs/noticed/application_job.rb +9 -0
- data/app/jobs/noticed/event_job.rb +19 -0
- data/app/models/concerns/noticed/deliverable.rb +115 -0
- data/app/models/concerns/noticed/notification_methods.rb +17 -0
- data/app/models/concerns/noticed/readable.rb +78 -0
- data/app/models/noticed/application_record.rb +6 -0
- data/app/models/noticed/deliverable/deliver_by.rb +43 -0
- data/app/models/noticed/event.rb +15 -0
- data/app/models/noticed/notification.rb +14 -0
- data/db/migrate/20231215190233_create_noticed_tables.rb +25 -0
- data/lib/generators/noticed/delivery_method_generator.rb +1 -1
- data/lib/generators/noticed/install_generator.rb +19 -0
- data/lib/generators/noticed/{notification_generator.rb → notifier_generator.rb} +2 -2
- data/lib/generators/noticed/templates/README +5 -4
- data/lib/generators/noticed/templates/delivery_method.rb.tt +4 -8
- data/lib/generators/noticed/templates/notifier.rb.tt +24 -0
- data/lib/noticed/api_client.rb +44 -0
- data/lib/noticed/bulk_delivery_method.rb +46 -0
- data/lib/noticed/bulk_delivery_methods/discord.rb +11 -0
- data/lib/noticed/bulk_delivery_methods/slack.rb +17 -0
- data/lib/noticed/bulk_delivery_methods/webhook.rb +18 -0
- data/lib/noticed/coder.rb +2 -0
- data/lib/noticed/delivery_method.rb +51 -0
- data/lib/noticed/delivery_methods/action_cable.rb +7 -39
- data/lib/noticed/delivery_methods/discord.rb +11 -0
- data/lib/noticed/delivery_methods/email.rb +13 -45
- data/lib/noticed/delivery_methods/fcm.rb +23 -64
- data/lib/noticed/delivery_methods/ios.rb +25 -112
- data/lib/noticed/delivery_methods/microsoft_teams.rb +5 -22
- data/lib/noticed/delivery_methods/slack.rb +6 -16
- data/lib/noticed/delivery_methods/test.rb +2 -12
- data/lib/noticed/delivery_methods/twilio_messaging.rb +37 -0
- data/lib/noticed/delivery_methods/vonage_sms.rb +20 -0
- data/lib/noticed/delivery_methods/webhook.rb +17 -0
- data/lib/noticed/engine.rb +1 -9
- data/lib/noticed/required_options.rb +21 -0
- data/lib/noticed/translation.rb +7 -3
- data/lib/noticed/version.rb +1 -1
- data/lib/noticed.rb +30 -15
- metadata +29 -40
- data/lib/generators/noticed/model/base_generator.rb +0 -47
- data/lib/generators/noticed/model/mysql_generator.rb +0 -18
- data/lib/generators/noticed/model/postgresql_generator.rb +0 -18
- data/lib/generators/noticed/model/sqlite3_generator.rb +0 -18
- data/lib/generators/noticed/model_generator.rb +0 -63
- data/lib/generators/noticed/templates/notification.rb.tt +0 -27
- data/lib/noticed/base.rb +0 -160
- data/lib/noticed/delivery_methods/base.rb +0 -95
- data/lib/noticed/delivery_methods/database.rb +0 -34
- data/lib/noticed/delivery_methods/twilio.rb +0 -51
- data/lib/noticed/delivery_methods/vonage.rb +0 -40
- data/lib/noticed/has_notifications.rb +0 -49
- data/lib/noticed/model.rb +0 -85
- data/lib/noticed/notification_channel.rb +0 -15
- data/lib/noticed/text_coder.rb +0 -16
- data/lib/rails_6_polyfills/actioncable/test_adapter.rb +0 -70
- data/lib/rails_6_polyfills/actioncable/test_helper.rb +0 -143
- data/lib/rails_6_polyfills/activejob/serializers.rb +0 -240
- data/lib/rails_6_polyfills/base.rb +0 -18
- data/lib/tasks/noticed_tasks.rake +0 -4
@@ -1,51 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
module DeliveryMethods
|
3
|
-
class Twilio < Base
|
4
|
-
def deliver
|
5
|
-
post(url, basic_auth: {user: account_sid, pass: auth_token}, form: format)
|
6
|
-
end
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def format
|
11
|
-
if (method = options[:format])
|
12
|
-
notification.send(method)
|
13
|
-
else
|
14
|
-
{
|
15
|
-
From: phone_number,
|
16
|
-
To: recipient.phone_number,
|
17
|
-
Body: notification.params[:message]
|
18
|
-
}
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def url
|
23
|
-
if (method = options[:url])
|
24
|
-
notification.send(method)
|
25
|
-
else
|
26
|
-
"https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def account_sid
|
31
|
-
credentials.fetch(:account_sid)
|
32
|
-
end
|
33
|
-
|
34
|
-
def auth_token
|
35
|
-
credentials.fetch(:auth_token)
|
36
|
-
end
|
37
|
-
|
38
|
-
def phone_number
|
39
|
-
credentials.fetch(:phone_number)
|
40
|
-
end
|
41
|
-
|
42
|
-
def credentials
|
43
|
-
if (method = options[:credentials])
|
44
|
-
notification.send(method)
|
45
|
-
else
|
46
|
-
Rails.application.credentials.twilio
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
module DeliveryMethods
|
3
|
-
class Vonage < Base
|
4
|
-
def deliver
|
5
|
-
response = post("https://rest.nexmo.com/sms/json", json: format)
|
6
|
-
status = response.parse.dig("messages", 0, "status")
|
7
|
-
if !options[:ignore_failure] && status != "0"
|
8
|
-
raise ResponseUnsuccessful.new(response)
|
9
|
-
end
|
10
|
-
|
11
|
-
response
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def format
|
17
|
-
if (method = options[:format])
|
18
|
-
notification.send(method)
|
19
|
-
else
|
20
|
-
{
|
21
|
-
api_key: credentials[:api_key],
|
22
|
-
api_secret: credentials[:api_secret],
|
23
|
-
from: notification.params[:from],
|
24
|
-
text: notification.params[:body],
|
25
|
-
to: notification.params[:to],
|
26
|
-
type: "unicode"
|
27
|
-
}
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def credentials
|
32
|
-
if (method = options[:credentials])
|
33
|
-
notification.send(method)
|
34
|
-
else
|
35
|
-
Rails.application.credentials.vonage
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
module HasNotifications
|
3
|
-
# Defines a method for the association and a before_destroy callback to remove notifications
|
4
|
-
# where this record is a param
|
5
|
-
#
|
6
|
-
# class User < ApplicationRecord
|
7
|
-
# has_noticed_notifications
|
8
|
-
# has_noticed_notifications param_name: :owner, destroy: false, model: "Notification"
|
9
|
-
# end
|
10
|
-
#
|
11
|
-
# @user.notifications_as_user
|
12
|
-
# @user.notifications_as_owner
|
13
|
-
|
14
|
-
extend ActiveSupport::Concern
|
15
|
-
|
16
|
-
class_methods do
|
17
|
-
def has_noticed_notifications(param_name: model_name.singular, **options)
|
18
|
-
define_method "notifications_as_#{param_name}" do
|
19
|
-
model = options.fetch(:model_name, "Notification").constantize
|
20
|
-
case current_adapter
|
21
|
-
when "postgresql", "postgis"
|
22
|
-
model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
23
|
-
when "mysql2"
|
24
|
-
model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
25
|
-
when "sqlite3"
|
26
|
-
model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json)
|
27
|
-
else
|
28
|
-
# This will perform an exact match which isn't ideal
|
29
|
-
model.where(params: {param_name.to_sym => self})
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
if options.fetch(:destroy, true)
|
34
|
-
before_destroy do
|
35
|
-
send("notifications_as_#{param_name}").destroy_all
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def current_adapter
|
42
|
-
if ActiveRecord::Base.respond_to?(:connection_db_config)
|
43
|
-
ActiveRecord::Base.connection_db_config.adapter
|
44
|
-
else
|
45
|
-
ActiveRecord::Base.connection_config[:adapter]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
data/lib/noticed/model.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
module Model
|
3
|
-
DATABASE_ERROR_CLASS_NAMES = lambda {
|
4
|
-
classes = [ActiveRecord::NoDatabaseError]
|
5
|
-
classes << ActiveRecord::ConnectionNotEstablished
|
6
|
-
classes << Mysql2::Error if defined?(::Mysql2)
|
7
|
-
classes << PG::ConnectionBad if defined?(::PG)
|
8
|
-
classes
|
9
|
-
}.call.freeze
|
10
|
-
|
11
|
-
extend ActiveSupport::Concern
|
12
|
-
|
13
|
-
included do
|
14
|
-
self.inheritance_column = nil
|
15
|
-
|
16
|
-
if Rails.gem_version >= Gem::Version.new("7.1.0.alpha")
|
17
|
-
serialize :params, coder: noticed_coder
|
18
|
-
else
|
19
|
-
serialize :params, noticed_coder
|
20
|
-
end
|
21
|
-
|
22
|
-
belongs_to :recipient, polymorphic: true
|
23
|
-
|
24
|
-
scope :newest_first, -> { order(created_at: :desc) }
|
25
|
-
scope :unread, -> { where(read_at: nil) }
|
26
|
-
scope :read, -> { where.not(read_at: nil) }
|
27
|
-
end
|
28
|
-
|
29
|
-
class_methods do
|
30
|
-
def mark_as_read!
|
31
|
-
update_all(read_at: Time.current, updated_at: Time.current)
|
32
|
-
end
|
33
|
-
|
34
|
-
def mark_as_unread!
|
35
|
-
update_all(read_at: nil, updated_at: Time.current)
|
36
|
-
end
|
37
|
-
|
38
|
-
def noticed_coder
|
39
|
-
return Noticed::TextCoder unless table_exists?
|
40
|
-
|
41
|
-
case attribute_types["params"].type
|
42
|
-
when :json, :jsonb
|
43
|
-
Noticed::Coder
|
44
|
-
else
|
45
|
-
Noticed::TextCoder
|
46
|
-
end
|
47
|
-
rescue *DATABASE_ERROR_CLASS_NAMES => _error
|
48
|
-
warn("Noticed was unable to bootstrap correctly as the database is unavailable.")
|
49
|
-
|
50
|
-
Noticed::TextCoder
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Rehydrate the database notification into the Notification object for rendering
|
55
|
-
def to_notification
|
56
|
-
@_notification ||= begin
|
57
|
-
instance = type.constantize.with(params)
|
58
|
-
instance.record = self
|
59
|
-
instance.recipient = recipient
|
60
|
-
instance
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def mark_as_read!
|
65
|
-
update(read_at: Time.current)
|
66
|
-
end
|
67
|
-
|
68
|
-
def mark_as_unread!
|
69
|
-
update(read_at: nil)
|
70
|
-
end
|
71
|
-
|
72
|
-
def unread?
|
73
|
-
!read?
|
74
|
-
end
|
75
|
-
|
76
|
-
def read?
|
77
|
-
read_at?
|
78
|
-
end
|
79
|
-
|
80
|
-
# If a GlobalID record in params is no longer found, the params will default with a noticed_error key
|
81
|
-
def deserialize_error?
|
82
|
-
!!params[:noticed_error]
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
class NotificationChannel < ApplicationCable::Channel
|
3
|
-
def subscribed
|
4
|
-
stream_for current_user
|
5
|
-
end
|
6
|
-
|
7
|
-
def unsubscribed
|
8
|
-
stop_all_streams
|
9
|
-
end
|
10
|
-
|
11
|
-
def mark_as_read(data)
|
12
|
-
current_user.notifications.where(id: data["ids"]).mark_as_read!
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
data/lib/noticed/text_coder.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module Noticed
|
2
|
-
class TextCoder
|
3
|
-
def self.load(data)
|
4
|
-
return if data.nil?
|
5
|
-
|
6
|
-
# Text columns need JSON parsing
|
7
|
-
data = JSON.parse(data)
|
8
|
-
ActiveJob::Arguments.send(:deserialize_argument, data)
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.dump(data)
|
12
|
-
return if data.nil?
|
13
|
-
ActiveJob::Arguments.send(:serialize_argument, data).to_json
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_cable/subscription_adapter/base"
|
4
|
-
require "action_cable/subscription_adapter/subscriber_map"
|
5
|
-
require "action_cable/subscription_adapter/async"
|
6
|
-
|
7
|
-
module ActionCable
|
8
|
-
module SubscriptionAdapter
|
9
|
-
# == Test adapter for Action Cable
|
10
|
-
#
|
11
|
-
# The test adapter should be used only in testing. Along with
|
12
|
-
# <tt>ActionCable::TestHelper</tt> it makes a great tool to test your Rails application.
|
13
|
-
#
|
14
|
-
# To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file.
|
15
|
-
#
|
16
|
-
# NOTE: Test adapter extends the <tt>ActionCable::SubscriptionsAdapter::Async</tt> adapter,
|
17
|
-
# so it could be used in system tests too.
|
18
|
-
class Test < Async
|
19
|
-
def broadcast(channel, payload)
|
20
|
-
broadcasts(channel) << payload
|
21
|
-
super
|
22
|
-
end
|
23
|
-
|
24
|
-
def broadcasts(channel)
|
25
|
-
channels_data[channel] ||= []
|
26
|
-
end
|
27
|
-
|
28
|
-
def clear_messages(channel)
|
29
|
-
channels_data[channel] = []
|
30
|
-
end
|
31
|
-
|
32
|
-
def clear
|
33
|
-
@channels_data = nil
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def channels_data
|
39
|
-
@channels_data ||= {}
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Update how broadcast_for determines the channel name so it's consistent with the Rails 6 way
|
45
|
-
module Channel
|
46
|
-
module Broadcasting
|
47
|
-
delegate :broadcast_to, to: :class
|
48
|
-
module ClassMethods
|
49
|
-
def broadcast_to(model, message)
|
50
|
-
ActionCable.server.broadcast(broadcasting_for(model), message)
|
51
|
-
end
|
52
|
-
|
53
|
-
def broadcasting_for(model)
|
54
|
-
serialize_broadcasting([channel_name, model])
|
55
|
-
end
|
56
|
-
|
57
|
-
def serialize_broadcasting(object) # :nodoc:
|
58
|
-
case # standard:disable Style/EmptyCaseCondition
|
59
|
-
when object.is_a?(Array)
|
60
|
-
object.map { |m| serialize_broadcasting(m) }.join(":")
|
61
|
-
when object.respond_to?(:to_gid_param)
|
62
|
-
object.to_gid_param
|
63
|
-
else
|
64
|
-
object.to_param
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,143 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionCable
|
4
|
-
# Have ActionCable pick its Test SubscriptionAdapter when it's called for in cable.yml
|
5
|
-
module Server
|
6
|
-
class Configuration
|
7
|
-
def pubsub_adapter
|
8
|
-
(cable["adapter"] == "test") ? ActionCable::SubscriptionAdapter::Test : super
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
# Provides helper methods for testing Action Cable broadcasting
|
14
|
-
module TestHelper
|
15
|
-
def before_setup # :nodoc:
|
16
|
-
server = ActionCable.server
|
17
|
-
test_adapter = ActionCable::SubscriptionAdapter::Test.new(server)
|
18
|
-
|
19
|
-
@old_pubsub_adapter = server.pubsub
|
20
|
-
|
21
|
-
server.instance_variable_set(:@pubsub, test_adapter)
|
22
|
-
super
|
23
|
-
end
|
24
|
-
|
25
|
-
def after_teardown # :nodoc:
|
26
|
-
super
|
27
|
-
ActionCable.server.instance_variable_set(:@pubsub, @old_pubsub_adapter)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Asserts that the number of broadcasted messages to the stream matches the given number.
|
31
|
-
#
|
32
|
-
# def test_broadcasts
|
33
|
-
# assert_broadcasts 'messages', 0
|
34
|
-
# ActionCable.server.broadcast 'messages', { text: 'hello' }
|
35
|
-
# assert_broadcasts 'messages', 1
|
36
|
-
# ActionCable.server.broadcast 'messages', { text: 'world' }
|
37
|
-
# assert_broadcasts 'messages', 2
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# If a block is passed, that block should cause the specified number of
|
41
|
-
# messages to be broadcasted.
|
42
|
-
#
|
43
|
-
# def test_broadcasts_again
|
44
|
-
# assert_broadcasts('messages', 1) do
|
45
|
-
# ActionCable.server.broadcast 'messages', { text: 'hello' }
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# assert_broadcasts('messages', 2) do
|
49
|
-
# ActionCable.server.broadcast 'messages', { text: 'hi' }
|
50
|
-
# ActionCable.server.broadcast 'messages', { text: 'how are you?' }
|
51
|
-
# end
|
52
|
-
# end
|
53
|
-
#
|
54
|
-
def assert_broadcasts(stream, number)
|
55
|
-
if block_given?
|
56
|
-
original_count = broadcasts_size(stream)
|
57
|
-
yield
|
58
|
-
new_count = broadcasts_size(stream)
|
59
|
-
actual_count = new_count - original_count
|
60
|
-
else
|
61
|
-
actual_count = broadcasts_size(stream)
|
62
|
-
end
|
63
|
-
|
64
|
-
assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent"
|
65
|
-
end
|
66
|
-
|
67
|
-
# Asserts that no messages have been sent to the stream.
|
68
|
-
#
|
69
|
-
# def test_no_broadcasts
|
70
|
-
# assert_no_broadcasts 'messages'
|
71
|
-
# ActionCable.server.broadcast 'messages', { text: 'hi' }
|
72
|
-
# assert_broadcasts 'messages', 1
|
73
|
-
# end
|
74
|
-
#
|
75
|
-
# If a block is passed, that block should not cause any message to be sent.
|
76
|
-
#
|
77
|
-
# def test_broadcasts_again
|
78
|
-
# assert_no_broadcasts 'messages' do
|
79
|
-
# # No job messages should be sent from this block
|
80
|
-
# end
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# Note: This assertion is simply a shortcut for:
|
84
|
-
#
|
85
|
-
# assert_broadcasts 'messages', 0, &block
|
86
|
-
#
|
87
|
-
def assert_no_broadcasts(stream, &block)
|
88
|
-
assert_broadcasts stream, 0, &block
|
89
|
-
end
|
90
|
-
|
91
|
-
# Asserts that the specified message has been sent to the stream.
|
92
|
-
#
|
93
|
-
# def test_assert_transmitted_message
|
94
|
-
# ActionCable.server.broadcast 'messages', text: 'hello'
|
95
|
-
# assert_broadcast_on('messages', text: 'hello')
|
96
|
-
# end
|
97
|
-
#
|
98
|
-
# If a block is passed, that block should cause a message with the specified data to be sent.
|
99
|
-
#
|
100
|
-
# def test_assert_broadcast_on_again
|
101
|
-
# assert_broadcast_on('messages', text: 'hello') do
|
102
|
-
# ActionCable.server.broadcast 'messages', text: 'hello'
|
103
|
-
# end
|
104
|
-
# end
|
105
|
-
#
|
106
|
-
def assert_broadcast_on(stream, data)
|
107
|
-
# Encode to JSON and back–we want to use this value to compare
|
108
|
-
# with decoded JSON.
|
109
|
-
# Comparing JSON strings doesn't work due to the order of the keys.
|
110
|
-
serialized_msg =
|
111
|
-
ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data))
|
112
|
-
|
113
|
-
new_messages = broadcasts(stream)
|
114
|
-
if block_given?
|
115
|
-
old_messages = new_messages
|
116
|
-
clear_messages(stream)
|
117
|
-
|
118
|
-
yield
|
119
|
-
new_messages = broadcasts(stream)
|
120
|
-
clear_messages(stream)
|
121
|
-
|
122
|
-
# Restore all sent messages
|
123
|
-
(old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) }
|
124
|
-
end
|
125
|
-
|
126
|
-
message = new_messages.find { |msg| ActiveSupport::JSON.decode(msg) == serialized_msg }
|
127
|
-
|
128
|
-
assert message, "No messages sent with #{data} to #{stream}"
|
129
|
-
end
|
130
|
-
|
131
|
-
def pubsub_adapter # :nodoc:
|
132
|
-
ActionCable.server.pubsub
|
133
|
-
end
|
134
|
-
|
135
|
-
delegate :broadcasts, :clear_messages, to: :pubsub_adapter
|
136
|
-
|
137
|
-
private
|
138
|
-
|
139
|
-
def broadcasts_size(channel)
|
140
|
-
broadcasts(channel).size
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|