notey 0.1.0 → 0.2.0
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 +57 -40
- data/app/models/notey/attempt.rb +10 -0
- data/lib/notey/channels.rb +1 -1
- data/lib/notey/version.rb +1 -1
- data/lib/notey.rb +16 -56
- data/the_local/agents/notey-develop.md +85 -72
- data/the_local/agents/notey-info.md +17 -14
- data/the_local/agents/notey-install.md +49 -45
- data/the_local/interface.yml +13 -10
- metadata +1 -4
- data/app/models/concerns/notey/notifier.rb +0 -19
- data/lib/notey/catalog.rb +0 -51
- data/lib/notey/event_delivery.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a4f1b1236c0e43b64f5624a251fdc64f4ce94cd3e12f000efb4619799d6c7c4
|
|
4
|
+
data.tar.gz: 8bc6db30919be535ab9b1a6064f8ba58be63791b0cb091fe63a6dca47e57df76
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e29900e2827d55ab8efd6bebe99a5a73fdd4d49b709dbeeafeefc73aeddbf31fba242105b0dd7e547f15852855e5a3e45dd3bf4df51fc6365dc156da936a094
|
|
7
|
+
data.tar.gz: cb4ae5408587d29aab57d7e87f94a5fbfcd3097eb93a2e5a0e9335816f704560c42acde42f6c1bd3b970ab043cb9163249a8d4672b59e04a26d4a9244ae414bf
|
data/README.md
CHANGED
|
@@ -15,8 +15,8 @@ leaves to the host.
|
|
|
15
15
|
|
|
16
16
|
## What Notey adds
|
|
17
17
|
|
|
18
|
-
- **
|
|
19
|
-
|
|
18
|
+
- **Channels.** One registration per channel the application can send on, made
|
|
19
|
+
once for the whole application rather than on every notification.
|
|
20
20
|
- **Preferences.** Which channels a person wants for each type, per account,
|
|
21
21
|
with a page they set them on.
|
|
22
22
|
- **Digest windows.** A type set to daily or weekly is held back and sent as one
|
|
@@ -24,8 +24,8 @@ leaves to the host.
|
|
|
24
24
|
- **An inbox.** In-app notification pages scoped to the account the person is in.
|
|
25
25
|
- **Destinations.** An address and an encrypted credential per account per
|
|
26
26
|
channel, so an account points a channel at its own workspace or endpoint.
|
|
27
|
-
- **
|
|
28
|
-
|
|
27
|
+
- **Notification types.** A class describing what a notification carries and
|
|
28
|
+
nothing about how it is sent.
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
|
|
@@ -54,20 +54,21 @@ That gives you `/notey/preferences`, `/notey/notifications` and
|
|
|
54
54
|
Notey owns its own tables and never owns your person or account records. Six
|
|
55
55
|
things connect it to yours.
|
|
56
56
|
|
|
57
|
-
### 1.
|
|
57
|
+
### 1. Register the channels your application has
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
notifier naming an undeclared type raises when the app boots.
|
|
59
|
+
Every application has email and in-app without registering anything. Register a
|
|
60
|
+
channel for anything else you send on, once, for the whole application.
|
|
62
61
|
|
|
63
62
|
```ruby
|
|
64
63
|
# config/initializers/notey.rb
|
|
65
|
-
Notey.
|
|
66
|
-
notification :comment, channels: %w[email sms], default: %w[email]
|
|
67
|
-
notification :mention, channels: %w[email], default: []
|
|
68
|
-
end
|
|
64
|
+
Notey.channel :sms, delivery_method: "Noticed::DeliveryMethods::TwilioMessaging", addressed: true
|
|
69
65
|
```
|
|
70
66
|
|
|
67
|
+
A registration names the channel, the delivery method that sends it, and whether
|
|
68
|
+
it needs an address before anything can go out on it. Nothing else names a
|
|
69
|
+
channel: a notification type does not, and a person's preferences offer exactly
|
|
70
|
+
what is registered.
|
|
71
|
+
|
|
71
72
|
### 2. Make your person model a recipient
|
|
72
73
|
|
|
73
74
|
```ruby
|
|
@@ -113,23 +114,41 @@ ActiveSupport.on_load :noticed_event do
|
|
|
113
114
|
end
|
|
114
115
|
```
|
|
115
116
|
|
|
116
|
-
### 5.
|
|
117
|
+
### 5. Define a notification type
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
A notification type describes what the notification carries and says nothing
|
|
120
|
+
about channels. Notey decides which channels each recipient gets.
|
|
120
121
|
|
|
121
122
|
```ruby
|
|
122
|
-
class
|
|
123
|
-
include Notey::Notifier
|
|
123
|
+
class CommentNotification < Notey::Notification
|
|
124
124
|
notey_type :comment
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
required_params :comment_id
|
|
127
|
+
|
|
128
|
+
def title
|
|
129
|
+
"New comment"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def body
|
|
133
|
+
"Someone replied to you"
|
|
129
134
|
end
|
|
130
135
|
end
|
|
131
136
|
```
|
|
132
137
|
|
|
138
|
+
`notey_type` is the name a person's stored preferences are keyed by, so renaming
|
|
139
|
+
the class does not orphan what they chose. Send it by naming the recipients your
|
|
140
|
+
own code resolved:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
CommentNotification.notify(recipients, comment_id: comment.id)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Notey builds the delivery list from the registered channels each time, so a
|
|
147
|
+
channel registered later needs no change here. For each recipient and each
|
|
148
|
+
channel it answers three things before sending: the person wants that channel
|
|
149
|
+
for that type, their window is immediate, and they have an address if the
|
|
150
|
+
channel needs one.
|
|
151
|
+
|
|
133
152
|
### 6. Tell Notey where a notification lives
|
|
134
153
|
|
|
135
154
|
Digest emails link to each notification through a lambda you supply, so the link
|
|
@@ -183,13 +202,10 @@ releases the window so it can be sent again.
|
|
|
183
202
|
|
|
184
203
|
A channel like email reaches a person at an address the app already holds. A
|
|
185
204
|
channel like SMS, a webhook, Slack or Discord does not, so somebody has to say
|
|
186
|
-
where it goes.
|
|
205
|
+
where it goes. Say so when you register the channel:
|
|
187
206
|
|
|
188
207
|
```ruby
|
|
189
|
-
Notey.
|
|
190
|
-
notification :comment, channels: %w[email sms], default: %w[email]
|
|
191
|
-
addressed :sms
|
|
192
|
-
end
|
|
208
|
+
Notey.channel :sms, delivery_method: "Noticed::DeliveryMethods::TwilioMessaging", addressed: true
|
|
193
209
|
```
|
|
194
210
|
|
|
195
211
|
**A person sets their own.** Their phone number, their endpoint. A notification
|
|
@@ -209,12 +225,14 @@ Bureau.section :notification_destinations, area: :account, title: "Notification
|
|
|
209
225
|
renders: "notey/destinations", runs: "Notey::SaveDestination", capability: :configure_site
|
|
210
226
|
```
|
|
211
227
|
|
|
212
|
-
|
|
228
|
+
Notey sends nothing on an addressed channel until the person has set an address.
|
|
229
|
+
The delivery method reads that address when it sends:
|
|
213
230
|
|
|
214
231
|
```ruby
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
class WebhookDeliveryMethod < Noticed::DeliveryMethod
|
|
233
|
+
def deliver
|
|
234
|
+
post_to Notey::Destinations.for(event.account_id, :webhook, member: recipient).address
|
|
235
|
+
end
|
|
218
236
|
end
|
|
219
237
|
```
|
|
220
238
|
|
|
@@ -226,13 +244,9 @@ it as a settings section so the shell's capability check guards it.
|
|
|
226
244
|
|
|
227
245
|
## Domain events
|
|
228
246
|
|
|
229
|
-
Notey does not depend on any event pipeline
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
# config/initializers/notey.rb
|
|
234
|
-
Notey.deliver_on :comment_posted, CommentNotifier
|
|
235
|
-
```
|
|
247
|
+
Notey does not depend on any event pipeline and holds no mapping from an event
|
|
248
|
+
to a notification. Your subscriber resolves who should hear about something and
|
|
249
|
+
calls the notification type.
|
|
236
250
|
|
|
237
251
|
```ruby
|
|
238
252
|
# app/subscribers/notey_notifications.rb
|
|
@@ -240,14 +254,17 @@ class NoteyNotifications < EventEngine::Subscribers::Base
|
|
|
240
254
|
subscribes_to :comment_posted
|
|
241
255
|
|
|
242
256
|
def handle(event)
|
|
243
|
-
|
|
257
|
+
payload = event.payload.to_h.symbolize_keys
|
|
258
|
+
|
|
259
|
+
Notey::Current.set(account_id: payload[:account_id]) do
|
|
260
|
+
CommentNotification.notify(User.where(id: payload[:user_ids]), comment_id: payload[:comment_id])
|
|
261
|
+
end
|
|
244
262
|
end
|
|
245
263
|
end
|
|
246
264
|
```
|
|
247
265
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
found. An event with no mapping does nothing.
|
|
266
|
+
Who receives a notification is decided outside notey; how each of them receives
|
|
267
|
+
it is decided inside.
|
|
251
268
|
|
|
252
269
|
## Development
|
|
253
270
|
|
data/app/models/notey/attempt.rb
CHANGED
|
@@ -3,5 +3,15 @@
|
|
|
3
3
|
module Notey
|
|
4
4
|
class Attempt < ApplicationRecord
|
|
5
5
|
belongs_to :notification, class_name: "Noticed::Notification"
|
|
6
|
+
|
|
7
|
+
def send_again
|
|
8
|
+
raise UnsendableAttempt, "#{channel} was already sent for this notification" if state == "sent"
|
|
9
|
+
|
|
10
|
+
delivery = notification.event.class.delivery_methods[channel.to_sym]
|
|
11
|
+
raise UnsendableAttempt, "#{channel} is not a channel this application has" if delivery.nil?
|
|
12
|
+
|
|
13
|
+
destroy
|
|
14
|
+
delivery.perform_later(notification)
|
|
15
|
+
end
|
|
6
16
|
end
|
|
7
17
|
end
|
data/lib/notey/channels.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Notey
|
|
|
10
10
|
stored = stored_for(member, notification_type, account_id)
|
|
11
11
|
|
|
12
12
|
Decision.new(
|
|
13
|
-
channels: stored
|
|
13
|
+
channels: channels_of(stored, notification_type) & Notey.channels,
|
|
14
14
|
window: stored&.digest_window || "immediate"
|
|
15
15
|
)
|
|
16
16
|
end
|
data/lib/notey/version.rb
CHANGED
data/lib/notey.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
require "notey/version"
|
|
2
2
|
require "notey/engine"
|
|
3
|
-
require "notey/catalog"
|
|
4
3
|
require "notey/channels"
|
|
5
4
|
require "notey/destinations"
|
|
6
|
-
require "notey/event_delivery"
|
|
7
5
|
require "notey/inbox"
|
|
8
6
|
require "notey/digest_run"
|
|
9
7
|
require "notey/records_attempt"
|
|
@@ -18,9 +16,9 @@ module Notey
|
|
|
18
16
|
end
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
class UndeclaredType < StandardError; end
|
|
22
|
-
class UndeliverableChannel < StandardError; end
|
|
23
19
|
class MissingSender < StandardError; end
|
|
20
|
+
class UnsendableChannel < StandardError; end
|
|
21
|
+
class UnsendableAttempt < StandardError; end
|
|
24
22
|
|
|
25
23
|
class << self
|
|
26
24
|
attr_writer :notification_url, :mailer_sender
|
|
@@ -85,65 +83,29 @@ module Notey
|
|
|
85
83
|
end
|
|
86
84
|
|
|
87
85
|
def self.check!
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
check_declared_types!
|
|
91
|
-
check_deliverable_channels!
|
|
86
|
+
registered_channels.each { |channel| check_channel!(channel) }
|
|
92
87
|
check_sender!
|
|
93
88
|
end
|
|
94
89
|
|
|
95
|
-
def self.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
raise MissingSender, "notey sends digests by email and no mailer_sender is set"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def self.check_deliverable_channels!
|
|
102
|
-
delivered = notifiers.flat_map { |notifier| notifier.delivery_methods.keys.map(&:to_s) }.uniq
|
|
103
|
-
|
|
104
|
-
(catalog.channels - delivered).each do |channel|
|
|
105
|
-
raise UndeliverableChannel, "the catalog offers the channel #{channel}, which no notifier delivers on"
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def self.check_declared_types!
|
|
110
|
-
notifiers.each do |notifier|
|
|
111
|
-
notification_type = notifier.notey_notification_type
|
|
112
|
-
next if notification_type.nil? || catalog.declared?(notification_type)
|
|
113
|
-
|
|
114
|
-
raise UndeclaredType,
|
|
115
|
-
"#{notifier} declares the notification type #{notification_type}, which the catalog does not hold"
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def self.destination_address(channel)
|
|
120
|
-
-> { Destinations.for(event.account_id, channel, member: recipient)&.address }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def self.addressed(channel)
|
|
124
|
-
-> { Destinations.for(event.account_id, channel, member: recipient).present? }
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def self.deliver_on(event_name, notifier)
|
|
128
|
-
event_notifiers[event_name.to_s] = notifier
|
|
129
|
-
end
|
|
90
|
+
def self.check_channel!(channel)
|
|
91
|
+
required = delivery_method_for(channel).required_option_names
|
|
92
|
+
return if required.empty?
|
|
130
93
|
|
|
131
|
-
|
|
132
|
-
|
|
94
|
+
raise UnsendableChannel,
|
|
95
|
+
"the channel #{channel.name} needs the option #{required.first}, which notey does not supply"
|
|
133
96
|
end
|
|
134
97
|
|
|
135
|
-
def self.
|
|
136
|
-
|
|
98
|
+
def self.delivery_method_for(channel)
|
|
99
|
+
name = channel.delivery_method || "Noticed::DeliveryMethods::#{channel.name.camelize}"
|
|
100
|
+
name.to_s.constantize
|
|
101
|
+
rescue NameError
|
|
102
|
+
raise UnsendableChannel, "the channel #{channel.name} names the delivery method #{name}, which does not exist"
|
|
137
103
|
end
|
|
138
104
|
|
|
139
|
-
def self.
|
|
140
|
-
|
|
141
|
-
@catalog.instance_eval(&block) if block
|
|
142
|
-
@catalog
|
|
143
|
-
end
|
|
105
|
+
def self.check_sender!
|
|
106
|
+
return if mailer_sender.present?
|
|
144
107
|
|
|
145
|
-
|
|
146
|
-
sends(notification_type, on: on)
|
|
108
|
+
raise MissingSender, "notey sends digests by email and no mailer_sender is set"
|
|
147
109
|
end
|
|
148
110
|
|
|
149
111
|
def self.sends(notification_type, on:, addressed: false)
|
|
@@ -158,8 +120,6 @@ module Notey
|
|
|
158
120
|
end
|
|
159
121
|
|
|
160
122
|
def self.reset!
|
|
161
|
-
@catalog = nil
|
|
162
|
-
@event_notifiers = nil
|
|
163
123
|
@host_channels = nil
|
|
164
124
|
forget_notifiers
|
|
165
125
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: notey-develop
|
|
3
|
-
description: Use PROACTIVELY for
|
|
3
|
+
description: Use PROACTIVELY for defining a notification type, sending one to the recipients your own code resolved, sending one when a domain event fires, addressing a channel at a person, scheduling daily and weekly digest runs, reading a person's inbox, and reading their channels and window — MUST BE USED instead of hand-rolling preference checks, per-person delivery addresses, or a digest loop.
|
|
4
4
|
tools: Read, Write, Edit, Grep
|
|
5
|
-
scope: notifications —
|
|
5
|
+
scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
This local follows the steps below exactly and invents no others. Where a step
|
|
@@ -12,28 +12,31 @@ picking one.
|
|
|
12
12
|
## What notey is
|
|
13
13
|
|
|
14
14
|
notey is the notification layer for a multi-tenant Rails app on top of Noticed.
|
|
15
|
-
It holds which
|
|
16
|
-
each account they belong to, and whether they want them as they happen
|
|
17
|
-
daily or weekly digest. Fire this local when a
|
|
18
|
-
changed, when a domain event should send one, when digests need
|
|
19
|
-
the host's own code needs to read a person's notifications or
|
|
15
|
+
It holds which channels the application can send on, which channels each person
|
|
16
|
+
wants in each account they belong to, and whether they want them as they happen
|
|
17
|
+
or in a daily or weekly digest. Fire this local when a notification type is
|
|
18
|
+
being written or changed, when a domain event should send one, when digests need
|
|
19
|
+
running, or when the host's own code needs to read a person's notifications or
|
|
20
|
+
their preferences.
|
|
20
21
|
|
|
21
22
|
This local assumes the gem is already hooked into the app; if it is not, that is
|
|
22
23
|
`notey-install`'s work and it comes first.
|
|
23
24
|
|
|
24
25
|
## Interface
|
|
25
26
|
|
|
26
|
-
- `
|
|
27
|
-
|
|
28
|
-
- `
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
- `Notey::Notification` — the class a notification type inherits, which builds
|
|
28
|
+
its delivery list from the registered channels and decides each one itself.
|
|
29
|
+
- `notey_type` — declared inside a notification type, names the key a person's
|
|
30
|
+
stored preferences are held under, so renaming the class keeps them.
|
|
31
|
+
- `required_params` — declared inside a notification type, names the information
|
|
32
|
+
a caller must pass, and a call that omits any of it is refused.
|
|
33
|
+
- `notify(recipients, **information)` — sends one notification to the recipients
|
|
34
|
+
the caller resolved, recording one row for each.
|
|
35
|
+
- `Notey.channel(name, delivery_method:, addressed:)` — registers one channel
|
|
36
|
+
the application can send on, for the whole application.
|
|
37
|
+
- `Notey::Destinations.for(account_id, channel, member:)` — returns the stored
|
|
38
|
+
destination for that person on that channel, which a delivery method reads to
|
|
39
|
+
learn where to send.
|
|
37
40
|
- `Notey::DigestRun` — sends one window's digests, with `call` for everyone on
|
|
38
41
|
that window and `deliver_to_member(member, account_id)` for one person in one
|
|
39
42
|
account.
|
|
@@ -45,65 +48,76 @@ This local assumes the gem is already hooked into the app; if it is not, that is
|
|
|
45
48
|
person gets that type on in that account.
|
|
46
49
|
- `digest_window_for(type, account_id:)` — on the recipient model, `immediate`,
|
|
47
50
|
`daily` or `weekly` for that type in that account.
|
|
48
|
-
- `Notey.reset!` — drops the
|
|
49
|
-
tests that
|
|
51
|
+
- `Notey.reset!` — drops the registered channels and notification types, for
|
|
52
|
+
tests that register their own.
|
|
50
53
|
|
|
51
54
|
## How to use it
|
|
52
55
|
|
|
53
|
-
1.
|
|
56
|
+
1. Write a notification type that says nothing about channels:
|
|
54
57
|
|
|
55
58
|
```ruby
|
|
56
|
-
class
|
|
59
|
+
class CommentNotification < Notey::Notification
|
|
57
60
|
notey_type :comment
|
|
61
|
+
|
|
62
|
+
required_params :comment_id
|
|
63
|
+
|
|
64
|
+
def title
|
|
65
|
+
"New comment"
|
|
66
|
+
end
|
|
58
67
|
end
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
Ask the developer what information the notification carries and what a person
|
|
71
|
+
should read on it. Two types may name the same `notey_type`, and renaming the
|
|
72
|
+
class keeps the preferences people have already stored.
|
|
63
73
|
|
|
64
|
-
2.
|
|
74
|
+
2. Send it by naming the recipients your own code resolved:
|
|
65
75
|
|
|
66
76
|
```ruby
|
|
67
|
-
|
|
68
|
-
config.if = Notey.wanted(:comment, on: :email)
|
|
69
|
-
end
|
|
77
|
+
CommentNotification.notify(recipients, comment_id: comment.id)
|
|
70
78
|
```
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
Working out who should receive it, and removing duplicates from that list, is
|
|
81
|
+
the caller's and never notey's. The call is refused before anything is
|
|
82
|
+
recorded when it omits information the type requires.
|
|
75
83
|
|
|
76
|
-
3.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
3. Write no condition on any channel. notey builds the delivery list from the
|
|
85
|
+
registered channels and answers three things per recipient per channel before
|
|
86
|
+
sending: the person wants that channel for that type, their window is
|
|
87
|
+
immediate, and they have an address if the channel needs one. A type that
|
|
88
|
+
tries to gate its own channels is doing notey's job twice.
|
|
80
89
|
|
|
81
|
-
4. For a channel
|
|
82
|
-
stored address and skip the delivery when there is none:
|
|
90
|
+
4. For a channel that needs an address, read it inside the delivery method:
|
|
83
91
|
|
|
84
92
|
```ruby
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
class WebhookDeliveryMethod < Noticed::DeliveryMethod
|
|
94
|
+
def deliver
|
|
95
|
+
post_to Notey::Destinations.for(event.account_id, :webhook, member: recipient).address
|
|
96
|
+
end
|
|
88
97
|
end
|
|
89
98
|
```
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
notey has already refused the delivery when no address is set, so the lookup
|
|
101
|
+
never comes back empty here. Ask the developer which channels need an address,
|
|
102
|
+
since that is named once on the registration and only they know which.
|
|
94
103
|
|
|
95
|
-
5.
|
|
104
|
+
5. Send from a domain event in the host's own subscriber:
|
|
96
105
|
|
|
97
106
|
```ruby
|
|
98
|
-
|
|
107
|
+
def handle(event)
|
|
108
|
+
payload = event.payload.to_h.symbolize_keys
|
|
109
|
+
|
|
110
|
+
Notey::Current.set(account_id: payload[:account_id]) do
|
|
111
|
+
CommentNotification.notify(User.where(id: payload[:user_ids]), comment_id: payload[:comment_id])
|
|
112
|
+
end
|
|
113
|
+
end
|
|
99
114
|
```
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
against no account and never reaches an inbox.
|
|
116
|
+
notey holds no mapping from an event to a notification and depends on no event
|
|
117
|
+
pipeline. The subscriber resolves the recipients and sets the account, because
|
|
118
|
+
a delivery runs in a job where the current account is gone. Without
|
|
119
|
+
`account_id` the notification is stored against no account and never reaches
|
|
120
|
+
an inbox.
|
|
107
121
|
|
|
108
122
|
6. Schedule the digest windows. notey registers no schedule of its own, so a
|
|
109
123
|
window nobody runs never sends:
|
|
@@ -139,27 +153,26 @@ This local assumes the gem is already hooked into the app; if it is not, that is
|
|
|
139
153
|
```
|
|
140
154
|
|
|
141
155
|
All three take the account from the current request when it is left out. All
|
|
142
|
-
three read a person's stored row for that type, and fall back to
|
|
143
|
-
|
|
156
|
+
three read a person's stored row for that type, and fall back to email and
|
|
157
|
+
in-app when they have never stored one.
|
|
144
158
|
|
|
145
|
-
9. In tests that
|
|
159
|
+
9. In tests that register their own channels or notification types, call
|
|
146
160
|
`Notey.reset!` in teardown. Both are held for the life of the process, so a
|
|
147
|
-
test that skips this leaves its
|
|
161
|
+
test that skips this leaves its channels and types in place for every test
|
|
148
162
|
after it.
|
|
149
163
|
|
|
150
164
|
## Conventions
|
|
151
165
|
|
|
152
|
-
- **Every
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- **Every channel
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
- **
|
|
161
|
-
|
|
162
|
-
the delivery silently never runs.
|
|
166
|
+
- **Every notification type names a `notey_type`.** Without one no digest ever
|
|
167
|
+
includes it, because a digest picks its rows by that name.
|
|
168
|
+
- **No notification type names a channel.** Channels are registered once for the
|
|
169
|
+
whole application, and a type that declares one is describing something it
|
|
170
|
+
does not decide.
|
|
171
|
+
- **Every registered channel is offered for every type.** A type cannot be kept
|
|
172
|
+
off a channel, so a channel nobody should get for a given notification is a
|
|
173
|
+
channel that should not be registered.
|
|
174
|
+
- **Email and in-app exist without being registered.** An application that
|
|
175
|
+
registers nothing can still send on both.
|
|
163
176
|
- **A digest is mailed to the person's `email`.** A recipient model without one
|
|
164
177
|
raises when its digest is sent, not when the run starts.
|
|
165
178
|
- **A run sends one window once.** Overlapping runs of the same window send one
|
|
@@ -170,9 +183,9 @@ This local assumes the gem is already hooked into the app; if it is not, that is
|
|
|
170
183
|
nothing.
|
|
171
184
|
- **A digest sends nothing when there is nothing to send** — no empty mail, and
|
|
172
185
|
no row saying it went out.
|
|
173
|
-
- **
|
|
174
|
-
code**, never in the gem.
|
|
175
|
-
- **Out of scope.**
|
|
176
|
-
a
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
- **Notification types, subscribers and digest schedules all belong in the
|
|
187
|
+
host's code**, never in the gem.
|
|
188
|
+
- **Out of scope.** Registering the channels the application has, making a model
|
|
189
|
+
a recipient, setting the current person and account, storing an account's
|
|
190
|
+
addresses, and the pages a person picks their own channels on all belong to
|
|
191
|
+
`notey-install`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: notey-info
|
|
3
3
|
description: Use to learn what notey offers — notification types and the channels they allow, per-person per-account delivery preferences, digest windows, an in-app inbox, and per-account destinations.
|
|
4
4
|
tools: Read
|
|
5
|
-
scope: notifications —
|
|
5
|
+
scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
This local explains what notey is and which of its other two locals you need. It
|
|
@@ -52,15 +52,12 @@ has already been sent, you are building, and `notey-develop` owns it.
|
|
|
52
52
|
## Conventions
|
|
53
53
|
|
|
54
54
|
- **Notification type** — the name of one kind of notification, as a string. It
|
|
55
|
-
is the unit everything else is keyed by: a person's preference
|
|
56
|
-
|
|
57
|
-
- **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- **Channel** — a named way of delivering, such as email or a chat webhook. The
|
|
62
|
-
catalog offers them per notification type; a notifier is what actually
|
|
63
|
-
delivers on one.
|
|
55
|
+
is the unit everything else is keyed by: a person's preference and the name a
|
|
56
|
+
notification type declares.
|
|
57
|
+
- **Channel** — a named way of delivering, such as email or a chat webhook,
|
|
58
|
+
registered once for the whole application. Email and in-app exist without
|
|
59
|
+
being registered; anything else exists only if the application registered it.
|
|
60
|
+
Every registered channel is offered for every notification type.
|
|
64
61
|
- **Member** — the person a preference belongs to. It is polymorphic, so a host
|
|
65
62
|
app names whichever model of its own receives notifications.
|
|
66
63
|
- **Account** — the tenant. Preferences, notifications, digests and destinations
|
|
@@ -72,9 +69,15 @@ has already been sent, you are building, and `notey-develop` owns it.
|
|
|
72
69
|
notification happens; the other two hold it for a grouped email covering the
|
|
73
70
|
current day or week.
|
|
74
71
|
- **Destination** — one address per account per channel, with an optional stored
|
|
75
|
-
credential,
|
|
72
|
+
credential, optionally owned by one person. A notification for a person uses
|
|
73
|
+
the address that person set, never the account's.
|
|
76
74
|
- **Inbox** — the notifications a member has received within one account, which
|
|
77
75
|
is what the in-app list reads and what a digest gathers from.
|
|
78
|
-
- **
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
- **Notification type** — the class describing what one notification carries and
|
|
77
|
+
what a person reads on it. It names no channel, because notey builds the
|
|
78
|
+
delivery list from the registered channels and decides each one against the
|
|
79
|
+
recipient's stored preference.
|
|
80
|
+
- **Attempt** — one row per notification per outbound channel, claimed before the
|
|
81
|
+
send and marked after, holding whether it was sent and what a failure said. It
|
|
82
|
+
is what answers why one person was not reached, and it is what stops the same
|
|
83
|
+
channel sending twice for the same notification.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: notey-install
|
|
3
|
-
description: Use to hook notey into a project — installing its tables, mounting its engine,
|
|
3
|
+
description: Use to hook notey into a project — installing its tables, mounting its engine, registering the channels the application can send on, making a model a recipient, setting the current person and account, putting the account on Noticed's rows, supplying a notification url, and reaching the preferences, inbox and destinations pages.
|
|
4
4
|
tools: Bash, Read, Edit
|
|
5
|
-
scope: notifications —
|
|
5
|
+
scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
This local follows the steps below exactly and invents no others. Where a step
|
|
@@ -17,31 +17,32 @@ different notifications in each.
|
|
|
17
17
|
|
|
18
18
|
## Interface
|
|
19
19
|
|
|
20
|
-
- `bin/rails notey:install:migrations` — copies notey's
|
|
20
|
+
- `bin/rails notey:install:migrations` — copies notey's migrations into the
|
|
21
21
|
host's `db/migrate`.
|
|
22
22
|
- `mount Notey::Engine` — in the host's `config/routes.rb`, putting notey's
|
|
23
23
|
three pages under a path the host chooses.
|
|
24
|
-
- `Notey.
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- `Notey.channel` — registers one channel the application can send on, naming
|
|
25
|
+
the delivery method that sends it and whether it needs an address. Email and
|
|
26
|
+
in-app exist without being registered.
|
|
27
27
|
- `Notey::Recipient` — included in the host model that receives notifications,
|
|
28
28
|
which gives that model its stored preferences.
|
|
29
|
-
- `Notey::
|
|
30
|
-
|
|
29
|
+
- `Notey::Notification` — the class each notification type inherits, which
|
|
30
|
+
registers the type and builds its delivery list from the registered channels.
|
|
31
31
|
- `Notey::Current` — holds the person and the account id for the current
|
|
32
32
|
request, and every preference read, preference save and inbox read goes
|
|
33
33
|
through it.
|
|
34
34
|
- `Notey.notification_url=` — takes a lambda returning the host's own url for
|
|
35
35
|
one notification, used to link the rows in a digest email.
|
|
36
|
-
- `Notey.check!` — raises when a
|
|
37
|
-
|
|
38
|
-
it itself after initialization when
|
|
36
|
+
- `Notey.check!` — raises when a registered channel names a delivery method that
|
|
37
|
+
does not exist or needs an option notey does not supply, and when notey has no
|
|
38
|
+
sender address for its digests. notey runs it itself after initialization when
|
|
39
|
+
the app eager loads.
|
|
39
40
|
- `/notey/preferences` — the page a person picks their channels and their
|
|
40
41
|
immediate, daily or weekly window on, per notification type.
|
|
41
42
|
- `/notey/notifications` — the person's inbox for the account they are in, 25
|
|
42
43
|
rows newest first with an unread count, and a button that marks one read.
|
|
43
44
|
- `/notey/destinations` — the page an account's address and credential are
|
|
44
|
-
stored on, one pair per channel
|
|
45
|
+
stored on, one pair per registered channel that needs an address.
|
|
45
46
|
|
|
46
47
|
The three paths assume the engine is mounted at `/notey`; a different mount path
|
|
47
48
|
moves all three.
|
|
@@ -51,8 +52,8 @@ moves all three.
|
|
|
51
52
|
1. Add `gem "notey"` to the host's `Gemfile` and run `bundle install`.
|
|
52
53
|
|
|
53
54
|
2. Run `bin/rails notey:install:migrations` and then `bin/rails db:migrate`.
|
|
54
|
-
This creates `notey_preferences`, `notey_digests
|
|
55
|
-
the host's database.
|
|
55
|
+
This creates `notey_preferences`, `notey_digests`, `notey_destinations` and
|
|
56
|
+
`notey_attempts` in the host's database.
|
|
56
57
|
|
|
57
58
|
3. Mount the engine in the host's `config/routes.rb`:
|
|
58
59
|
|
|
@@ -63,18 +64,18 @@ moves all three.
|
|
|
63
64
|
Ask the developer which path to mount at if the app already has a convention
|
|
64
65
|
for engine paths, since the path prefixes all three pages.
|
|
65
66
|
|
|
66
|
-
4.
|
|
67
|
+
4. Register the channels the application sends on, in
|
|
68
|
+
`config/initializers/notey.rb`:
|
|
67
69
|
|
|
68
70
|
```ruby
|
|
69
|
-
Notey.
|
|
70
|
-
notification :comment, channels: %w[email sms], default: %w[email]
|
|
71
|
-
end
|
|
71
|
+
Notey.channel :sms, delivery_method: "Noticed::DeliveryMethods::TwilioMessaging", addressed: true
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
Every application has email and in-app already, so register nothing to get
|
|
75
|
+
those two. Ask the developer which other channels this application sends on,
|
|
76
|
+
which delivery method sends each one, and which of them need an address
|
|
77
|
+
before anything can go out. A channel registered here is offered for every
|
|
78
|
+
notification type; there is no way to keep a type off one.
|
|
78
79
|
|
|
79
80
|
5. Include the recipient concern in the model that receives notifications:
|
|
80
81
|
|
|
@@ -99,7 +100,7 @@ moves all three.
|
|
|
99
100
|
|
|
100
101
|
Ask the developer what supplies the signed-in person and the current account
|
|
101
102
|
in this app, since both names above are guesses about the host. A read with
|
|
102
|
-
no account set returns
|
|
103
|
+
no account set returns email and in-app and an empty inbox,
|
|
103
104
|
never another account's rows, and a save on the preferences page fails
|
|
104
105
|
because a stored preference needs an account.
|
|
105
106
|
|
|
@@ -125,17 +126,18 @@ moves all three.
|
|
|
125
126
|
Without these columns the inbox is empty and every digest is, because a
|
|
126
127
|
delivery runs in a job where the current account is gone.
|
|
127
128
|
|
|
128
|
-
8.
|
|
129
|
-
the catalog's types:
|
|
129
|
+
8. Define one notification type so there is something to send:
|
|
130
130
|
|
|
131
131
|
```ruby
|
|
132
|
-
class
|
|
133
|
-
|
|
132
|
+
class CommentNotification < Notey::Notification
|
|
133
|
+
notey_type :comment
|
|
134
|
+
|
|
135
|
+
required_params :comment_id
|
|
134
136
|
end
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
What each type carries, what a person reads on it, and sending one are
|
|
140
|
+
`notey-develop`'s steps rather than this local's.
|
|
139
141
|
|
|
140
142
|
9. Point digest links at the host's own pages in
|
|
141
143
|
`config/initializers/notey.rb`:
|
|
@@ -150,8 +152,9 @@ moves all three.
|
|
|
150
152
|
set, a digest still sends and its rows are plain text instead of links.
|
|
151
153
|
|
|
152
154
|
10. Boot the app with eager loading on and watch it raise or come up. notey runs
|
|
153
|
-
`Notey.check!` itself at that point
|
|
154
|
-
to
|
|
155
|
+
`Notey.check!` itself at that point, which refuses a channel that cannot
|
|
156
|
+
send and refuses to run with no sender address for its digests; call it
|
|
157
|
+
directly in a test or a console to check the same thing without a boot.
|
|
155
158
|
|
|
156
159
|
11. Decide the pages' layout. They render in notey's own layout, which loads
|
|
157
160
|
notey's stylesheet and nothing of the host's, unless the host defines
|
|
@@ -159,8 +162,7 @@ moves all three.
|
|
|
159
162
|
the engine's copy. Ask the developer whether these pages should carry the
|
|
160
163
|
app's navigation.
|
|
161
164
|
|
|
162
|
-
12. Set up destinations only if a channel
|
|
163
|
-
account rather than at a person. The stored credential is encrypted, so
|
|
165
|
+
12. Set up destinations only if a registered channel needs an address. The stored credential is encrypted, so
|
|
164
166
|
Active Record encryption keys must be configured in the host's credentials
|
|
165
167
|
(`bin/rails db:encryption:init` generates a set) or saving a destination
|
|
166
168
|
raises. Ask the developer whether any channel needs this before doing it.
|
|
@@ -168,16 +170,18 @@ moves all three.
|
|
|
168
170
|
## Conventions
|
|
169
171
|
|
|
170
172
|
- **Check each page while signed in with an account set.** `/notey/preferences`
|
|
171
|
-
lists one panel per
|
|
172
|
-
|
|
173
|
-
address and credential field per
|
|
173
|
+
lists one panel per notification type with every registered channel against
|
|
174
|
+
it, `/notey/notifications` lists that person's notifications for the current
|
|
175
|
+
account, and `/notey/destinations` lists one address and credential field per
|
|
176
|
+
registered channel that needs an address.
|
|
174
177
|
- **A preferences page that will not save is the symptom of a missing account.**
|
|
175
|
-
Reads fall back to
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
Reads fall back to email and in-app when `Notey::Current.account_id` is nil
|
|
179
|
+
and a save is refused, so check step 6 before anything else.
|
|
180
|
+
- **A registration for a channel notey already has replaces it.** Registering
|
|
181
|
+
`:email` with no delivery method leaves notey's own email delivery behind, and
|
|
182
|
+
the app refuses to boot rather than sending nothing, so register a channel
|
|
183
|
+
notey supplies only to replace it deliberately and with a delivery method that
|
|
184
|
+
can send.
|
|
181
185
|
- **Authentication on the three pages is the host's.** Its controllers inherit
|
|
182
186
|
from the host's `ApplicationController`, so the host's own filters run and
|
|
183
187
|
notey adds no authorization of its own — restricting who may set an account's
|
|
@@ -186,6 +190,6 @@ moves all three.
|
|
|
186
190
|
that changes it.
|
|
187
191
|
- **Re-run `bin/rails notey:install:migrations` after upgrading the gem.** It
|
|
188
192
|
copies only the migrations the host does not already have.
|
|
189
|
-
- **Out of scope.** Scheduling the digest runs,
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
- **Out of scope.** Scheduling the digest runs, writing a notification type,
|
|
194
|
+
sending one when a domain event fires, and reading the inbox from the host's
|
|
195
|
+
own code all belong to `notey-develop`.
|
data/the_local/interface.yml
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
scope: notifications —
|
|
1
|
+
scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
|
|
2
2
|
|
|
3
3
|
install:
|
|
4
4
|
- bin/rails notey:install:migrations
|
|
5
5
|
- mount Notey::Engine
|
|
6
|
-
- Notey.
|
|
6
|
+
- Notey.channel
|
|
7
7
|
- Notey::Recipient
|
|
8
|
-
- Notey::
|
|
8
|
+
- Notey::Notification
|
|
9
9
|
- Notey::Current
|
|
10
10
|
- Notey.notification_url=
|
|
11
11
|
- Notey.check!
|
|
@@ -15,10 +15,9 @@ install:
|
|
|
15
15
|
|
|
16
16
|
develop:
|
|
17
17
|
- notey_type
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Notey.
|
|
21
|
-
- Notey.addressed
|
|
18
|
+
- required_params
|
|
19
|
+
- notify
|
|
20
|
+
- Notey::Destinations.for
|
|
22
21
|
- Notey::DigestRun
|
|
23
22
|
- Notey::Inbox.for
|
|
24
23
|
- wants?
|
|
@@ -29,20 +28,23 @@ develop:
|
|
|
29
28
|
sources:
|
|
30
29
|
- lib/notey.rb
|
|
31
30
|
- lib/notey/engine.rb
|
|
32
|
-
- lib/notey/catalog.rb
|
|
33
31
|
- lib/notey/channels.rb
|
|
34
32
|
- lib/notey/destinations.rb
|
|
35
33
|
- lib/notey/digest_run.rb
|
|
36
|
-
- lib/notey/event_subscriber.rb
|
|
37
34
|
- lib/notey/inbox.rb
|
|
35
|
+
- lib/notey/records_attempt.rb
|
|
38
36
|
- app/models/concerns/notey/recipient.rb
|
|
39
|
-
- app/models/
|
|
37
|
+
- app/models/notey/notification.rb
|
|
40
38
|
- app/models/notey/current.rb
|
|
41
39
|
- app/models/notey/preference.rb
|
|
42
40
|
- app/models/notey/destination.rb
|
|
43
41
|
- app/models/notey/digest.rb
|
|
42
|
+
- app/models/notey/attempt.rb
|
|
43
|
+
- app/delivery_methods/notey/email.rb
|
|
44
|
+
- app/delivery_methods/notey/in_app.rb
|
|
44
45
|
- app/jobs/notey/digest_job.rb
|
|
45
46
|
- app/mailers/notey/digest_mailer.rb
|
|
47
|
+
- app/mailers/notey/notification_mailer.rb
|
|
46
48
|
- app/controllers/notey/preferences_controller.rb
|
|
47
49
|
- app/controllers/notey/notifications_controller.rb
|
|
48
50
|
- app/controllers/notey/destinations_controller.rb
|
|
@@ -50,3 +52,4 @@ sources:
|
|
|
50
52
|
- db/migrate/20260917000002_create_notey_preferences.rb
|
|
51
53
|
- db/migrate/20260918030002_create_notey_digests.rb
|
|
52
54
|
- db/migrate/20260918050001_create_notey_destinations.rb
|
|
55
|
+
- db/migrate/20260921000001_create_notey_attempts.rb
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: notey
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
@@ -84,7 +84,6 @@ files:
|
|
|
84
84
|
- app/mailers/notey/application_mailer.rb
|
|
85
85
|
- app/mailers/notey/digest_mailer.rb
|
|
86
86
|
- app/mailers/notey/notification_mailer.rb
|
|
87
|
-
- app/models/concerns/notey/notifier.rb
|
|
88
87
|
- app/models/concerns/notey/recipient.rb
|
|
89
88
|
- app/models/notey/application_record.rb
|
|
90
89
|
- app/models/notey/attempt.rb
|
|
@@ -115,12 +114,10 @@ files:
|
|
|
115
114
|
- db/migrate/20260919010001_add_member_to_notey_destinations.rb
|
|
116
115
|
- db/migrate/20260921000001_create_notey_attempts.rb
|
|
117
116
|
- lib/notey.rb
|
|
118
|
-
- lib/notey/catalog.rb
|
|
119
117
|
- lib/notey/channels.rb
|
|
120
118
|
- lib/notey/destinations.rb
|
|
121
119
|
- lib/notey/digest_run.rb
|
|
122
120
|
- lib/notey/engine.rb
|
|
123
|
-
- lib/notey/event_delivery.rb
|
|
124
121
|
- lib/notey/inbox.rb
|
|
125
122
|
- lib/notey/records_attempt.rb
|
|
126
123
|
- lib/notey/version.rb
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Notey
|
|
4
|
-
module Notifier
|
|
5
|
-
extend ActiveSupport::Concern
|
|
6
|
-
|
|
7
|
-
included do
|
|
8
|
-
class_attribute :notey_notification_type, instance_writer: false
|
|
9
|
-
|
|
10
|
-
Notey.register_notifier(self)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
class_methods do
|
|
14
|
-
def notey_type(name)
|
|
15
|
-
self.notey_notification_type = name.to_s
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
data/lib/notey/catalog.rb
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Notey
|
|
4
|
-
class Catalog
|
|
5
|
-
Notification = Struct.new(:channels, :default, keyword_init: true)
|
|
6
|
-
|
|
7
|
-
def initialize
|
|
8
|
-
@notifications = {}
|
|
9
|
-
@addressed = []
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def addressed(*channels)
|
|
13
|
-
return @addressed if channels.empty?
|
|
14
|
-
|
|
15
|
-
@addressed |= channels.map(&:to_s)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def notification(name, channels: [], default: [])
|
|
19
|
-
@notifications[name.to_s] = Notification.new(
|
|
20
|
-
channels: Array(channels).map(&:to_s),
|
|
21
|
-
default: Array(default).map(&:to_s)
|
|
22
|
-
)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def notifications
|
|
26
|
-
@notifications
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def channels
|
|
30
|
-
@notifications.values.flat_map(&:channels).uniq
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def declared?(notification_type)
|
|
34
|
-
@notifications.key?(notification_type.to_s)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def channels_for(notification_type)
|
|
38
|
-
declared(notification_type)&.channels || []
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def default_channels_for(notification_type)
|
|
42
|
-
declared(notification_type)&.default || []
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def declared(notification_type)
|
|
48
|
-
@notifications[notification_type.to_s]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
data/lib/notey/event_delivery.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Notey
|
|
4
|
-
class EventDelivery
|
|
5
|
-
def self.call(event)
|
|
6
|
-
notifier = Notey.notifier_for(event.event_name)
|
|
7
|
-
return if notifier.nil?
|
|
8
|
-
|
|
9
|
-
payload = event.payload.to_h.symbolize_keys
|
|
10
|
-
|
|
11
|
-
Current.set(account_id: payload[:account_id]) { notifier.with(**payload).deliver }
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|