genius_referrals 0.1.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 +7 -0
- data/LICENSE +28 -0
- data/README.md +134 -0
- data/lib/genius_referrals/client.rb +169 -0
- data/lib/genius_referrals/errors.rb +84 -0
- data/lib/genius_referrals/resource.rb +49 -0
- data/lib/genius_referrals/resources/accounts.rb +13 -0
- data/lib/genius_referrals/resources/advocates.rb +85 -0
- data/lib/genius_referrals/resources/authentications.rb +8 -0
- data/lib/genius_referrals/resources/bonuses.rb +63 -0
- data/lib/genius_referrals/resources/campaigns.rb +15 -0
- data/lib/genius_referrals/resources/products.rb +86 -0
- data/lib/genius_referrals/resources/redemption_requests.rb +36 -0
- data/lib/genius_referrals/resources/referrals.rb +34 -0
- data/lib/genius_referrals/resources/reports.rb +72 -0
- data/lib/genius_referrals/resources/root.rb +8 -0
- data/lib/genius_referrals/resources/tags.rb +35 -0
- data/lib/genius_referrals/resources/utilities.rb +58 -0
- data/lib/genius_referrals/resources/vouchers.rb +33 -0
- data/lib/genius_referrals/resources/widgets_packages.rb +94 -0
- data/lib/genius_referrals/resources.rb +14 -0
- data/lib/genius_referrals/response.rb +34 -0
- data/lib/genius_referrals/transport.rb +30 -0
- data/lib/genius_referrals/version.rb +3 -0
- data/lib/genius_referrals.rb +7 -0
- data/test/client_test.rb +130 -0
- data/test/integration/authentication_test.rb +13 -0
- data/test/resources_test.rb +101 -0
- data/test/test_helper.rb +32 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c34a1d0205f298289eed4ce89eb837e18a5405c644c003c77c83215ffbabc03f
|
|
4
|
+
data.tar.gz: 654890ad79e62b41413ee18a61919f4f69dbfb794f7e98fbb210c1097df1bb0a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c30024498ceae4d664ed86b9aa884679f537a52bb9db2c8df88c6b9c61ec54a7d59c2d083cd41e710d9de1da77c3492b51eb656683976fa5e15d28266cb4be8c
|
|
7
|
+
data.tar.gz: 54fea89f16a36c671bc0faa52330f34db88417e80c12d4cbec9e6c536fb551f1218c8339c78eac21806da987fe92c801fdc698f3903f848c81223891cf16bf40
|
data/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
License:
|
|
2
|
+
========
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
http://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2014 - 2016 APIMATIC Limited
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
|
16
|
+
all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
24
|
+
THE SOFTWARE.
|
|
25
|
+
|
|
26
|
+
Trade Mark:
|
|
27
|
+
==========
|
|
28
|
+
APIMATIC is a trade mark for APIMATIC Limited
|
data/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Genius Referrals Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the Genius Referrals public API.
|
|
4
|
+
|
|
5
|
+
This rebuild is based on the accepted public API contract matrix from
|
|
6
|
+
`alainhl/gr-agent-led-dev-delivery#14` and is the first implementation slice for
|
|
7
|
+
`GeniusReferrals/Genius-Referrals-RUBY#2`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
gem install genius_referrals
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For local validation from this repository:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gem build genius_referrals.gemspec
|
|
19
|
+
gem install ./genius_referrals-0.1.0.gem
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Authentication
|
|
23
|
+
|
|
24
|
+
The API uses the `X-Auth-Token` header. The SDK redacts the token from exception
|
|
25
|
+
request snapshots and never includes it in error messages.
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require 'genius_referrals'
|
|
29
|
+
|
|
30
|
+
client = GeniusReferrals::Client.new(auth_token: 'gr_live_or_test_token')
|
|
31
|
+
result = client.authentications.test
|
|
32
|
+
puts result.data
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The default base URL is `https://api.geniusreferrals.com`.
|
|
36
|
+
|
|
37
|
+
## Resource Examples
|
|
38
|
+
|
|
39
|
+
List advocates:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
page = client.advocates.list('my-account', page: 1, limit: 25)
|
|
43
|
+
|
|
44
|
+
page.results.each do |advocate|
|
|
45
|
+
puts [advocate['token'], advocate['email']].compact.join(' ')
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Create an advocate with the accepted wrapper:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
client.advocates.create(
|
|
53
|
+
'my-account',
|
|
54
|
+
{
|
|
55
|
+
name: 'Ada',
|
|
56
|
+
lastname: 'Lovelace',
|
|
57
|
+
email: 'ada@example.com'
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Patch a bonus with unwrapped partial fields:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
client.bonuses.patch('my-account', 1234, status: 'approved')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Call a report:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
client.reports.revenue(
|
|
72
|
+
account_slug: 'my-account',
|
|
73
|
+
start_date: '2026-01-01',
|
|
74
|
+
end_date: '2026-01-31'
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Pagination
|
|
79
|
+
|
|
80
|
+
Collection helpers return a `GeniusReferrals::Page` object when the response
|
|
81
|
+
uses the standard `limit`, `page`, `total`, and `results` shape.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
client.advocates.each('my-account', limit: 100) do |advocate|
|
|
85
|
+
process(advocate)
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The SDK preserves API parameter names from the accepted matrix: `page` is sent
|
|
90
|
+
as `page`, and `limit` is sent as `limit`.
|
|
91
|
+
|
|
92
|
+
## Errors
|
|
93
|
+
|
|
94
|
+
The SDK raises typed exceptions:
|
|
95
|
+
|
|
96
|
+
- `GeniusReferrals::AuthenticationError` for HTTP `401`
|
|
97
|
+
- `GeniusReferrals::AuthorizationError` for HTTP `403`
|
|
98
|
+
- `GeniusReferrals::NotFoundError` for HTTP `404`
|
|
99
|
+
- `GeniusReferrals::ConflictError` for HTTP `409`
|
|
100
|
+
- `GeniusReferrals::ValidationError` for HTTP `400` and `422`
|
|
101
|
+
- `GeniusReferrals::RateLimitError` for HTTP `429`
|
|
102
|
+
- `GeniusReferrals::APIError` for other non-success responses
|
|
103
|
+
- `GeniusReferrals::TransportError` for local network or serialization failures
|
|
104
|
+
|
|
105
|
+
All API errors include `status_code`, parsed `response`, raw body text when
|
|
106
|
+
available, and redacted request details.
|
|
107
|
+
|
|
108
|
+
## Guarded Integration Test
|
|
109
|
+
|
|
110
|
+
Integration tests are opt-in so CI and local development do not use real
|
|
111
|
+
credentials by accident:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
GR_SDK_RUN_INTEGRATION=1 GR_API_TOKEN=... ruby -Itest test/integration/authentication_test.rb
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Do not print or commit the token.
|
|
118
|
+
|
|
119
|
+
## Package Validation
|
|
120
|
+
|
|
121
|
+
This ticket does not publish to RubyGems. Validate the candidate locally with:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ruby -Itest test/client_test.rb test/resources_test.rb
|
|
125
|
+
ruby -c lib/genius_referrals.rb
|
|
126
|
+
gem build genius_referrals.gemspec
|
|
127
|
+
sha256sum genius_referrals-0.1.0.gem
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Current candidate version: `0.1.0`.
|
|
131
|
+
|
|
132
|
+
Rollback/removal if an unpublished candidate is bad: delete the local gem from
|
|
133
|
+
the repository root, fix the branch, rebuild, and replace the PR evidence.
|
|
134
|
+
Public RubyGems publication requires Ledger risk review and Alain approval.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module GeniusReferrals
|
|
5
|
+
DEFAULT_BASE_URL = 'https://api.geniusreferrals.com'
|
|
6
|
+
RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504].freeze
|
|
7
|
+
|
|
8
|
+
class Client
|
|
9
|
+
attr_reader :auth_token, :base_url, :timeout, :max_retries, :backoff_factor,
|
|
10
|
+
:transport, :user_agent
|
|
11
|
+
|
|
12
|
+
# Client for the Genius Referrals public API.
|
|
13
|
+
#
|
|
14
|
+
# @param auth_token [String] API token sent as `X-Auth-Token`.
|
|
15
|
+
# @param base_url [String] API origin. Defaults to production.
|
|
16
|
+
# @param timeout [Numeric] Request timeout in seconds.
|
|
17
|
+
# @param max_retries [Integer] Retry budget for transient errors.
|
|
18
|
+
# @param backoff_factor [Numeric] Initial exponential backoff delay.
|
|
19
|
+
# @param transport [Object] Optional test/custom transport.
|
|
20
|
+
def initialize(auth_token:, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 2,
|
|
21
|
+
backoff_factor: 0.25, transport: nil,
|
|
22
|
+
user_agent: "geniusreferrals-ruby/#{VERSION}")
|
|
23
|
+
raise ArgumentError, 'auth_token is required' if auth_token.to_s.empty?
|
|
24
|
+
|
|
25
|
+
@auth_token = auth_token
|
|
26
|
+
@base_url = base_url.to_s.sub(%r{/+\z}, '')
|
|
27
|
+
@timeout = timeout
|
|
28
|
+
@max_retries = max_retries
|
|
29
|
+
@backoff_factor = backoff_factor
|
|
30
|
+
@transport = transport || NetHTTPTransport.new
|
|
31
|
+
@user_agent = user_agent
|
|
32
|
+
|
|
33
|
+
@accounts = AccountsResource.new(self)
|
|
34
|
+
@advocates = AdvocatesResource.new(self)
|
|
35
|
+
@authentications = AuthenticationsResource.new(self)
|
|
36
|
+
@bonuses = BonusesResource.new(self)
|
|
37
|
+
@campaigns = CampaignsResource.new(self)
|
|
38
|
+
@products = ProductsResource.new(self)
|
|
39
|
+
@redemption_requests = RedemptionRequestsResource.new(self)
|
|
40
|
+
@referrals = ReferralsResource.new(self)
|
|
41
|
+
@reports = ReportsResource.new(self)
|
|
42
|
+
@root = RootResource.new(self)
|
|
43
|
+
@tags = TagsResource.new(self)
|
|
44
|
+
@utilities = UtilitiesResource.new(self)
|
|
45
|
+
@vouchers = VouchersResource.new(self)
|
|
46
|
+
@widgets_packages = WidgetsPackagesResource.new(self)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attr_reader :accounts, :advocates, :authentications, :bonuses, :campaigns,
|
|
50
|
+
:products, :redemption_requests, :referrals, :reports, :root,
|
|
51
|
+
:tags, :utilities, :vouchers, :widgets_packages
|
|
52
|
+
|
|
53
|
+
# Send a request to an API path and return parsed response content.
|
|
54
|
+
def request(method, path, params: nil, json: nil)
|
|
55
|
+
request = build_request(method, path, params: params, json: json)
|
|
56
|
+
attempt = 0
|
|
57
|
+
|
|
58
|
+
loop do
|
|
59
|
+
begin
|
|
60
|
+
response = transport.request(
|
|
61
|
+
request[:method],
|
|
62
|
+
request[:url],
|
|
63
|
+
headers: request[:headers],
|
|
64
|
+
body: request[:body],
|
|
65
|
+
timeout: timeout
|
|
66
|
+
)
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
raise TransportError, e.message if attempt >= max_retries
|
|
69
|
+
|
|
70
|
+
sleep_for_retry(attempt)
|
|
71
|
+
attempt += 1
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return parse_success(response.status_code, response.body) if response.status_code < 400
|
|
76
|
+
|
|
77
|
+
if RETRYABLE_STATUS_CODES.include?(response.status_code) && attempt < max_retries
|
|
78
|
+
sleep_for_retry(attempt)
|
|
79
|
+
attempt += 1
|
|
80
|
+
next
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
parsed = parse_json(response.body)
|
|
84
|
+
error_class = ERROR_BY_STATUS.fetch(response.status_code, APIError)
|
|
85
|
+
raise error_class.new(
|
|
86
|
+
error_message(response.status_code, parsed, response.body),
|
|
87
|
+
status_code: response.status_code,
|
|
88
|
+
response: parsed,
|
|
89
|
+
body: response.body,
|
|
90
|
+
request: request,
|
|
91
|
+
secrets: [auth_token]
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Build an authenticated JSON request hash.
|
|
97
|
+
def build_request(method, path, params: nil, json: nil)
|
|
98
|
+
clean_path = path.start_with?('/') ? path : "/#{path}"
|
|
99
|
+
url = "#{base_url}#{clean_path}#{query_string(params || {})}"
|
|
100
|
+
body = json.nil? ? nil : JSON.generate(json)
|
|
101
|
+
headers = {
|
|
102
|
+
'Accept' => 'application/json',
|
|
103
|
+
'User-Agent' => user_agent,
|
|
104
|
+
'X-Auth-Token' => auth_token
|
|
105
|
+
}
|
|
106
|
+
headers['Content-Type'] = 'application/json' unless json.nil?
|
|
107
|
+
|
|
108
|
+
{ method: method.to_s.upcase, url: url, headers: headers, body: body }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def query_string(params)
|
|
114
|
+
pairs = []
|
|
115
|
+
params.each do |key, value|
|
|
116
|
+
next if value.nil?
|
|
117
|
+
|
|
118
|
+
if value.is_a?(Array)
|
|
119
|
+
value.each { |item| pairs << [key, item] }
|
|
120
|
+
else
|
|
121
|
+
pairs << [key, value]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
pairs.empty? ? '' : "?#{URI.encode_www_form(pairs)}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def parse_success(status_code, body)
|
|
128
|
+
return nil if status_code == 204 || body.to_s.empty?
|
|
129
|
+
|
|
130
|
+
parse_json(body)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_json(body)
|
|
134
|
+
return nil if body.to_s.empty?
|
|
135
|
+
|
|
136
|
+
JSON.parse(body)
|
|
137
|
+
rescue JSON::ParserError
|
|
138
|
+
body
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def error_message(status_code, parsed, body)
|
|
142
|
+
if parsed.is_a?(Hash)
|
|
143
|
+
message = parsed['message'] || parsed['error']
|
|
144
|
+
return "Genius Referrals API error #{status_code}: #{message}" if message
|
|
145
|
+
end
|
|
146
|
+
return "Genius Referrals API error #{status_code}" unless body.to_s.empty?
|
|
147
|
+
|
|
148
|
+
"Genius Referrals API error #{status_code}: empty response"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def sleep_for_retry(attempt)
|
|
152
|
+
delay = backoff_factor * (2**attempt)
|
|
153
|
+
sleep(delay) if delay.positive?
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Backward-compatible class name for callers of the older generated gem.
|
|
158
|
+
class GeniusReferralsClient < Client
|
|
159
|
+
def initialize(content_type = 'application/json', x_auth_token = nil, **options)
|
|
160
|
+
if content_type.is_a?(Hash)
|
|
161
|
+
options = content_type.merge(options)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
x_auth_token = options.delete(:x_auth_token) || options.delete('x_auth_token') || x_auth_token
|
|
165
|
+
token = options.delete(:auth_token) || x_auth_token
|
|
166
|
+
super(auth_token: token, **options)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module GeniusReferrals
|
|
2
|
+
SENSITIVE_HEADER_NAMES = ['authorization', 'x-auth-token'].freeze
|
|
3
|
+
SENSITIVE_FIELD_FRAGMENTS = ['api_key', 'apikey', 'authorization', 'password', 'secret', 'token'].freeze
|
|
4
|
+
|
|
5
|
+
# Returns a redacted deep copy for objects that may contain credentials.
|
|
6
|
+
def self.redact(value, secrets: [])
|
|
7
|
+
secret_values = Array(secrets).compact.map(&:to_s).reject(&:empty?)
|
|
8
|
+
case value
|
|
9
|
+
when Hash
|
|
10
|
+
value.each_with_object({}) do |(key, item), redacted|
|
|
11
|
+
lowered = key.to_s.downcase
|
|
12
|
+
redacted[key] = if sensitive_key?(lowered)
|
|
13
|
+
'[REDACTED]'
|
|
14
|
+
else
|
|
15
|
+
redact(item, secrets: secret_values)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
when Array
|
|
19
|
+
value.map { |item| redact(item, secrets: secret_values) }
|
|
20
|
+
when String
|
|
21
|
+
redact_string(value, secret_values)
|
|
22
|
+
else
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.sensitive_key?(key)
|
|
28
|
+
SENSITIVE_HEADER_NAMES.include?(key) ||
|
|
29
|
+
SENSITIVE_FIELD_FRAGMENTS.any? { |fragment| key.include?(fragment) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.redact_string(value, secrets)
|
|
33
|
+
secrets.reduce(value.dup) do |text, secret|
|
|
34
|
+
text.gsub(secret, '[REDACTED]')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Error < StandardError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class TransportError < Error
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class APIError < Error
|
|
45
|
+
attr_reader :status_code, :response, :body, :request
|
|
46
|
+
|
|
47
|
+
def initialize(message, status_code:, response: nil, body: nil, request: nil, secrets: [])
|
|
48
|
+
redacted_message = GeniusReferrals.redact(message, secrets: secrets)
|
|
49
|
+
super(redacted_message)
|
|
50
|
+
@status_code = status_code
|
|
51
|
+
@response = GeniusReferrals.redact(response, secrets: secrets)
|
|
52
|
+
@body = GeniusReferrals.redact(body, secrets: secrets)
|
|
53
|
+
@request = GeniusReferrals.redact(request || {}, secrets: secrets)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class AuthenticationError < APIError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class AuthorizationError < APIError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class NotFoundError < APIError
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class ConflictError < APIError
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class ValidationError < APIError
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class RateLimitError < APIError
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ERROR_BY_STATUS = {
|
|
76
|
+
400 => ValidationError,
|
|
77
|
+
401 => AuthenticationError,
|
|
78
|
+
403 => AuthorizationError,
|
|
79
|
+
404 => NotFoundError,
|
|
80
|
+
409 => ConflictError,
|
|
81
|
+
422 => ValidationError,
|
|
82
|
+
429 => RateLimitError
|
|
83
|
+
}.freeze
|
|
84
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module GeniusReferrals
|
|
4
|
+
class Resource
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
attr_reader :client
|
|
12
|
+
|
|
13
|
+
def request(method, path, params: nil, json: nil)
|
|
14
|
+
APIResponse.from_api(client.request(method, path, params: params, json: json))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def page(method, path, params: nil)
|
|
18
|
+
Page.from_api(client.request(method, path, params: params))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each_page(path, page_number: 1, limit: 100, params: nil)
|
|
22
|
+
return enum_for(:each_page, path, page_number: page_number, limit: limit, params: params) unless block_given?
|
|
23
|
+
|
|
24
|
+
current = page_number
|
|
25
|
+
loop do
|
|
26
|
+
response = page('GET', path, params: compact((params || {}).merge(page: current, limit: limit)))
|
|
27
|
+
response.results.each { |item| yield item }
|
|
28
|
+
break if response.total.nil? || response.results.empty?
|
|
29
|
+
break if current * limit >= response.total.to_i
|
|
30
|
+
|
|
31
|
+
current += 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def compact(hash)
|
|
36
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
37
|
+
result[key] = value unless value.nil?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def wrapped(name, payload)
|
|
42
|
+
{ name => payload.to_h }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def esc(value)
|
|
46
|
+
URI.encode_www_form_component(value.to_s)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module GeniusReferrals
|
|
2
|
+
class AccountsResource < Resource
|
|
3
|
+
# List accounts with standard page/limit pagination.
|
|
4
|
+
def list(page: 1, limit: 10, filter: nil, sort: nil)
|
|
5
|
+
page('GET', '/accounts', params: compact(page: page, limit: limit, filter: filter, sort: sort))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Retrieve one account by slug.
|
|
9
|
+
def retrieve(account_slug)
|
|
10
|
+
request('GET', "/accounts/#{esc(account_slug)}")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module GeniusReferrals
|
|
2
|
+
class AdvocatesResource < Resource
|
|
3
|
+
# List advocates for an account.
|
|
4
|
+
def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil, **filters)
|
|
5
|
+
params = compact({ page: page, limit: limit, filter: filter, sort: sort }.merge(filters))
|
|
6
|
+
page('GET', "/accounts/#{esc(account_slug)}/advocates", params: params)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Iterate advocates across all available pages.
|
|
10
|
+
def each(account_slug, page_number: 1, limit: 100, **filters, &block)
|
|
11
|
+
each_page("/accounts/#{esc(account_slug)}/advocates",
|
|
12
|
+
page_number: page_number,
|
|
13
|
+
limit: limit,
|
|
14
|
+
params: filters,
|
|
15
|
+
&block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create an advocate using the accepted `advocate` wrapper.
|
|
19
|
+
def create(account_slug, advocate)
|
|
20
|
+
request('POST', "/accounts/#{esc(account_slug)}/advocates", json: wrapped('advocate', advocate))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Retrieve one advocate by token.
|
|
24
|
+
def retrieve(account_slug, advocate_token)
|
|
25
|
+
request('GET', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Replace an advocate using the accepted `advocate` wrapper.
|
|
29
|
+
def update(account_slug, advocate_token, advocate)
|
|
30
|
+
request('PUT',
|
|
31
|
+
"/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}",
|
|
32
|
+
json: wrapped('advocate', advocate))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Patch an advocate with unwrapped partial fields.
|
|
36
|
+
def patch(account_slug, advocate_token, **fields)
|
|
37
|
+
request('PATCH', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}", json: fields)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Delete one advocate.
|
|
41
|
+
def delete(account_slug, advocate_token)
|
|
42
|
+
request('DELETE', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Delete all advocates for an account, or a scoped comma-separated token set.
|
|
46
|
+
def delete_all(account_slug, advocates: nil)
|
|
47
|
+
query_value = advocates.is_a?(Array) ? advocates.join(',') : advocates
|
|
48
|
+
request('DELETE',
|
|
49
|
+
"/accounts/#{esc(account_slug)}/advocates",
|
|
50
|
+
params: compact(advocates: query_value))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Return share links for one advocate.
|
|
54
|
+
def share_links(account_slug, advocate_token)
|
|
55
|
+
request('GET', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/share-links")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# List payment methods for one advocate.
|
|
59
|
+
def payment_methods(account_slug, advocate_token, page: 1, limit: 10)
|
|
60
|
+
page('GET',
|
|
61
|
+
"/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods",
|
|
62
|
+
params: { page: page, limit: limit })
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Create an advocate payment method.
|
|
66
|
+
def create_payment_method(account_slug, advocate_token, payment_method)
|
|
67
|
+
request('POST',
|
|
68
|
+
"/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods",
|
|
69
|
+
json: wrapped('advocate_payment_method', payment_method))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Retrieve one advocate payment method.
|
|
73
|
+
def retrieve_payment_method(account_slug, advocate_token, payment_method_id)
|
|
74
|
+
request('GET',
|
|
75
|
+
"/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods/#{esc(payment_method_id)}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Replace one advocate payment method.
|
|
79
|
+
def update_payment_method(account_slug, advocate_token, payment_method_id, payment_method)
|
|
80
|
+
request('PUT',
|
|
81
|
+
"/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods/#{esc(payment_method_id)}",
|
|
82
|
+
json: wrapped('advocate_payment_method', payment_method))
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module GeniusReferrals
|
|
2
|
+
class BonusesResource < Resource
|
|
3
|
+
# List bonuses for an account.
|
|
4
|
+
def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil, **filters)
|
|
5
|
+
params = compact({ page: page, limit: limit, filter: filter, sort: sort }.merge(filters))
|
|
6
|
+
page('GET', "/accounts/#{esc(account_slug)}/bonuses", params: params)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Create a bonus using the `bonus` wrapper.
|
|
10
|
+
def create(account_slug, bonus)
|
|
11
|
+
request('POST', "/accounts/#{esc(account_slug)}/bonuses", json: wrapped('bonus', bonus))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Create a forced bonus using the `bonus` wrapper.
|
|
15
|
+
def force(account_slug, bonus)
|
|
16
|
+
request('POST', "/accounts/#{esc(account_slug)}/bonuses/force", json: wrapped('bonus', bonus))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Simulate bonus eligibility with API query parameters.
|
|
20
|
+
def checkup(account_slug, **params)
|
|
21
|
+
request('GET', "/accounts/#{esc(account_slug)}/bonuses/checkup", params: params)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# List bonus request traces.
|
|
25
|
+
def traces(account_slug, page: 1, limit: 10, **params)
|
|
26
|
+
page('GET',
|
|
27
|
+
"/accounts/#{esc(account_slug)}/bonuses/traces",
|
|
28
|
+
params: compact({ page: page, limit: limit }.merge(params)))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Retrieve one bonus request trace.
|
|
32
|
+
def retrieve_trace(account_slug, trace_id)
|
|
33
|
+
request('GET', "/accounts/#{esc(account_slug)}/bonuses/traces/#{esc(trace_id)}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Retrieve one bonus.
|
|
37
|
+
def retrieve(account_slug, bonus_id)
|
|
38
|
+
request('GET', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Patch a bonus with unwrapped partial fields.
|
|
42
|
+
def patch(account_slug, bonus_id, **fields)
|
|
43
|
+
request('PATCH', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}", json: fields)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Delete one bonus.
|
|
47
|
+
def delete(account_slug, bonus_id)
|
|
48
|
+
request('DELETE', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Attach a tag to a bonus using the live-doc `bonus.tag.slug` shape.
|
|
52
|
+
def add_tag(account_slug, bonus_id, tag_slug)
|
|
53
|
+
request('POST',
|
|
54
|
+
"/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}/tags",
|
|
55
|
+
json: { 'bonus' => { 'tag' => { 'slug' => tag_slug } } })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Remove one tag from a bonus.
|
|
59
|
+
def remove_tag(account_slug, bonus_id, tag_slug)
|
|
60
|
+
request('DELETE', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}/tags/#{esc(tag_slug)}")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module GeniusReferrals
|
|
2
|
+
class CampaignsResource < Resource
|
|
3
|
+
# List campaigns for an account.
|
|
4
|
+
def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil)
|
|
5
|
+
page('GET',
|
|
6
|
+
"/accounts/#{esc(account_slug)}/campaigns",
|
|
7
|
+
params: compact(page: page, limit: limit, filter: filter, sort: sort))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Retrieve one campaign.
|
|
11
|
+
def retrieve(account_slug, campaign_slug)
|
|
12
|
+
request('GET', "/accounts/#{esc(account_slug)}/campaigns/#{esc(campaign_slug)}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|