broadcast-ruby 0.4.1 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a0fbeb95adead9eca90faa48afb7e5cd463c7caa23d9e0a217abd2fd4f27350
4
- data.tar.gz: abc65de39dfaf74dc17a5f5021f4c2b7a9b24821b3bd9bd87582811153d2901e
3
+ metadata.gz: 98cb127e971456521ce14ecd8b2f3f8a90b5ce295623c6ad3e8b60495f3b231f
4
+ data.tar.gz: ac3dab036f2d6bfdf07108e82e14c57821010ecbbc0434355b85319f71c63868
5
5
  SHA512:
6
- metadata.gz: e0b60c030234703fce2c09b864519a227ae7ebf34a60f32e5b5ae92b7650199ef548949d85fa2e190b788527168004c066b29adccf032716b3a968127b35d69b
7
- data.tar.gz: 9be0806cdda4c12ab34b5cb03fc2a4e8daee603e90318a469b64be5015a532ec771b6c28cf58a2b3e41cd612c02b2af036345375689107aeb79da9feb699dd6a
6
+ metadata.gz: 901e3de9e03327d7750e80b55ef9db13dd615eb258ddef2ec36d0c6063d7b3307689798b626a05fcc6c38e611a0954977f2341de0584f89be103d2e16bfae2e2
7
+ data.tar.gz: b657ab5a20df302fc9e0c5b217937deb50ddec49dc4f87200f95292433e4464eb0c89ae7104b741f787909d2a9b1b5d5f9117e1a2730c3d2c0e3c29cf502e849
data/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.0] - 2026-10-03
8
+
9
+ ### Added
10
+
11
+ - `Migration#unsubscribed_emails` for `GET /api/migration/v1/unsubscribed_emails`,
12
+ the channel's own suppression list (rows `id`, `email`,
13
+ `broadcast_channel_id`, `created_at`, `updated_at`). `each_record(:unsubscribed_emails)`
14
+ pages it. `suppressions` returns only the global suppression list, so an
15
+ export needs both.
16
+
17
+ ## [0.5.0] - 2026-09-25
18
+
19
+ ### Added
20
+ - `subscribers.purged` and `subscribers.purge_failed` webhook event types, in
21
+ `Broadcast::Webhook::SUBSCRIBER_EVENTS` and `EVENT_TYPES` (now 34). A purge
22
+ of the whole list sends one of these instead of a `subscriber.deleted` per
23
+ subscriber.
24
+ - `Users` resource (`client.users`) for the admin-only Users API: `list`,
25
+ `get_user`, `create`, `update`, `deactivate`, `activate`, `delete`, plus
26
+ `channel_permissions`/`set_channel_permissions`/`remove_channel_permissions`/
27
+ `bulk_channel_permissions` and `system_permissions`/`update_system_permissions`.
28
+ Requires an admin API token — a channel token raises
29
+ `Broadcast::AuthorizationError`. Sudo users are read-only through this API
30
+ and sudo access can never be granted. `set_channel_permissions` and
31
+ `bulk_channel_permissions` accept exactly one of `permissions:`, `role:`,
32
+ or `preset_id:`, raising `ArgumentError` otherwise; the underlying `PUT` is
33
+ a full replace of the channel's permission record. `Connection` gained PUT
34
+ support to carry this call.
35
+ - `Channel#design` (`client.channel.design`) for `GET /api/v1/channel/design`,
36
+ which returns the token's channel brand kit, fully resolved: colours, font
37
+ key and stack, layout, logo URL, website and social links. Read-only; needs
38
+ the `templates_read` permission.
39
+
7
40
  ## [0.4.1] - 2026-08-16
8
41
 
9
42
  ### Fixed — sequence enrolment never worked
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- broadcast-ruby (0.4.1)
4
+ broadcast-ruby (0.6.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.4.1)
180
+ broadcast-ruby (0.6.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 32
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:
@@ -1302,10 +1380,14 @@ Available collections:
1302
1380
  ```
1303
1381
  channels subscribers templates segments sequences email_servers
1304
1382
  opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
1305
- suppressions tags users link_redirects link_clicks
1306
- subscriber_histories file_assets
1383
+ suppressions unsubscribed_emails tags users link_redirects
1384
+ link_clicks subscriber_histories file_assets
1307
1385
  ```
1308
1386
 
1387
+ `suppressions` is the global suppression list (GlobalSuppression rows, global
1388
+ or scoped to the channel). `unsubscribed_emails` is the channel's own
1389
+ suppression list. Export both to keep every suppressed address.
1390
+
1309
1391
  File assets are downloaded separately and return raw bytes:
1310
1392
 
1311
1393
  ```ruby
@@ -1322,8 +1404,8 @@ All API errors inherit from `Broadcast::Error`. Put specific errors before gener
1322
1404
  ```ruby
1323
1405
  begin
1324
1406
  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 -- token lacks the required permission, or admin-only endpoint
1407
+ rescue Broadcast::AuthenticationError # 401 -- invalid or expired API token, or it lacks the required permission
1408
+ rescue Broadcast::AuthorizationError # 403 -- admin-only endpoint, or the user is sudo (read-only)
1327
1409
  rescue Broadcast::NotFoundError # 404 -- resource does not exist
1328
1410
  rescue Broadcast::ConflictError # 409 -- Idempotency-Key request still in flight
1329
1411
  rescue Broadcast::ValidationError # 422 -- missing or invalid parameters
@@ -1357,9 +1439,10 @@ Each token can be scoped to specific resources. The ActionMailer delivery method
1357
1439
  | Sequences | `sequences_read` -- list, get, list steps | `sequences_write` -- create, update, delete, manage steps, enroll subscribers |
1358
1440
  | Broadcasts | `broadcasts_read` -- list, get, statistics | `broadcasts_write` -- create, update, delete, send, schedule |
1359
1441
  | Segments | `segments_read` -- list, get | `segments_write` -- create, update, delete |
1360
- | Templates | `templates_read` -- list, get | `templates_write` -- create, update, delete |
1442
+ | Templates | `templates_read` -- list, get, channel brand kit (`channel.design`) | `templates_write` -- create, update, delete |
1361
1443
  | Opt-In Forms | `opt_in_forms_read` -- list, get, analytics | `opt_in_forms_write` -- create, update, delete, create_variant, duplicate |
1362
1444
  | Email Servers | `email_servers_read` -- list, get | `email_servers_write` -- create, update, delete, test_connection, copy_to_channel (admin) |
1445
+ | Users (admin token only) | `users_read` -- list, get, channel_permissions, system_permissions | `users_write` -- create, update, deactivate, activate, delete, permission writes |
1363
1446
  | Webhook Endpoints | `webhook_endpoints_read` -- list, get, deliveries | `webhook_endpoints_write` -- create, update, delete, test |
1364
1447
  | Autopilot | `autopilot_read` -- list, get, runs | `autopilot_write` -- create, update, delete, activate, pause, deactivate, trigger_run |
1365
1448
  | Suppressions | `suppressions_read` -- list, check | `suppressions_write` -- add, remove, bulk add/remove |
data/SDK-COVERAGE.md CHANGED
@@ -53,7 +53,7 @@ Two details worth knowing when reading the map below:
53
53
  - **PATCH is canonical.** Rails' `resources` routes `update` to both PATCH and
54
54
  PUT. They are one operation; the generator drops the PUT alias. Counting both
55
55
  would inflate the denominator by 8 and make this gem look 8 endpoints short.
56
- - **Metaprogrammed endpoints are declared.** The 18 migration collections are
56
+ - **Metaprogrammed endpoints are declared.** The 19 migration collections are
57
57
  defined by `define_method` over `Migration::COLLECTIONS`, so no path literal
58
58
  exists to scan. They are listed in `.api-coverage.yml` and marked
59
59
  *(generated)* below. That file is produced from the gem's own constants — it
@@ -268,6 +268,7 @@ on the client and the gem attaches it.
268
268
  | GET | `/api/migration/v1/webhook_endpoints` | *(generated)* `Migration#webhook_endpoints` |
269
269
  | GET | `/api/migration/v1/tokens` | *(generated)* `Migration#tokens` |
270
270
  | GET | `/api/migration/v1/suppressions` | *(generated)* `Migration#suppressions` |
271
+ | GET | `/api/migration/v1/unsubscribed_emails` | *(generated)* `Migration#unsubscribed_emails` |
271
272
  | GET | `/api/migration/v1/tags` | *(generated)* `Migration#tags` |
272
273
  | GET | `/api/migration/v1/users` | *(generated)* `Migration#users` |
273
274
  | GET | `/api/migration/v1/link_redirects` | *(generated)* `Migration#link_redirects` |
@@ -330,7 +331,7 @@ Inbound webhooks — the app calling you.
330
331
  | HMAC-SHA256 verification (`v1,<base64>`) | ✅ `Webhook.verify` |
331
332
  | 5-minute timestamp tolerance | ✅ Replay protection |
332
333
  | Constant-time comparison | ✅ `OpenSSL.fixed_length_secure_compare` |
333
- | Event type constants | ✅ `Webhook::EVENT_TYPES` — 32 values |
334
+ | Event type constants | ✅ `Webhook::EVENT_TYPES` — 34 values |
334
335
 
335
336
  Categories: `EMAIL_EVENTS` (8), `SUBSCRIBER_EVENTS` (7), `BROADCAST_EVENTS` (8),
336
337
  `SEQUENCE_EVENTS` (7), `SYSTEM_EVENTS` (2).
@@ -119,6 +119,11 @@ module Broadcast
119
119
  @email_servers ||= Resources::EmailServers.new(self)
120
120
  end
121
121
 
122
+ # Installation users and their permissions. Requires an admin API token.
123
+ def users
124
+ @users ||= Resources::Users.new(self)
125
+ end
126
+
122
127
  def autopilots
123
128
  @autopilots ||= Resources::Autopilots.new(self)
124
129
  end
@@ -139,6 +144,11 @@ module Broadcast
139
144
  @global_suppressions ||= Resources::GlobalSuppressions.new(self)
140
145
  end
141
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
+
142
152
  # Read-only export endpoints under /api/migration/v1. Requires an admin
143
153
  # (system) API token.
144
154
  def migration
@@ -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
@@ -20,8 +20,8 @@ module Broadcast
20
20
  COLLECTIONS = %i[
21
21
  channels subscribers templates segments sequences email_servers
22
22
  opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
23
- suppressions tags users link_redirects link_clicks subscriber_histories
24
- file_assets
23
+ suppressions unsubscribed_emails tags users link_redirects link_clicks
24
+ subscriber_histories file_assets
25
25
  ].freeze
26
26
 
27
27
  COLLECTIONS.each do |collection|
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Broadcast
4
- VERSION = '0.4.1'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -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.1
4
+ version: 0.6.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.17
93
+ rubygems_version: 4.0.10
92
94
  specification_version: 4
93
95
  summary: Ruby client for the Broadcast email platform
94
96
  test_files: []