kit-rb 0.0.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/CHANGELOG.md +41 -0
- data/README.md +56 -5
- data/Rakefile +17 -0
- data/lib/kit/client.rb +56 -0
- data/lib/kit/configuration.rb +7 -2
- data/lib/kit/connection.rb +37 -11
- data/lib/kit/errors.rb +15 -0
- data/lib/kit/oauth/client.rb +132 -0
- data/lib/kit/oauth/pkce.rb +42 -0
- data/lib/kit/oauth/token.rb +38 -0
- data/lib/kit/objects/broadcast.rb +26 -0
- data/lib/kit/objects/broadcast_click.rb +17 -0
- data/lib/kit/objects/broadcast_stats.rb +30 -0
- data/lib/kit/objects/creator_profile.rb +13 -0
- data/lib/kit/objects/custom_field.rb +14 -0
- data/lib/kit/objects/email_stats.rb +25 -0
- data/lib/kit/objects/email_template.rb +14 -0
- data/lib/kit/objects/form.rb +22 -0
- data/lib/kit/objects/growth_stats.rb +20 -0
- data/lib/kit/objects/post.rb +24 -0
- data/lib/kit/objects/purchase.rb +24 -0
- data/lib/kit/objects/segment.rb +12 -0
- data/lib/kit/objects/sequence.rb +26 -0
- data/lib/kit/objects/sequence_email.rb +23 -0
- data/lib/kit/objects/snippet.rb +21 -0
- data/lib/kit/objects/subscriber.rb +25 -0
- data/lib/kit/objects/subscriber_stats.rb +26 -0
- data/lib/kit/objects/tag.rb +17 -0
- data/lib/kit/objects/webhook.rb +15 -0
- data/lib/kit/objects/webhook_endpoint.rb +22 -0
- data/lib/kit/pagination.rb +66 -0
- data/lib/kit/resources/account.rb +27 -1
- data/lib/kit/resources/base.rb +38 -6
- data/lib/kit/resources/broadcasts.rb +56 -0
- data/lib/kit/resources/bulk.rb +63 -0
- data/lib/kit/resources/custom_fields.rb +29 -0
- data/lib/kit/resources/email_templates.rb +13 -0
- data/lib/kit/resources/forms.rb +31 -0
- data/lib/kit/resources/posts.rb +18 -0
- data/lib/kit/resources/purchases.rb +26 -0
- data/lib/kit/resources/segments.rb +13 -0
- data/lib/kit/resources/sequences.rb +78 -0
- data/lib/kit/resources/snippets.rb +29 -0
- data/lib/kit/resources/subscribers.rb +67 -0
- data/lib/kit/resources/tags.rb +52 -0
- data/lib/kit/resources/webhook_endpoints.rb +46 -0
- data/lib/kit/resources/webhooks.rb +24 -0
- data/lib/kit/version.rb +1 -1
- data/lib/kit-rb.rb +38 -0
- data/sig/kit-rb.rbs +429 -1
- metadata +43 -5
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A custom field as returned by /v4/custom_fields. `label` is what a creator
|
|
6
|
+
# sets; `key` is the derived attribute used on subscribers (e.g. a `label`
|
|
7
|
+
# of "Last name" yields a `key` of "last_name").
|
|
8
|
+
CustomField = Data.define(:id, :name, :key, :label) do
|
|
9
|
+
def self.from(hash)
|
|
10
|
+
new(id: hash["id"], name: hash["name"], key: hash["key"], label: hash["label"])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# Account-wide email engagement stats (/v4/account/email_stats), covering the
|
|
6
|
+
# window [starting, ending].
|
|
7
|
+
EmailStats = Data.define(
|
|
8
|
+
:sent, :clicked, :opened, :email_stats_mode,
|
|
9
|
+
:open_tracking_enabled, :click_tracking_enabled,
|
|
10
|
+
:starting, :ending, :open_rate, :click_rate, :unsubscribe_rate, :bounce_rate
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
sent: hash["sent"], clicked: hash["clicked"], opened: hash["opened"],
|
|
15
|
+
email_stats_mode: hash["email_stats_mode"],
|
|
16
|
+
open_tracking_enabled: hash["open_tracking_enabled"],
|
|
17
|
+
click_tracking_enabled: hash["click_tracking_enabled"],
|
|
18
|
+
starting: hash["starting"], ending: hash["ending"],
|
|
19
|
+
open_rate: hash["open_rate"], click_rate: hash["click_rate"],
|
|
20
|
+
unsubscribe_rate: hash["unsubscribe_rate"], bounce_rate: hash["bounce_rate"]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# An email template as returned by /v4/email_templates. `is_default` marks the
|
|
6
|
+
# account's default; `category` groups templates in the UI.
|
|
7
|
+
EmailTemplate = Data.define(:id, :name, :is_default, :category) do
|
|
8
|
+
def self.from(hash)
|
|
9
|
+
new(id: hash["id"], name: hash["name"],
|
|
10
|
+
is_default: hash["is_default"], category: hash["category"])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A form as returned by /v4/forms. `type` is e.g. "embed"/"hosted"; `uid` is
|
|
6
|
+
# the public identifier used in embed URLs. `subscriber_count` is present
|
|
7
|
+
# only when the request asks to include it.
|
|
8
|
+
Form = Data.define(
|
|
9
|
+
:id, :name, :created_at, :type, :format,
|
|
10
|
+
:embed_js, :embed_url, :archived, :uid, :subscriber_count
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], name: hash["name"], created_at: hash["created_at"],
|
|
15
|
+
type: hash["type"], format: hash["format"], embed_js: hash["embed_js"],
|
|
16
|
+
embed_url: hash["embed_url"], archived: hash["archived"], uid: hash["uid"],
|
|
17
|
+
subscriber_count: hash["subscriber_count"]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# Account subscriber-growth stats (/v4/account/growth_stats) over the window
|
|
6
|
+
# [starting, ending].
|
|
7
|
+
GrowthStats = Data.define(
|
|
8
|
+
:cancellations, :net_new_subscribers, :new_subscribers, :subscribers,
|
|
9
|
+
:starting, :ending
|
|
10
|
+
) do
|
|
11
|
+
def self.from(hash)
|
|
12
|
+
new(
|
|
13
|
+
cancellations: hash["cancellations"], net_new_subscribers: hash["net_new_subscribers"],
|
|
14
|
+
new_subscribers: hash["new_subscribers"], subscribers: hash["subscribers"],
|
|
15
|
+
starting: hash["starting"], ending: hash["ending"]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A post (a broadcast published to the web) as returned by /v4/posts.
|
|
6
|
+
# `publication_id` ties it back to the broadcast it was published from.
|
|
7
|
+
Post = Data.define(
|
|
8
|
+
:id, :publication_id, :created_at, :title, :slug, :description,
|
|
9
|
+
:meta_description, :status, :published_at, :sent_at,
|
|
10
|
+
:thumbnail_alt, :thumbnail_url, :is_paid, :public_url
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], publication_id: hash["publication_id"], created_at: hash["created_at"],
|
|
15
|
+
title: hash["title"], slug: hash["slug"], description: hash["description"],
|
|
16
|
+
meta_description: hash["meta_description"], status: hash["status"],
|
|
17
|
+
published_at: hash["published_at"], sent_at: hash["sent_at"],
|
|
18
|
+
thumbnail_alt: hash["thumbnail_alt"], thumbnail_url: hash["thumbnail_url"],
|
|
19
|
+
is_paid: hash["is_paid"], public_url: hash["public_url"]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A purchase as returned by /v4/purchases. Monetary fields are numbers in the
|
|
6
|
+
# purchase `currency`; `products` is the raw line-item array (each with
|
|
7
|
+
# name/pid/lid/sku/unit_price/quantity).
|
|
8
|
+
Purchase = Data.define(
|
|
9
|
+
:id, :transaction_id, :subscriber_id, :status, :email_address, :currency,
|
|
10
|
+
:transaction_time, :subtotal, :discount, :tax, :total, :products, :source
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], transaction_id: hash["transaction_id"],
|
|
15
|
+
subscriber_id: hash["subscriber_id"], status: hash["status"],
|
|
16
|
+
email_address: hash["email_address"], currency: hash["currency"],
|
|
17
|
+
transaction_time: hash["transaction_time"], subtotal: hash["subtotal"],
|
|
18
|
+
discount: hash["discount"], tax: hash["tax"], total: hash["total"],
|
|
19
|
+
products: hash["products"], source: hash["source"]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A segment (a saved subscriber filter) as returned by /v4/segments.
|
|
6
|
+
Segment = Data.define(:id, :name, :created_at) do
|
|
7
|
+
def self.from(hash)
|
|
8
|
+
new(id: hash["id"], name: hash["name"], created_at: hash["created_at"])
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A sequence (formerly "course") as returned by /v4/sequences. Only id, name,
|
|
6
|
+
# hold, repeat, and created_at are always present; the scheduling fields and
|
|
7
|
+
# counts appear on full reads, and `stats` only when the request includes it.
|
|
8
|
+
Sequence = Data.define(
|
|
9
|
+
:id, :name, :hold, :repeat, :created_at, :updated_at,
|
|
10
|
+
:email_address, :email_template_id, :send_days, :send_hour, :time_zone,
|
|
11
|
+
:active, :exclude_subscriber_sources, :email_count, :subscriber_count, :stats
|
|
12
|
+
) do
|
|
13
|
+
def self.from(hash)
|
|
14
|
+
new(
|
|
15
|
+
id: hash["id"], name: hash["name"], hold: hash["hold"], repeat: hash["repeat"],
|
|
16
|
+
created_at: hash["created_at"], updated_at: hash["updated_at"],
|
|
17
|
+
email_address: hash["email_address"], email_template_id: hash["email_template_id"],
|
|
18
|
+
send_days: hash["send_days"], send_hour: hash["send_hour"], time_zone: hash["time_zone"],
|
|
19
|
+
active: hash["active"], exclude_subscriber_sources: hash["exclude_subscriber_sources"],
|
|
20
|
+
email_count: hash["email_count"], subscriber_count: hash["subscriber_count"],
|
|
21
|
+
stats: hash["stats"]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# An email within a sequence, as returned by
|
|
6
|
+
# /v4/sequences/:sequence_id/emails. `position` is its order in the sequence;
|
|
7
|
+
# `delay_value`/`delay_unit` set how long after the previous step it sends.
|
|
8
|
+
SequenceEmail = Data.define(
|
|
9
|
+
:id, :sequence_id, :subject, :preview_text, :email_address, :email_template_id,
|
|
10
|
+
:published, :position, :delay_value, :delay_unit, :send_days, :stats
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], sequence_id: hash["sequence_id"], subject: hash["subject"],
|
|
15
|
+
preview_text: hash["preview_text"], email_address: hash["email_address"],
|
|
16
|
+
email_template_id: hash["email_template_id"], published: hash["published"],
|
|
17
|
+
position: hash["position"], delay_value: hash["delay_value"],
|
|
18
|
+
delay_unit: hash["delay_unit"], send_days: hash["send_days"], stats: hash["stats"]
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A snippet (reusable content) as returned by /v4/snippets. `snippet_type` is
|
|
6
|
+
# "inline" or "document"; `content` holds an inline snippet's body and
|
|
7
|
+
# `document` a document snippet's; `key` is the reference used in emails.
|
|
8
|
+
Snippet = Data.define(
|
|
9
|
+
:id, :name, :snippet_type, :archived, :key,
|
|
10
|
+
:created_at, :updated_at, :content, :document
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], name: hash["name"], snippet_type: hash["snippet_type"],
|
|
15
|
+
archived: hash["archived"], key: hash["key"], created_at: hash["created_at"],
|
|
16
|
+
updated_at: hash["updated_at"], content: hash["content"], document: hash["document"]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A subscriber as returned by /v4/subscribers. `fields` holds custom-field
|
|
6
|
+
# values; `location` is present on some responses; both are plain Hashes.
|
|
7
|
+
Subscriber = Data.define(
|
|
8
|
+
:id, :first_name, :email_address, :state,
|
|
9
|
+
:created_at, :canceled_at, :location, :fields
|
|
10
|
+
) do
|
|
11
|
+
def self.from(hash)
|
|
12
|
+
new(
|
|
13
|
+
id: hash["id"],
|
|
14
|
+
first_name: hash["first_name"],
|
|
15
|
+
email_address: hash["email_address"],
|
|
16
|
+
state: hash["state"],
|
|
17
|
+
created_at: hash["created_at"],
|
|
18
|
+
canceled_at: hash["canceled_at"],
|
|
19
|
+
location: hash["location"],
|
|
20
|
+
fields: hash["fields"]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# Engagement stats for a subscriber (/v4/subscribers/:id/stats). The API
|
|
6
|
+
# nests the metrics under "stats" next to the id; this flattens them.
|
|
7
|
+
SubscriberStats = Data.define(
|
|
8
|
+
:id, :sent, :opened, :clicked, :bounced, :open_rate, :click_rate,
|
|
9
|
+
:last_sent, :last_opened, :last_clicked,
|
|
10
|
+
:sends_since_last_open, :sends_since_last_click
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
stats = hash["stats"] || {}
|
|
14
|
+
new(
|
|
15
|
+
id: hash["id"], sent: stats["sent"], opened: stats["opened"],
|
|
16
|
+
clicked: stats["clicked"], bounced: stats["bounced"],
|
|
17
|
+
open_rate: stats["open_rate"], click_rate: stats["click_rate"],
|
|
18
|
+
last_sent: stats["last_sent"], last_opened: stats["last_opened"],
|
|
19
|
+
last_clicked: stats["last_clicked"],
|
|
20
|
+
sends_since_last_open: stats["sends_since_last_open"],
|
|
21
|
+
sends_since_last_click: stats["sends_since_last_click"]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A tag as returned by /v4/tags. Fields are all non-nil.
|
|
6
|
+
Tag = Data.define(:id, :name, :created_at, :subscriber_count) do
|
|
7
|
+
def self.from(hash)
|
|
8
|
+
new(
|
|
9
|
+
id: hash["id"],
|
|
10
|
+
name: hash["name"],
|
|
11
|
+
created_at: hash["created_at"],
|
|
12
|
+
subscriber_count: hash["subscriber_count"]
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A webhook (an automation rule that POSTs to target_url) as returned by
|
|
6
|
+
# /v4/webhooks. `event` is the raw trigger hash ({ "name" => ..., plus the
|
|
7
|
+
# optional tag_id/form_id/etc. that scope it }).
|
|
8
|
+
Webhook = Data.define(:id, :account_id, :event, :target_url) do
|
|
9
|
+
def self.from(hash)
|
|
10
|
+
new(id: hash["id"], account_id: hash["account_id"],
|
|
11
|
+
event: hash["event"], target_url: hash["target_url"])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Objects
|
|
5
|
+
# A webhook endpoint as returned by /v4/webhook_endpoints — the newer signed-
|
|
6
|
+
# delivery model, with a secret that can be rotated. `events` is the list of
|
|
7
|
+
# subscribed event names; `previous_secret_expires_at` is set after a rotate.
|
|
8
|
+
WebhookEndpoint = Data.define(
|
|
9
|
+
:id, :name, :url, :events, :status, :source, :description,
|
|
10
|
+
:created_by_app, :created_at, :previous_secret_expires_at
|
|
11
|
+
) do
|
|
12
|
+
def self.from(hash)
|
|
13
|
+
new(
|
|
14
|
+
id: hash["id"], name: hash["name"], url: hash["url"], events: hash["events"],
|
|
15
|
+
status: hash["status"], source: hash["source"], description: hash["description"],
|
|
16
|
+
created_by_app: hash["created_by_app"], created_at: hash["created_at"],
|
|
17
|
+
previous_secret_expires_at: hash["previous_secret_expires_at"]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
# The `pagination` object Kit returns alongside every list. Kit uses cursor
|
|
5
|
+
# pagination: to walk forward, pass `after: end_cursor`; backward, `before:
|
|
6
|
+
# start_cursor`.
|
|
7
|
+
Pagination = Data.define(
|
|
8
|
+
:has_previous_page, :has_next_page, :start_cursor, :end_cursor, :per_page
|
|
9
|
+
) do
|
|
10
|
+
def self.from(hash)
|
|
11
|
+
new(
|
|
12
|
+
has_previous_page: hash["has_previous_page"],
|
|
13
|
+
has_next_page: hash["has_next_page"],
|
|
14
|
+
start_cursor: hash["start_cursor"],
|
|
15
|
+
end_cursor: hash["end_cursor"],
|
|
16
|
+
per_page: hash["per_page"]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# One page of results plus the cursor to fetch the next. `Collection` is
|
|
22
|
+
# Enumerable over the *current* page; `auto_paging_each` lazily walks every
|
|
23
|
+
# remaining page by following `end_cursor`, so callers never touch cursors:
|
|
24
|
+
#
|
|
25
|
+
# client.subscribers.list.auto_paging_each { |s| puts s.email_address }
|
|
26
|
+
# client.subscribers.list.auto_paging_each.lazy.first(500)
|
|
27
|
+
#
|
|
28
|
+
# The block given to `.new` fetches the next Collection from an `after` cursor.
|
|
29
|
+
class Collection
|
|
30
|
+
include Enumerable
|
|
31
|
+
|
|
32
|
+
attr_reader :data, :pagination
|
|
33
|
+
|
|
34
|
+
def initialize(data:, pagination:, &fetch_after)
|
|
35
|
+
@data = data
|
|
36
|
+
@pagination = pagination
|
|
37
|
+
@fetch_after = fetch_after
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Iterates the current page only.
|
|
41
|
+
def each(&)
|
|
42
|
+
@data.each(&)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The next Collection, or nil when there is no next page.
|
|
46
|
+
def next_page
|
|
47
|
+
return nil unless @pagination.has_next_page
|
|
48
|
+
|
|
49
|
+
@fetch_after.call(@pagination.end_cursor)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Iterates every item across every remaining page, fetching lazily. Returns
|
|
53
|
+
# an Enumerator when no block is given (so `.lazy` composes).
|
|
54
|
+
def auto_paging_each(&block)
|
|
55
|
+
return enum_for(:auto_paging_each) unless block
|
|
56
|
+
|
|
57
|
+
page = self
|
|
58
|
+
loop do
|
|
59
|
+
page.data.each(&block)
|
|
60
|
+
break unless page.pagination.has_next_page
|
|
61
|
+
|
|
62
|
+
page = page.next_page
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -9,7 +9,33 @@ module Kit
|
|
|
9
9
|
#
|
|
10
10
|
# @return [Kit::Objects::AccountInfo]
|
|
11
11
|
def get
|
|
12
|
-
Objects::AccountInfo.from(
|
|
12
|
+
Objects::AccountInfo.from(http_get("/v4/account"))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# GET /v4/account/colors — the account's brand color palette (hex strings).
|
|
16
|
+
def colors
|
|
17
|
+
http_get("/v4/account/colors").fetch("colors")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# PUT /v4/account/colors — replace the palette; returns the saved colors.
|
|
21
|
+
def update_colors(colors)
|
|
22
|
+
http_put("/v4/account/colors", body: { colors: colors }).fetch("colors")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# GET /v4/account/creator_profile
|
|
26
|
+
def creator_profile
|
|
27
|
+
one(:get, "/v4/account/creator_profile", "profile", Objects::CreatorProfile)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# GET /v4/account/email_stats — account-wide email engagement stats.
|
|
31
|
+
def email_stats
|
|
32
|
+
one(:get, "/v4/account/email_stats", "stats", Objects::EmailStats)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# GET /v4/account/growth_stats — subscriber-growth stats; accepts
|
|
36
|
+
# starting/ending to bound the window.
|
|
37
|
+
def growth_stats(**params)
|
|
38
|
+
one(:get, "/v4/account/growth_stats", "stats", Objects::GrowthStats, params: params)
|
|
13
39
|
end
|
|
14
40
|
end
|
|
15
41
|
end
|
data/lib/kit/resources/base.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Kit
|
|
4
4
|
module Resources
|
|
5
5
|
# Shared base for every resource group. Holds the connection and exposes
|
|
6
|
-
#
|
|
6
|
+
# verb helpers under `http_*` names so a resource can define public methods
|
|
7
|
+
# like `get(id)` or `list` without colliding with the transport helpers.
|
|
7
8
|
class Base
|
|
8
9
|
def initialize(connection)
|
|
9
10
|
@connection = connection
|
|
@@ -11,20 +12,51 @@ module Kit
|
|
|
11
12
|
|
|
12
13
|
private
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
# Non-enveloped read (whole body) and list reads go through http_get;
|
|
16
|
+
# deletes return no object and go through http_delete. Enveloped
|
|
17
|
+
# single-object and list responses are built by `one` and `collection`.
|
|
18
|
+
def http_get(path, params: {})
|
|
15
19
|
@connection.request(:get, path, params: params)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
# Raw POST/PUT for payloads that are not a single wrapped object — the
|
|
23
|
+
# account colors array, and the bulk endpoints' composite
|
|
24
|
+
# { <resource>, failures } result. Enveloped objects go through `one`.
|
|
25
|
+
def http_post(path, body: nil, params: {})
|
|
19
26
|
@connection.request(:post, path, params: params, body: body)
|
|
20
27
|
end
|
|
21
28
|
|
|
22
|
-
def
|
|
29
|
+
def http_put(path, body: nil, params: {})
|
|
23
30
|
@connection.request(:put, path, params: params, body: body)
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
# Bulk deletes carry a body (the items to remove), so body is accepted.
|
|
34
|
+
def http_delete(path, body: nil, params: {})
|
|
35
|
+
@connection.request(:delete, path, params: params, body: body)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sends one request that returns a single wrapped object and builds it.
|
|
39
|
+
# `key` is the envelope key (e.g. "subscriber"), `klass` the object built
|
|
40
|
+
# via `klass.from`. Centralised so a resource never hand-writes the read/
|
|
41
|
+
# build/return plumbing — it declares only verb, path, key, class, body.
|
|
42
|
+
def one(verb, path, key, klass, body: nil, params: {})
|
|
43
|
+
response = @connection.request(verb, path, params: params, body: body)
|
|
44
|
+
klass.from(response.fetch(key))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Fetches a cursor-paginated list and wraps it in a Collection whose next
|
|
48
|
+
# page follows end_cursor. `key` is the array key in the envelope (e.g.
|
|
49
|
+
# "subscribers"), `klass` the object built from each element. Centralised
|
|
50
|
+
# here so every list resource paginates identically and correctly.
|
|
51
|
+
#
|
|
52
|
+
# `verb`/`body` default to a GET with no body; the POST-based filter
|
|
53
|
+
# endpoints pass verb: :post with a filter body, still paging by cursor.
|
|
54
|
+
def collection(path, key, klass, params, verb: :get, body: nil)
|
|
55
|
+
response = @connection.request(verb, path, params: params, body: body)
|
|
56
|
+
data = response.fetch(key).map { |element| klass.from(element) }
|
|
57
|
+
Collection.new(data: data, pagination: Pagination.from(response.fetch("pagination"))) do |after|
|
|
58
|
+
collection(path, key, klass, params.merge(after: after), verb: verb, body: body)
|
|
59
|
+
end
|
|
28
60
|
end
|
|
29
61
|
end
|
|
30
62
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Resources
|
|
5
|
+
# The /v4/broadcasts endpoints — one-off emails, plus their stats and link
|
|
6
|
+
# click reports.
|
|
7
|
+
class Broadcasts < Base
|
|
8
|
+
# GET /v4/broadcasts
|
|
9
|
+
def list(**params)
|
|
10
|
+
collection("/v4/broadcasts", "broadcasts", Objects::Broadcast, params)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# GET /v4/broadcasts/:id
|
|
14
|
+
def get(id)
|
|
15
|
+
one(:get, "/v4/broadcasts/#{id}", "broadcast", Objects::Broadcast)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# POST /v4/broadcasts
|
|
19
|
+
def create(**attributes)
|
|
20
|
+
one(:post, "/v4/broadcasts", "broadcast", Objects::Broadcast, body: attributes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# PUT /v4/broadcasts/:id
|
|
24
|
+
def update(id, **attributes)
|
|
25
|
+
one(:put, "/v4/broadcasts/#{id}", "broadcast", Objects::Broadcast, body: attributes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# DELETE /v4/broadcasts/:id
|
|
29
|
+
def delete(id)
|
|
30
|
+
http_delete("/v4/broadcasts/#{id}")
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET /v4/broadcasts/stats — a cursor-paginated list of per-broadcast stats.
|
|
35
|
+
def stats_list(**params)
|
|
36
|
+
collection("/v4/broadcasts/stats", "broadcasts", Objects::BroadcastStats, params)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# GET /v4/broadcasts/:id/stats — one broadcast's performance stats.
|
|
40
|
+
def stats(id)
|
|
41
|
+
one(:get, "/v4/broadcasts/#{id}/stats", "broadcast", Objects::BroadcastStats)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# GET /v4/broadcasts/:id/clicks — a cursor-paginated Collection of the
|
|
45
|
+
# broadcast's clicked links. The API nests the array under "broadcast", so
|
|
46
|
+
# this is built directly rather than through Base#collection.
|
|
47
|
+
def clicks(id, **params)
|
|
48
|
+
body = http_get("/v4/broadcasts/#{id}/clicks", params: params)
|
|
49
|
+
rows = body.fetch("broadcast").fetch("clicks").map { |row| Objects::BroadcastClick.from(row) }
|
|
50
|
+
Collection.new(data: rows, pagination: Pagination.from(body.fetch("pagination"))) do |after|
|
|
51
|
+
clicks(id, **params, after: after)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Resources
|
|
5
|
+
# The /v4/bulk endpoints — batch operations that all require OAuth. Each call
|
|
6
|
+
# takes an array of items and an optional callback_url, and returns the raw
|
|
7
|
+
# result hash: the created/affected items under their resource key plus a
|
|
8
|
+
# "failures" array. Large batches are processed asynchronously and reported
|
|
9
|
+
# to callback_url; the response still carries whatever completed inline.
|
|
10
|
+
class Bulk < Base
|
|
11
|
+
# POST /v4/bulk/subscribers
|
|
12
|
+
def create_subscribers(subscribers, callback_url: nil)
|
|
13
|
+
post("/v4/bulk/subscribers", subscribers: subscribers, callback_url: callback_url)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /v4/bulk/custom_fields
|
|
17
|
+
def create_custom_fields(custom_fields, callback_url: nil)
|
|
18
|
+
post("/v4/bulk/custom_fields", custom_fields: custom_fields, callback_url: callback_url)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# POST /v4/bulk/custom_fields/subscribers
|
|
22
|
+
def update_custom_field_values(custom_field_values, callback_url: nil)
|
|
23
|
+
post("/v4/bulk/custom_fields/subscribers",
|
|
24
|
+
custom_field_values: custom_field_values, callback_url: callback_url)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# POST /v4/bulk/forms/subscribers
|
|
28
|
+
def add_subscribers_to_forms(additions, callback_url: nil)
|
|
29
|
+
post("/v4/bulk/forms/subscribers", additions: additions, callback_url: callback_url)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# POST /v4/bulk/tags
|
|
33
|
+
def create_tags(tags, callback_url: nil)
|
|
34
|
+
post("/v4/bulk/tags", tags: tags, callback_url: callback_url)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# DELETE /v4/bulk/tags
|
|
38
|
+
def delete_tags(tags, callback_url: nil)
|
|
39
|
+
delete("/v4/bulk/tags", tags: tags, callback_url: callback_url)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# POST /v4/bulk/tags/subscribers
|
|
43
|
+
def tag_subscribers(taggings, callback_url: nil)
|
|
44
|
+
post("/v4/bulk/tags/subscribers", taggings: taggings, callback_url: callback_url)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# DELETE /v4/bulk/tags/subscribers
|
|
48
|
+
def remove_tag_subscribers(taggings, callback_url: nil)
|
|
49
|
+
delete("/v4/bulk/tags/subscribers", taggings: taggings, callback_url: callback_url)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def post(path, **body)
|
|
55
|
+
http_post(path, body: body.compact)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def delete(path, **body)
|
|
59
|
+
http_delete(path, body: body.compact)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|