broadcast-ruby 0.10.0 → 0.11.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 +9 -0
- data/Gemfile.lock +2 -2
- data/README.md +0 -69
- data/SDK-COVERAGE.md +5 -43
- data/lib/broadcast/client.rb +0 -6
- data/lib/broadcast/version.rb +1 -1
- data/lib/broadcast.rb +0 -1
- metadata +1 -2
- data/lib/broadcast/resources/migration.rb +0 -75
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 046bdebddc0d68f63776039530a40783734df10ca736d3f8212f598f72927c12
|
|
4
|
+
data.tar.gz: 5f1e53c5af9d8654410849eaf485e3f45657e02fb30cb575254585e49eda88e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 718ae1afd1950d245ae33084763e96fcc9c186ca56ebe6e4f44a8109b944bf8674d87ca9357ac9782c6ebac6ee4f8e46a6a92a86053c1dc73099c13fa5a1fb8d
|
|
7
|
+
data.tar.gz: d61a1d23973470942c06de04d732b01d0df32e3ab098f88ac7b82d68b68a142535de1ab93085f53cf0b18a4981f1c52b0d77c91c3217db2a566d774d9e0be10b
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.11.0] - 2026-11-07
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
- **Breaking:** `client.migration` (`Broadcast::Resources::Migration`): `manifest`,
|
|
12
|
+
the 19 export collections, `each_record` and `download_file_asset`. Broadcast 3
|
|
13
|
+
removes the `/api/migration/v1` export API. A server before 3.0 still serves
|
|
14
|
+
it; stay on 0.10.x to keep using it.
|
|
15
|
+
|
|
7
16
|
## [0.10.0] - 2026-10-17
|
|
8
17
|
|
|
9
18
|
### Added
|
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.11.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.11.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
|
@@ -1461,75 +1461,6 @@ raise 'channel not ready to send' unless client.status.dig('readiness', 'broadca
|
|
|
1461
1461
|
|
|
1462
1462
|
---
|
|
1463
1463
|
|
|
1464
|
-
## Export & Migration
|
|
1465
|
-
|
|
1466
|
-
Read-only endpoints under `/api/migration/v1` for backups and moving a channel
|
|
1467
|
-
between instances. Two constraints differ from the rest of the API:
|
|
1468
|
-
|
|
1469
|
-
- **Admin tokens only.** Channel-scoped tokens are rejected.
|
|
1470
|
-
- **`broadcast_channel_id` is required on every call.** Set it once on the
|
|
1471
|
-
client and the gem attaches it for you.
|
|
1472
|
-
|
|
1473
|
-
```ruby
|
|
1474
|
-
client = Broadcast::Client.new(
|
|
1475
|
-
api_token: ENV['BROADCAST_ADMIN_TOKEN'],
|
|
1476
|
-
host: 'https://mail.example.com',
|
|
1477
|
-
broadcast_channel_id: 1
|
|
1478
|
-
)
|
|
1479
|
-
|
|
1480
|
-
# Size the export first
|
|
1481
|
-
manifest = client.migration.manifest
|
|
1482
|
-
manifest['counts'] # => { 'subscribers' => 5000, 'templates' => 12, ... }
|
|
1483
|
-
manifest['export_format_version']
|
|
1484
|
-
|
|
1485
|
-
# Time-bounded counts (broadcasts, receipts, histories) default to 90 days
|
|
1486
|
-
client.migration.manifest(days_history: 365)
|
|
1487
|
-
```
|
|
1488
|
-
|
|
1489
|
-
Each collection is a paginated list returning `data` and `pagination`:
|
|
1490
|
-
|
|
1491
|
-
```ruby
|
|
1492
|
-
page = client.migration.subscribers(limit: 250, offset: 0)
|
|
1493
|
-
page['data']
|
|
1494
|
-
page['pagination'] # => { 'total' => 5000, 'limit' => 250, 'offset' => 0, 'has_more' => true }
|
|
1495
|
-
```
|
|
1496
|
-
|
|
1497
|
-
`each_record` handles the paging for you, advancing by the page size the server
|
|
1498
|
-
actually granted rather than the one you asked for:
|
|
1499
|
-
|
|
1500
|
-
```ruby
|
|
1501
|
-
CSV.open('subscribers.csv', 'w') do |csv|
|
|
1502
|
-
client.migration.each_record(:subscribers) do |subscriber|
|
|
1503
|
-
csv << [subscriber['email'], subscriber['first_name'], subscriber['created_at']]
|
|
1504
|
-
end
|
|
1505
|
-
end
|
|
1506
|
-
|
|
1507
|
-
# Without a block you get an Enumerator
|
|
1508
|
-
client.migration.each_record(:tags).map { |tag| tag['name'] }
|
|
1509
|
-
```
|
|
1510
|
-
|
|
1511
|
-
Available collections:
|
|
1512
|
-
|
|
1513
|
-
```
|
|
1514
|
-
channels subscribers templates segments sequences email_servers
|
|
1515
|
-
opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
|
|
1516
|
-
suppressions unsubscribed_emails tags users link_redirects
|
|
1517
|
-
link_clicks subscriber_histories file_assets
|
|
1518
|
-
```
|
|
1519
|
-
|
|
1520
|
-
`suppressions` is the global suppression list (GlobalSuppression rows, global
|
|
1521
|
-
or scoped to the channel). `unsubscribed_emails` is the channel's own
|
|
1522
|
-
suppression list. Export both to keep every suppressed address.
|
|
1523
|
-
|
|
1524
|
-
File assets are downloaded separately and return raw bytes:
|
|
1525
|
-
|
|
1526
|
-
```ruby
|
|
1527
|
-
bytes = client.migration.download_file_asset(7)
|
|
1528
|
-
File.binwrite('logo.png', bytes)
|
|
1529
|
-
```
|
|
1530
|
-
|
|
1531
|
-
---
|
|
1532
|
-
|
|
1533
1464
|
## Errors
|
|
1534
1465
|
|
|
1535
1466
|
All API errors inherit from `Broadcast::Error`. Put specific errors before general ones:
|
data/SDK-COVERAGE.md
CHANGED
|
@@ -53,11 +53,6 @@ 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 19 migration collections are
|
|
57
|
-
defined by `define_method` over `Migration::COLLECTIONS`, so no path literal
|
|
58
|
-
exists to scan. They are listed in `.api-coverage.yml` and marked
|
|
59
|
-
*(generated)* below. That file is produced from the gem's own constants — it
|
|
60
|
-
is not a hand-written promise.
|
|
61
56
|
|
|
62
57
|
---
|
|
63
58
|
|
|
@@ -247,39 +242,6 @@ payload so a fetch-modify-save cannot destroy the stored credential.
|
|
|
247
242
|
naming the missing prerequisites. `trigger_run` returns 202 — generation is
|
|
248
243
|
asynchronous, so poll `runs`.
|
|
249
244
|
|
|
250
|
-
### Migration / export — 20/20
|
|
251
|
-
|
|
252
|
-
Admin tokens only. `broadcast_channel_id` is required on every call; set it once
|
|
253
|
-
on the client and the gem attaches it.
|
|
254
|
-
|
|
255
|
-
| Method | Path | Gem |
|
|
256
|
-
|---|---|---|
|
|
257
|
-
| GET | `/api/migration/v1/manifest` | `Migration#manifest` |
|
|
258
|
-
| GET | `/api/migration/v1/file_assets/{id}/download` | `Migration#download_file_asset` — raw bytes |
|
|
259
|
-
| GET | `/api/migration/v1/channels` | *(generated)* `Migration#channels` |
|
|
260
|
-
| GET | `/api/migration/v1/subscribers` | *(generated)* `Migration#subscribers` |
|
|
261
|
-
| GET | `/api/migration/v1/templates` | *(generated)* `Migration#templates` |
|
|
262
|
-
| GET | `/api/migration/v1/segments` | *(generated)* `Migration#segments` |
|
|
263
|
-
| GET | `/api/migration/v1/sequences` | *(generated)* `Migration#sequences` |
|
|
264
|
-
| GET | `/api/migration/v1/email_servers` | *(generated)* `Migration#email_servers` |
|
|
265
|
-
| GET | `/api/migration/v1/opt_in_forms` | *(generated)* `Migration#opt_in_forms` |
|
|
266
|
-
| GET | `/api/migration/v1/broadcasts` | *(generated)* `Migration#broadcasts` |
|
|
267
|
-
| GET | `/api/migration/v1/outbound_receipts` | *(generated)* `Migration#outbound_receipts` |
|
|
268
|
-
| GET | `/api/migration/v1/webhook_endpoints` | *(generated)* `Migration#webhook_endpoints` |
|
|
269
|
-
| GET | `/api/migration/v1/tokens` | *(generated)* `Migration#tokens` |
|
|
270
|
-
| GET | `/api/migration/v1/suppressions` | *(generated)* `Migration#suppressions` |
|
|
271
|
-
| GET | `/api/migration/v1/unsubscribed_emails` | *(generated)* `Migration#unsubscribed_emails` |
|
|
272
|
-
| GET | `/api/migration/v1/tags` | *(generated)* `Migration#tags` |
|
|
273
|
-
| GET | `/api/migration/v1/users` | *(generated)* `Migration#users` |
|
|
274
|
-
| GET | `/api/migration/v1/link_redirects` | *(generated)* `Migration#link_redirects` |
|
|
275
|
-
| GET | `/api/migration/v1/link_clicks` | *(generated)* `Migration#link_clicks` |
|
|
276
|
-
| GET | `/api/migration/v1/subscriber_histories` | *(generated)* `Migration#subscriber_histories` |
|
|
277
|
-
| GET | `/api/migration/v1/file_assets` | *(generated)* `Migration#file_assets` |
|
|
278
|
-
|
|
279
|
-
`Migration#each_record(:collection)` pages any of them automatically, advancing
|
|
280
|
-
by the page size the **server granted** rather than the one requested (the
|
|
281
|
-
server clamps `limit` to 250).
|
|
282
|
-
|
|
283
245
|
---
|
|
284
246
|
|
|
285
247
|
## Transport coverage
|
|
@@ -301,10 +263,10 @@ complete while quietly losing information.
|
|
|
301
263
|
| Retry on 5xx and timeout | ✅ | `retry_attempts`, `retry_delay` |
|
|
302
264
|
| Typed errors | ✅ | 401/403/404/409/422/429/5xx each distinct |
|
|
303
265
|
| Redirect handling | ✅ | Same-host GET follows; writes and cross-host refuse |
|
|
304
|
-
| Raw/text responses | ✅ | `client.skill
|
|
266
|
+
| Raw/text responses | ✅ | `client.skill` |
|
|
305
267
|
| Channel scoping | ✅ | `broadcast_channel_id:`, `with_channel(id) { }` |
|
|
306
268
|
| Debug logging | ✅ | Credentials redacted |
|
|
307
|
-
| Pagination helper |
|
|
269
|
+
| Pagination helper | ❌ | v1 endpoints page manually |
|
|
308
270
|
| ActionMailer integration | ✅ | `:broadcast` delivery method + Railtie |
|
|
309
271
|
|
|
310
272
|
Error mapping in full:
|
|
@@ -355,9 +317,9 @@ this is an API gap, not a gem gap, and nothing here is actionable in this repo.
|
|
|
355
317
|
| **Channel management** | Web UI | Creating channels, domain/sender setup, onboarding |
|
|
356
318
|
| **User & permission management** | Web UI | Users, system permissions, per-channel permissions |
|
|
357
319
|
| **Settings** | Web UI | General, AI, DNS checks, ESP integrations, sender details |
|
|
358
|
-
| **Global suppressions** | Web UI
|
|
359
|
-
| **File assets** | Web UI
|
|
360
|
-
| **Link tracking config** | Web UI | Click data readable via `statistics_links`
|
|
320
|
+
| **Global suppressions** | Web UI | No v1 CRUD |
|
|
321
|
+
| **File assets** | Web UI | No v1 upload |
|
|
322
|
+
| **Link tracking config** | Web UI | Click data readable via `statistics_links` |
|
|
361
323
|
| **Admin / platform** | Web UI | Admin API tokens, SaaS platform admin |
|
|
362
324
|
| **Confirmation templates & pages** | Partly API | Configurable as template attributes; no dedicated endpoints |
|
|
363
325
|
|
data/lib/broadcast/client.rb
CHANGED
|
@@ -158,12 +158,6 @@ module Broadcast
|
|
|
158
158
|
@file_assets ||= Resources::FileAssets.new(self)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
-
# Read-only export endpoints under /api/migration/v1. Requires an admin
|
|
162
|
-
# (system) API token.
|
|
163
|
-
def migration
|
|
164
|
-
@migration ||= Resources::Migration.new(self)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
161
|
# @api private
|
|
168
162
|
# rubocop:disable Metrics/ParameterLists -- transport options passed straight through to Connection
|
|
169
163
|
def request(method, path, body_or_params = nil, headers: {}, raw: false, multipart: false)
|
data/lib/broadcast/version.rb
CHANGED
data/lib/broadcast.rb
CHANGED
|
@@ -27,7 +27,6 @@ require_relative 'broadcast/resources/suppressions'
|
|
|
27
27
|
require_relative 'broadcast/resources/global_suppressions'
|
|
28
28
|
require_relative 'broadcast/resources/channel'
|
|
29
29
|
require_relative 'broadcast/resources/file_assets'
|
|
30
|
-
require_relative 'broadcast/resources/migration'
|
|
31
30
|
|
|
32
31
|
# ActionMailer integration — only loaded when Rails is present
|
|
33
32
|
if defined?(Rails::Railtie)
|
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.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Chiu
|
|
@@ -56,7 +56,6 @@ files:
|
|
|
56
56
|
- lib/broadcast/resources/email_servers.rb
|
|
57
57
|
- lib/broadcast/resources/file_assets.rb
|
|
58
58
|
- lib/broadcast/resources/global_suppressions.rb
|
|
59
|
-
- lib/broadcast/resources/migration.rb
|
|
60
59
|
- lib/broadcast/resources/opt_in_forms.rb
|
|
61
60
|
- lib/broadcast/resources/segments.rb
|
|
62
61
|
- lib/broadcast/resources/sequences.rb
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Broadcast
|
|
4
|
-
module Resources
|
|
5
|
-
# Read-only export endpoints under /api/migration/v1 — the surface behind
|
|
6
|
-
# backups and instance-to-instance migration.
|
|
7
|
-
#
|
|
8
|
-
# Two things differ from the v1 API:
|
|
9
|
-
#
|
|
10
|
-
# 1. **Admin tokens only.** Channel-scoped tokens are rejected outright;
|
|
11
|
-
# the controller looks up AdminApiToken and nothing else.
|
|
12
|
-
# 2. **broadcast_channel_id is required on every call.** Set it once via
|
|
13
|
-
# `Client.new(broadcast_channel_id:)` or `client.with_channel(id)` and
|
|
14
|
-
# the gem attaches it automatically; otherwise pass it per call.
|
|
15
|
-
#
|
|
16
|
-
# Every list endpoint pages with `limit:` (1..250, default 250) and
|
|
17
|
-
# `offset:`, and returns `{"data" => [...], "pagination" => {...}}`.
|
|
18
|
-
class Migration < Base
|
|
19
|
-
# Endpoints that are a plain paginated list of the channel's records.
|
|
20
|
-
COLLECTIONS = %i[
|
|
21
|
-
channels subscribers templates segments sequences email_servers
|
|
22
|
-
opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
|
|
23
|
-
suppressions unsubscribed_emails tags users link_redirects link_clicks
|
|
24
|
-
subscriber_histories file_assets
|
|
25
|
-
].freeze
|
|
26
|
-
|
|
27
|
-
COLLECTIONS.each do |collection|
|
|
28
|
-
define_method(collection) do |**params|
|
|
29
|
-
get("/api/migration/v1/#{collection}", params)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Export summary: format version, channel identity, per-resource counts,
|
|
34
|
-
# and recent-history totals. Call this first to size an export.
|
|
35
|
-
#
|
|
36
|
-
# @param days_history [Integer] window for the time-bounded counts
|
|
37
|
-
# (broadcasts, receipts, histories). Server clamps to 1..365, default 90.
|
|
38
|
-
def manifest(**params)
|
|
39
|
-
get('/api/migration/v1/manifest', params)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Binary contents of a stored file asset. Returns a String of bytes, not
|
|
43
|
-
# JSON — write it straight to disk.
|
|
44
|
-
def download_file_asset(id, **params)
|
|
45
|
-
@client.request(:get, "/api/migration/v1/file_assets/#{id}/download", params, raw: true)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Pages through a collection, yielding each record.
|
|
49
|
-
#
|
|
50
|
-
# client.migration.each_record(:subscribers) { |sub| csv << sub }
|
|
51
|
-
#
|
|
52
|
-
# Stops when the server reports `has_more: false`, so it stays correct if
|
|
53
|
-
# the page size is clamped server-side.
|
|
54
|
-
def each_record(collection, limit: 250, **params, &block)
|
|
55
|
-
return enum_for(:each_record, collection, limit: limit, **params) unless block_given?
|
|
56
|
-
|
|
57
|
-
offset = 0
|
|
58
|
-
loop do
|
|
59
|
-
page = public_send(collection, limit: limit, offset: offset, **params)
|
|
60
|
-
records = Array(page['data'])
|
|
61
|
-
records.each(&block)
|
|
62
|
-
|
|
63
|
-
pagination = page['pagination'] || {}
|
|
64
|
-
break unless pagination['has_more']
|
|
65
|
-
|
|
66
|
-
# Advance by what the server actually returned — it clamps `limit`.
|
|
67
|
-
advanced = pagination['limit'] || records.size
|
|
68
|
-
break if advanced.to_i.zero?
|
|
69
|
-
|
|
70
|
-
offset += advanced.to_i
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|