broadcast-ruby 0.4.0 → 0.5.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/CHANGELOG.md +45 -1
- data/Gemfile.lock +2 -2
- data/README.md +85 -6
- data/SDK-COVERAGE.md +1 -1
- data/lib/broadcast/client.rb +14 -0
- data/lib/broadcast/connection.rb +1 -0
- data/lib/broadcast/resources/channel.rb +22 -0
- data/lib/broadcast/resources/discovery.rb +12 -0
- data/lib/broadcast/resources/sequences.rb +7 -2
- data/lib/broadcast/resources/users.rb +103 -0
- data/lib/broadcast/version.rb +1 -1
- data/lib/broadcast/webhook.rb +3 -1
- data/lib/broadcast.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06eed741f3800e40f0734e35389d38fe527b22508b4bd623621a7eb54bb0ed3e
|
|
4
|
+
data.tar.gz: 8dd349d9829003e8895f1d2612d5c86cd5248037307a32313a4e6b05f943a1d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 425b9de89f5fc7448483c66e082a42ef1a0b81dc13e7e8ec0067d5c43cf02da3197f7c80826b1b896ea59fe79abe631fd37b2b38e7180fa4f37320e58698d91d
|
|
7
|
+
data.tar.gz: f3d825c247f222b67e8d76e63a9fbaf14cab5a6a1ad81768ec22a9712c0a56de8ca10733b99a5e26755ae20618fe25228465935129e33a7d4254312ee6b8c01d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.5.0] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `subscribers.purged` and `subscribers.purge_failed` webhook event types, in
|
|
9
|
+
`Broadcast::Webhook::SUBSCRIBER_EVENTS` and `EVENT_TYPES` (now 34). A purge
|
|
10
|
+
of the whole list sends one of these instead of a `subscriber.deleted` per
|
|
11
|
+
subscriber.
|
|
12
|
+
- `Users` resource (`client.users`) for the admin-only Users API: `list`,
|
|
13
|
+
`get_user`, `create`, `update`, `deactivate`, `activate`, `delete`, plus
|
|
14
|
+
`channel_permissions`/`set_channel_permissions`/`remove_channel_permissions`/
|
|
15
|
+
`bulk_channel_permissions` and `system_permissions`/`update_system_permissions`.
|
|
16
|
+
Requires an admin API token — a channel token raises
|
|
17
|
+
`Broadcast::AuthorizationError`. Sudo users are read-only through this API
|
|
18
|
+
and sudo access can never be granted. `set_channel_permissions` and
|
|
19
|
+
`bulk_channel_permissions` accept exactly one of `permissions:`, `role:`,
|
|
20
|
+
or `preset_id:`, raising `ArgumentError` otherwise; the underlying `PUT` is
|
|
21
|
+
a full replace of the channel's permission record. `Connection` gained PUT
|
|
22
|
+
support to carry this call.
|
|
23
|
+
- `Channel#design` (`client.channel.design`) for `GET /api/v1/channel/design`,
|
|
24
|
+
which returns the token's channel brand kit, fully resolved: colours, font
|
|
25
|
+
key and stack, layout, logo URL, website and social links. Read-only; needs
|
|
26
|
+
the `templates_read` permission.
|
|
27
|
+
|
|
28
|
+
## [0.4.1] - 2026-08-16
|
|
29
|
+
|
|
30
|
+
### Fixed — sequence enrolment never worked
|
|
31
|
+
|
|
32
|
+
`Sequences#add_subscriber` and `#remove_subscriber` sent the subscriber
|
|
33
|
+
attributes flat, but the API reads them with `params.require(:subscriber)`.
|
|
34
|
+
Every call was rejected:
|
|
35
|
+
|
|
36
|
+
Broadcast::APIError: param is missing or the value is empty or invalid: subscriber
|
|
37
|
+
|
|
38
|
+
Both now nest the attributes under a `subscriber` key.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Discovery#openapi` for `GET /api/v1/openapi`, which returns the
|
|
43
|
+
installation's own OpenAPI document.
|
|
44
|
+
|
|
45
|
+
This was invisible to the test suite because the tests asserted the shape the
|
|
46
|
+
code already sent (`hash_including('email' => ...)` at the top level) rather
|
|
47
|
+
than the shape the server requires — so they encoded the bug instead of
|
|
48
|
+
catching it. Found only by a real enrolment against a live instance; the tests
|
|
49
|
+
now pin the nested payload.
|
|
6
50
|
|
|
7
51
|
## [0.4.0] - 2026-08-14
|
|
8
52
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
broadcast-ruby (0.
|
|
4
|
+
broadcast-ruby (0.5.0)
|
|
5
5
|
base64
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -177,7 +177,7 @@ CHECKSUMS
|
|
|
177
177
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
178
178
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
179
179
|
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
180
|
-
broadcast-ruby (0.
|
|
180
|
+
broadcast-ruby (0.5.0)
|
|
181
181
|
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
|
182
182
|
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
183
183
|
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
data/README.md
CHANGED
|
@@ -799,6 +799,23 @@ client.templates.update(1, subject: 'Updated subject')
|
|
|
799
799
|
client.templates.delete(1)
|
|
800
800
|
```
|
|
801
801
|
|
|
802
|
+
### Brand Kit
|
|
803
|
+
|
|
804
|
+
Read the channel's brand kit (Settings → Design) so your own templates can
|
|
805
|
+
match it. Read-only, fully resolved (defaults filled in), and always the
|
|
806
|
+
token's own channel, so it takes no arguments. Needs `templates_read`.
|
|
807
|
+
|
|
808
|
+
```ruby
|
|
809
|
+
design = client.channel.design
|
|
810
|
+
design['colors']['accent'] # => '#2563eb'
|
|
811
|
+
design['typography']['font'] # => 'georgia'
|
|
812
|
+
design['typography']['font_stack'] # => email-safe CSS font stack
|
|
813
|
+
design['layout'] # => { 'width' => 600, 'radius' => 8 }
|
|
814
|
+
design['brand']['logo_url'] # => public URL, or nil when no logo is set
|
|
815
|
+
design['brand']['website_url'] # => 'https://acme.example'
|
|
816
|
+
design['brand']['social_links'] # => [{ 'network' => 'x', 'url' => 'https://x.com/acme' }]
|
|
817
|
+
```
|
|
818
|
+
|
|
802
819
|
---
|
|
803
820
|
|
|
804
821
|
## Opt-In Forms
|
|
@@ -962,6 +979,64 @@ admin_client.email_servers.copy_to_channel(99, target_channel_id: 7)
|
|
|
962
979
|
|
|
963
980
|
---
|
|
964
981
|
|
|
982
|
+
## Users
|
|
983
|
+
|
|
984
|
+
Manage installation users and their permissions. **Requires an admin/system API token** -- a regular per-channel token gets `Broadcast::AuthorizationError` ("Admin API token required for user management"). Sudo users are read-only through this API: `update`, `deactivate`, `activate`, `delete`, and any permission write against a sudo user raise `Broadcast::AuthorizationError`. Sudo access can never be granted through this resource.
|
|
985
|
+
|
|
986
|
+
```ruby
|
|
987
|
+
# List / search
|
|
988
|
+
result = admin_client.users.list
|
|
989
|
+
result = admin_client.users.list(q: 'ada', status: 'active', limit: 10, offset: 0)
|
|
990
|
+
|
|
991
|
+
# Get a single user (includes system_permissions and channel_permissions)
|
|
992
|
+
user = admin_client.users.get_user(3)
|
|
993
|
+
|
|
994
|
+
# Create -- one of password: or send_password_reset: true is required
|
|
995
|
+
admin_client.users.create(
|
|
996
|
+
email: 'ada@example.com',
|
|
997
|
+
first_name: 'Ada',
|
|
998
|
+
last_name: 'Lovelace',
|
|
999
|
+
send_password_reset: true
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
# Update -- pass only the fields you want to change
|
|
1003
|
+
admin_client.users.update(3, first_name: 'Grace')
|
|
1004
|
+
|
|
1005
|
+
admin_client.users.deactivate(3)
|
|
1006
|
+
admin_client.users.activate(3) # also clears lockout
|
|
1007
|
+
admin_client.users.delete(3)
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### Channel Permissions
|
|
1011
|
+
|
|
1012
|
+
`set_channel_permissions` is a `PUT` -- it **replaces the whole channel permission record**; any flag not named under `permissions:` becomes `false`. Pass exactly one of `permissions:`, `role:`, or `preset_id:`; passing zero or more than one raises `ArgumentError` before a request is sent.
|
|
1013
|
+
|
|
1014
|
+
```ruby
|
|
1015
|
+
admin_client.users.channel_permissions(3)
|
|
1016
|
+
|
|
1017
|
+
admin_client.users.set_channel_permissions(3, 7, permissions: { subscribers_read: true, broadcasts_write: true })
|
|
1018
|
+
admin_client.users.set_channel_permissions(3, 7, role: 'Editor')
|
|
1019
|
+
admin_client.users.set_channel_permissions(3, 7, preset_id: 12)
|
|
1020
|
+
|
|
1021
|
+
admin_client.users.remove_channel_permissions(3, 7)
|
|
1022
|
+
|
|
1023
|
+
# Apply the same permissions|role|preset_id to several channels at once.
|
|
1024
|
+
# Returns `applied` and `failed` (per-channel errors) rather than raising.
|
|
1025
|
+
admin_client.users.bulk_channel_permissions(3, broadcast_channel_ids: [7, 8], role: 'Viewer')
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### System Permissions
|
|
1029
|
+
|
|
1030
|
+
```ruby
|
|
1031
|
+
admin_client.users.system_permissions(3)
|
|
1032
|
+
|
|
1033
|
+
# PATCH changes only the flags named -- others are left as-is. `sudo_access`
|
|
1034
|
+
# is never accepted; sending it is a 422.
|
|
1035
|
+
admin_client.users.update_system_permissions(3, user_management: true)
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
965
1040
|
## Suppressions
|
|
966
1041
|
|
|
967
1042
|
A suppressed address is one Broadcast will not email. Each channel has its own list, and the installation has a global one; `client.suppressions` manages the current channel's list:
|
|
@@ -1056,9 +1131,9 @@ secret = result['secret']
|
|
|
1056
1131
|
|
|
1057
1132
|
# Every valid event type is available as a constant. An unknown event type is
|
|
1058
1133
|
# dropped server-side rather than rejected, so subscribe from these.
|
|
1059
|
-
Broadcast::Webhook::EVENT_TYPES # all
|
|
1134
|
+
Broadcast::Webhook::EVENT_TYPES # all 34
|
|
1060
1135
|
Broadcast::Webhook::EMAIL_EVENTS # email.sent, email.delivered, ...
|
|
1061
|
-
Broadcast::Webhook::SUBSCRIBER_EVENTS # subscriber.created,
|
|
1136
|
+
Broadcast::Webhook::SUBSCRIBER_EVENTS # subscriber.created, ..., subscribers.purged
|
|
1062
1137
|
Broadcast::Webhook::BROADCAST_EVENTS # broadcast.sending, broadcast.sent, ...
|
|
1063
1138
|
Broadcast::Webhook::SEQUENCE_EVENTS # sequence.subscriber_added, ...
|
|
1064
1139
|
Broadcast::Webhook::SYSTEM_EVENTS # message.attempt.exhausted, test.webhook
|
|
@@ -1088,11 +1163,14 @@ result['data'] # => [{'id' => 1, 'event_type' => 'email.sent', 'response_status
|
|
|
1088
1163
|
| Category | Events |
|
|
1089
1164
|
|----------|--------|
|
|
1090
1165
|
| Email | `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.opened`, `email.clicked`, `email.bounced`, `email.complained`, `email.failed` |
|
|
1091
|
-
| Subscriber | `subscriber.created`, `subscriber.updated`, `subscriber.deleted`, `subscriber.subscribed`, `subscriber.unsubscribed`, `subscriber.bounced`, `subscriber.complained` |
|
|
1166
|
+
| Subscriber | `subscriber.created`, `subscriber.updated`, `subscriber.deleted`, `subscriber.subscribed`, `subscriber.unsubscribed`, `subscriber.bounced`, `subscriber.complained`, `subscribers.purged`, `subscribers.purge_failed` |
|
|
1092
1167
|
| Broadcast | `broadcast.scheduled`, `broadcast.queueing`, `broadcast.sending`, `broadcast.sent`, `broadcast.failed`, `broadcast.partial_failure`, `broadcast.aborted`, `broadcast.paused` |
|
|
1093
1168
|
| Sequence | `sequence.subscriber_added`, `sequence.subscriber_completed`, `sequence.subscriber_moved`, `sequence.subscriber_removed`, `sequence.subscriber_paused`, `sequence.subscriber_resumed`, `sequence.subscriber_error` |
|
|
1094
1169
|
| System | `message.attempt.exhausted`, `test.webhook` |
|
|
1095
1170
|
|
|
1171
|
+
A purge of a channel's whole list sends one `subscribers.purged` (or
|
|
1172
|
+
`subscribers.purge_failed`) instead of a `subscriber.deleted` per subscriber.
|
|
1173
|
+
|
|
1096
1174
|
### Webhook Signature Verification
|
|
1097
1175
|
|
|
1098
1176
|
All incoming webhooks are signed with HMAC-SHA256. Here's a complete Rails controller:
|
|
@@ -1322,8 +1400,8 @@ All API errors inherit from `Broadcast::Error`. Put specific errors before gener
|
|
|
1322
1400
|
```ruby
|
|
1323
1401
|
begin
|
|
1324
1402
|
client.send_email(to: 'user@example.com', subject: 'Hi', body: 'Hello')
|
|
1325
|
-
rescue Broadcast::AuthenticationError # 401 -- invalid or expired API token
|
|
1326
|
-
rescue Broadcast::AuthorizationError # 403 --
|
|
1403
|
+
rescue Broadcast::AuthenticationError # 401 -- invalid or expired API token, or it lacks the required permission
|
|
1404
|
+
rescue Broadcast::AuthorizationError # 403 -- admin-only endpoint, or the user is sudo (read-only)
|
|
1327
1405
|
rescue Broadcast::NotFoundError # 404 -- resource does not exist
|
|
1328
1406
|
rescue Broadcast::ConflictError # 409 -- Idempotency-Key request still in flight
|
|
1329
1407
|
rescue Broadcast::ValidationError # 422 -- missing or invalid parameters
|
|
@@ -1357,9 +1435,10 @@ Each token can be scoped to specific resources. The ActionMailer delivery method
|
|
|
1357
1435
|
| Sequences | `sequences_read` -- list, get, list steps | `sequences_write` -- create, update, delete, manage steps, enroll subscribers |
|
|
1358
1436
|
| Broadcasts | `broadcasts_read` -- list, get, statistics | `broadcasts_write` -- create, update, delete, send, schedule |
|
|
1359
1437
|
| Segments | `segments_read` -- list, get | `segments_write` -- create, update, delete |
|
|
1360
|
-
| Templates | `templates_read` -- list, get | `templates_write` -- create, update, delete |
|
|
1438
|
+
| Templates | `templates_read` -- list, get, channel brand kit (`channel.design`) | `templates_write` -- create, update, delete |
|
|
1361
1439
|
| Opt-In Forms | `opt_in_forms_read` -- list, get, analytics | `opt_in_forms_write` -- create, update, delete, create_variant, duplicate |
|
|
1362
1440
|
| Email Servers | `email_servers_read` -- list, get | `email_servers_write` -- create, update, delete, test_connection, copy_to_channel (admin) |
|
|
1441
|
+
| Users (admin token only) | `users_read` -- list, get, channel_permissions, system_permissions | `users_write` -- create, update, deactivate, activate, delete, permission writes |
|
|
1363
1442
|
| Webhook Endpoints | `webhook_endpoints_read` -- list, get, deliveries | `webhook_endpoints_write` -- create, update, delete, test |
|
|
1364
1443
|
| Autopilot | `autopilot_read` -- list, get, runs | `autopilot_write` -- create, update, delete, activate, pause, deactivate, trigger_run |
|
|
1365
1444
|
| Suppressions | `suppressions_read` -- list, check | `suppressions_write` -- add, remove, bulk add/remove |
|
data/SDK-COVERAGE.md
CHANGED
|
@@ -330,7 +330,7 @@ Inbound webhooks — the app calling you.
|
|
|
330
330
|
| HMAC-SHA256 verification (`v1,<base64>`) | ✅ `Webhook.verify` |
|
|
331
331
|
| 5-minute timestamp tolerance | ✅ Replay protection |
|
|
332
332
|
| Constant-time comparison | ✅ `OpenSSL.fixed_length_secure_compare` |
|
|
333
|
-
| Event type constants | ✅ `Webhook::EVENT_TYPES` —
|
|
333
|
+
| Event type constants | ✅ `Webhook::EVENT_TYPES` — 34 values |
|
|
334
334
|
|
|
335
335
|
Categories: `EMAIL_EVENTS` (8), `SUBSCRIBER_EVENTS` (7), `BROADCAST_EVENTS` (8),
|
|
336
336
|
`SEQUENCE_EVENTS` (7), `SYSTEM_EVENTS` (2).
|
data/lib/broadcast/client.rb
CHANGED
|
@@ -77,6 +77,10 @@ module Broadcast
|
|
|
77
77
|
discovery.skill
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
def openapi
|
|
81
|
+
discovery.openapi
|
|
82
|
+
end
|
|
83
|
+
|
|
80
84
|
# --- Resource sub-clients ---
|
|
81
85
|
|
|
82
86
|
def subscribers
|
|
@@ -115,6 +119,11 @@ module Broadcast
|
|
|
115
119
|
@email_servers ||= Resources::EmailServers.new(self)
|
|
116
120
|
end
|
|
117
121
|
|
|
122
|
+
# Installation users and their permissions. Requires an admin API token.
|
|
123
|
+
def users
|
|
124
|
+
@users ||= Resources::Users.new(self)
|
|
125
|
+
end
|
|
126
|
+
|
|
118
127
|
def autopilots
|
|
119
128
|
@autopilots ||= Resources::Autopilots.new(self)
|
|
120
129
|
end
|
|
@@ -135,6 +144,11 @@ module Broadcast
|
|
|
135
144
|
@global_suppressions ||= Resources::GlobalSuppressions.new(self)
|
|
136
145
|
end
|
|
137
146
|
|
|
147
|
+
# The token's own channel, e.g. `client.channel.design` for its brand kit.
|
|
148
|
+
def channel
|
|
149
|
+
@channel ||= Resources::Channel.new(self)
|
|
150
|
+
end
|
|
151
|
+
|
|
138
152
|
# Read-only export endpoints under /api/migration/v1. Requires an admin
|
|
139
153
|
# (system) API token.
|
|
140
154
|
def migration
|
data/lib/broadcast/connection.rb
CHANGED
|
@@ -118,6 +118,7 @@ module Broadcast
|
|
|
118
118
|
when :get then Net::HTTP::Get
|
|
119
119
|
when :post then Net::HTTP::Post
|
|
120
120
|
when :patch then Net::HTTP::Patch
|
|
121
|
+
when :put then Net::HTTP::Put
|
|
121
122
|
when :delete then Net::HTTP::Delete
|
|
122
123
|
else raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
123
124
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Broadcast
|
|
4
|
+
module Resources
|
|
5
|
+
# The token's own channel. Reads only the channel the token resolves to
|
|
6
|
+
# (or, for an admin token, the channel it is scoped to); there is no
|
|
7
|
+
# channel id argument.
|
|
8
|
+
class Channel < Base
|
|
9
|
+
# The channel's brand kit from Settings -> Design, fully resolved
|
|
10
|
+
# (defaults filled in), read-only. Requires the `templates_read`
|
|
11
|
+
# permission.
|
|
12
|
+
#
|
|
13
|
+
# Returns a Hash with `colors`, `typography` (`font` key and email-safe
|
|
14
|
+
# `font_stack`), `layout` (`width`, `radius`), and `brand` (`logo_url` as
|
|
15
|
+
# a public URL or nil, `logo_width`, `website_url`, `social_links`,
|
|
16
|
+
# `social_icon_style`).
|
|
17
|
+
def design
|
|
18
|
+
get('/api/v1/channel/design')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -31,6 +31,18 @@ module Broadcast
|
|
|
31
31
|
def skill
|
|
32
32
|
@client.request(:get, '/api/v1/skill', nil, raw: true)
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
# This installation's own OpenAPI document, as YAML. Returns a String,
|
|
36
|
+
# not a Hash — the endpoint serves application/yaml.
|
|
37
|
+
#
|
|
38
|
+
# The server URL in the document is rewritten by the installation to the
|
|
39
|
+
# host that served it, so the result feeds a client generator or an API
|
|
40
|
+
# explorer without hand-editing. Worth preferring over a spec copied from
|
|
41
|
+
# anywhere else: a 2.28 install serves the 2.28 surface, so the document
|
|
42
|
+
# cannot drift from the routes it describes.
|
|
43
|
+
def openapi
|
|
44
|
+
@client.request(:get, '/api/v1/openapi', nil, raw: true)
|
|
45
|
+
end
|
|
34
46
|
end
|
|
35
47
|
end
|
|
36
48
|
end
|
|
@@ -26,12 +26,17 @@ module Broadcast
|
|
|
26
26
|
|
|
27
27
|
# --- Subscriber enrollment ---
|
|
28
28
|
|
|
29
|
+
# Both of these nest the attributes under `subscriber`, because the API
|
|
30
|
+
# reads them with `params.require(:subscriber)`. Sent flat they are
|
|
31
|
+
# rejected outright with "param is missing or the value is empty or
|
|
32
|
+
# invalid: subscriber".
|
|
29
33
|
def add_subscriber(sequence_id, **attrs)
|
|
30
|
-
post("/api/v1/sequences/#{sequence_id}/add_subscriber", attrs)
|
|
34
|
+
post("/api/v1/sequences/#{sequence_id}/add_subscriber", { subscriber: attrs })
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def remove_subscriber(sequence_id, email:)
|
|
34
|
-
@client.request(:delete, "/api/v1/sequences/#{sequence_id}/remove_subscriber",
|
|
38
|
+
@client.request(:delete, "/api/v1/sequences/#{sequence_id}/remove_subscriber",
|
|
39
|
+
{ subscriber: { email: email } })
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
def list_subscribers(sequence_id, page: 1)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Broadcast
|
|
4
|
+
module Resources
|
|
5
|
+
# Manage installation users, their per-channel permissions, and their
|
|
6
|
+
# system-wide permissions. **Requires an admin API token** — a regular
|
|
7
|
+
# channel token gets `Broadcast::AuthorizationError`
|
|
8
|
+
# ("Admin API token required for user management").
|
|
9
|
+
#
|
|
10
|
+
# Sudo users are read-only through this API: `update`, `deactivate`,
|
|
11
|
+
# `activate`, `delete`, and any permission write against a sudo user
|
|
12
|
+
# raise `Broadcast::AuthorizationError`
|
|
13
|
+
# ("Sudo users cannot be changed through the API"). Sudo access can never
|
|
14
|
+
# be granted through this resource.
|
|
15
|
+
class Users < Base
|
|
16
|
+
# rubocop:disable Naming/MethodParameterName -- `q` matches the API's own query param name
|
|
17
|
+
def list(limit: nil, offset: nil, q: nil, status: nil)
|
|
18
|
+
# rubocop:enable Naming/MethodParameterName
|
|
19
|
+
params = {}
|
|
20
|
+
params[:limit] = limit unless limit.nil?
|
|
21
|
+
params[:offset] = offset unless offset.nil?
|
|
22
|
+
params[:q] = q unless q.nil?
|
|
23
|
+
params[:status] = status unless status.nil?
|
|
24
|
+
get('/api/v1/users', params)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_user(id)
|
|
28
|
+
get("/api/v1/users/#{id}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create(**attrs)
|
|
32
|
+
post('/api/v1/users', { user: attrs })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Update a user. Attrs are wrapped under `user:` on the wire. Raises
|
|
36
|
+
# `Broadcast::AuthorizationError` if the target user is a sudo user.
|
|
37
|
+
def update(id, **attrs)
|
|
38
|
+
patch("/api/v1/users/#{id}", { user: attrs })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def deactivate(id)
|
|
42
|
+
post("/api/v1/users/#{id}/deactivate")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Reactivates the user and also clears any lockout.
|
|
46
|
+
def activate(id)
|
|
47
|
+
post("/api/v1/users/#{id}/activate")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete(id)
|
|
51
|
+
@client.request(:delete, "/api/v1/users/#{id}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def channel_permissions(id)
|
|
55
|
+
get("/api/v1/users/#{id}/channel_permissions")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Set a user's permissions for one channel. This is a PUT: it REPLACES
|
|
59
|
+
# the whole channel permission record, so any flag not named under
|
|
60
|
+
# `permissions:` becomes false. Pass exactly one of `permissions:`,
|
|
61
|
+
# `role:`, or `preset_id:` — anything else raises `ArgumentError`
|
|
62
|
+
# client-side before a request is made. Sudo can never be granted this
|
|
63
|
+
# way, and doing so is rejected server-side.
|
|
64
|
+
def set_channel_permissions(id, broadcast_channel_id, permissions: nil, role: nil, preset_id: nil)
|
|
65
|
+
body = exactly_one_option!(permissions: permissions, role: role, preset_id: preset_id)
|
|
66
|
+
@client.request(:put, "/api/v1/users/#{id}/channel_permissions/#{broadcast_channel_id}", body)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def remove_channel_permissions(id, broadcast_channel_id)
|
|
70
|
+
@client.request(:delete, "/api/v1/users/#{id}/channel_permissions/#{broadcast_channel_id}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Apply the same permissions|role|preset_id (exactly one, same rule as
|
|
74
|
+
# `set_channel_permissions`) to several channels at once. Returns
|
|
75
|
+
# `applied` and `failed` (per-channel errors), rather than raising, for
|
|
76
|
+
# channels that could not be updated.
|
|
77
|
+
def bulk_channel_permissions(id, broadcast_channel_ids:, permissions: nil, role: nil, preset_id: nil)
|
|
78
|
+
body = exactly_one_option!(permissions: permissions, role: role, preset_id: preset_id)
|
|
79
|
+
body[:broadcast_channel_ids] = broadcast_channel_ids
|
|
80
|
+
post("/api/v1/users/#{id}/channel_permissions/bulk", body)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def system_permissions(id)
|
|
84
|
+
get("/api/v1/users/#{id}/system_permissions")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Updates only the flags named in `permissions` -- unnamed flags are
|
|
88
|
+
# left as-is. `sudo_access` is never accepted; sending it is a 422.
|
|
89
|
+
def update_system_permissions(id, permissions)
|
|
90
|
+
patch("/api/v1/users/#{id}/system_permissions", { permissions: permissions })
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def exactly_one_option!(permissions:, role:, preset_id:)
|
|
96
|
+
given = { permissions: permissions, role: role, preset_id: preset_id }.compact
|
|
97
|
+
raise ArgumentError, 'Pass exactly one of permissions:, role:, or preset_id:' unless given.size == 1
|
|
98
|
+
|
|
99
|
+
given
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/broadcast/version.rb
CHANGED
data/lib/broadcast/webhook.rb
CHANGED
|
@@ -15,10 +15,12 @@ module Broadcast
|
|
|
15
15
|
email.bounced email.opened email.clicked email.failed
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
|
+
# subscribers.* are sent once for a whole-list purge, in place of a
|
|
19
|
+
# subscriber.deleted per row.
|
|
18
20
|
SUBSCRIBER_EVENTS = %w[
|
|
19
21
|
subscriber.created subscriber.updated subscriber.deleted
|
|
20
22
|
subscriber.subscribed subscriber.unsubscribed subscriber.bounced
|
|
21
|
-
subscriber.complained
|
|
23
|
+
subscriber.complained subscribers.purged subscribers.purge_failed
|
|
22
24
|
].freeze
|
|
23
25
|
|
|
24
26
|
BROADCAST_EVENTS = %w[
|
data/lib/broadcast.rb
CHANGED
|
@@ -18,10 +18,12 @@ require_relative 'broadcast/resources/webhook_endpoints'
|
|
|
18
18
|
require_relative 'broadcast/resources/transactionals'
|
|
19
19
|
require_relative 'broadcast/resources/opt_in_forms'
|
|
20
20
|
require_relative 'broadcast/resources/email_servers'
|
|
21
|
+
require_relative 'broadcast/resources/users'
|
|
21
22
|
require_relative 'broadcast/resources/autopilots'
|
|
22
23
|
require_relative 'broadcast/resources/discovery'
|
|
23
24
|
require_relative 'broadcast/resources/suppressions'
|
|
24
25
|
require_relative 'broadcast/resources/global_suppressions'
|
|
26
|
+
require_relative 'broadcast/resources/channel'
|
|
25
27
|
require_relative 'broadcast/resources/migration'
|
|
26
28
|
|
|
27
29
|
# ActionMailer integration — only loaded when Rails is present
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: broadcast-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Chiu
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- lib/broadcast/resources/autopilots.rb
|
|
51
51
|
- lib/broadcast/resources/base.rb
|
|
52
52
|
- lib/broadcast/resources/broadcasts.rb
|
|
53
|
+
- lib/broadcast/resources/channel.rb
|
|
53
54
|
- lib/broadcast/resources/discovery.rb
|
|
54
55
|
- lib/broadcast/resources/email_servers.rb
|
|
55
56
|
- lib/broadcast/resources/global_suppressions.rb
|
|
@@ -61,6 +62,7 @@ files:
|
|
|
61
62
|
- lib/broadcast/resources/suppressions.rb
|
|
62
63
|
- lib/broadcast/resources/templates.rb
|
|
63
64
|
- lib/broadcast/resources/transactionals.rb
|
|
65
|
+
- lib/broadcast/resources/users.rb
|
|
64
66
|
- lib/broadcast/resources/webhook_endpoints.rb
|
|
65
67
|
- lib/broadcast/response.rb
|
|
66
68
|
- lib/broadcast/version.rb
|
|
@@ -88,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
90
|
- !ruby/object:Gem::Version
|
|
89
91
|
version: '0'
|
|
90
92
|
requirements: []
|
|
91
|
-
rubygems_version: 4.0.
|
|
93
|
+
rubygems_version: 4.0.16
|
|
92
94
|
specification_version: 4
|
|
93
95
|
summary: Ruby client for the Broadcast email platform
|
|
94
96
|
test_files: []
|