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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +269 -237
  3. data/app/jobs/noticed/application_job.rb +9 -0
  4. data/app/jobs/noticed/event_job.rb +19 -0
  5. data/app/models/concerns/noticed/deliverable.rb +115 -0
  6. data/app/models/concerns/noticed/notification_methods.rb +17 -0
  7. data/app/models/concerns/noticed/readable.rb +78 -0
  8. data/app/models/noticed/application_record.rb +6 -0
  9. data/app/models/noticed/deliverable/deliver_by.rb +43 -0
  10. data/app/models/noticed/event.rb +15 -0
  11. data/app/models/noticed/notification.rb +14 -0
  12. data/db/migrate/20231215190233_create_noticed_tables.rb +25 -0
  13. data/lib/generators/noticed/delivery_method_generator.rb +1 -1
  14. data/lib/generators/noticed/install_generator.rb +19 -0
  15. data/lib/generators/noticed/{notification_generator.rb → notifier_generator.rb} +2 -2
  16. data/lib/generators/noticed/templates/README +5 -4
  17. data/lib/generators/noticed/templates/delivery_method.rb.tt +4 -8
  18. data/lib/generators/noticed/templates/notifier.rb.tt +24 -0
  19. data/lib/noticed/api_client.rb +44 -0
  20. data/lib/noticed/bulk_delivery_method.rb +46 -0
  21. data/lib/noticed/bulk_delivery_methods/discord.rb +11 -0
  22. data/lib/noticed/bulk_delivery_methods/slack.rb +17 -0
  23. data/lib/noticed/bulk_delivery_methods/webhook.rb +18 -0
  24. data/lib/noticed/coder.rb +2 -0
  25. data/lib/noticed/delivery_method.rb +51 -0
  26. data/lib/noticed/delivery_methods/action_cable.rb +7 -39
  27. data/lib/noticed/delivery_methods/discord.rb +11 -0
  28. data/lib/noticed/delivery_methods/email.rb +13 -45
  29. data/lib/noticed/delivery_methods/fcm.rb +23 -64
  30. data/lib/noticed/delivery_methods/ios.rb +25 -112
  31. data/lib/noticed/delivery_methods/microsoft_teams.rb +5 -22
  32. data/lib/noticed/delivery_methods/slack.rb +6 -16
  33. data/lib/noticed/delivery_methods/test.rb +2 -12
  34. data/lib/noticed/delivery_methods/twilio_messaging.rb +37 -0
  35. data/lib/noticed/delivery_methods/vonage_sms.rb +20 -0
  36. data/lib/noticed/delivery_methods/webhook.rb +17 -0
  37. data/lib/noticed/engine.rb +1 -9
  38. data/lib/noticed/required_options.rb +21 -0
  39. data/lib/noticed/translation.rb +7 -3
  40. data/lib/noticed/version.rb +1 -1
  41. data/lib/noticed.rb +30 -15
  42. metadata +29 -40
  43. data/lib/generators/noticed/model/base_generator.rb +0 -47
  44. data/lib/generators/noticed/model/mysql_generator.rb +0 -18
  45. data/lib/generators/noticed/model/postgresql_generator.rb +0 -18
  46. data/lib/generators/noticed/model/sqlite3_generator.rb +0 -18
  47. data/lib/generators/noticed/model_generator.rb +0 -63
  48. data/lib/generators/noticed/templates/notification.rb.tt +0 -27
  49. data/lib/noticed/base.rb +0 -160
  50. data/lib/noticed/delivery_methods/base.rb +0 -95
  51. data/lib/noticed/delivery_methods/database.rb +0 -34
  52. data/lib/noticed/delivery_methods/twilio.rb +0 -51
  53. data/lib/noticed/delivery_methods/vonage.rb +0 -40
  54. data/lib/noticed/has_notifications.rb +0 -49
  55. data/lib/noticed/model.rb +0 -85
  56. data/lib/noticed/notification_channel.rb +0 -15
  57. data/lib/noticed/text_coder.rb +0 -16
  58. data/lib/rails_6_polyfills/actioncable/test_adapter.rb +0 -70
  59. data/lib/rails_6_polyfills/actioncable/test_helper.rb +0 -143
  60. data/lib/rails_6_polyfills/activejob/serializers.rb +0 -240
  61. data/lib/rails_6_polyfills/base.rb +0 -18
  62. 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
@@ -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