risenexa-leads 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/.rspec +3 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +245 -0
- data/Rakefile +8 -0
- data/lib/risenexa/leads/version.rb +7 -0
- data/lib/risenexa/leads.rb +262 -0
- data/sig/risenexa/leads.rbs +6 -0
- metadata +129 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bf34b5b98cf6c49a1cba41db3e85d60e10967e51315c3fcbc0a69f172039c65b
|
|
4
|
+
data.tar.gz: 581d96bc497842f48c752e56cc472727f9d2c39fe60bad605a1ecdec6abda261
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a0ec6e59a6c61ce13587b19bd95d49054b7d42ea3a39bb3229219dcdd5c6164ca7deec18c25b43fd5354312b330897e8bf02d8e078dbb5379615da760aae1771
|
|
7
|
+
data.tar.gz: bce5295272ceb7d35257a043eb5bb57f464625d57022a54ac0991ee395c390e23544dfa9eefa2a5dff058a4cde1255a25346701f5f5cbd85afcbabbeb0498ab8
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-04-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial release of the `risenexa-leads` gem
|
|
15
|
+
- Global configuration via `RisenexaLeads.configure` block (`api_key`, `startup_id`,
|
|
16
|
+
`base_url`, `timeout`)
|
|
17
|
+
- `RisenexaLeads.track` for capturing a single lead with email, name, plan tier,
|
|
18
|
+
source, language, external ID, UTM parameters, and custom properties
|
|
19
|
+
- `RisenexaLeads.batch` for sending up to 100 leads in a single request, with
|
|
20
|
+
partial-success handling
|
|
21
|
+
- `Result` object with `success?`, `partial_success?`, `created`, `errors`, and
|
|
22
|
+
`error` accessors for ergonomic response handling
|
|
23
|
+
- Faraday-based HTTP client with `faraday-retry` for transient failure recovery
|
|
24
|
+
- Argument validation for required fields and batch size limits
|
|
25
|
+
- RSpec test suite covering the public API (25 examples)
|
|
26
|
+
- MIT License
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Espake
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Risenexa Leads
|
|
2
|
+
|
|
3
|
+
Ruby SDK for capturing leads and sending them to [Risenexa](https://app.risenexa.com) for AI-powered customer discovery.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'risenexa-leads'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install risenexa-leads
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Configure the gem with your API credentials. In a Rails app, create an initializer:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# config/initializers/risenexa_leads.rb
|
|
31
|
+
|
|
32
|
+
RisenexaLeads.configure do |config|
|
|
33
|
+
config.api_key = ENV['RISENEXA_API_KEY']
|
|
34
|
+
config.startup_id = ENV['RISENEXA_STARTUP_ID']
|
|
35
|
+
|
|
36
|
+
# Optional settings
|
|
37
|
+
config.base_url = 'https://app.risenexa.com' # Default
|
|
38
|
+
config.timeout = 30 # Default: 30 seconds
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Getting Your Credentials
|
|
43
|
+
|
|
44
|
+
1. Log in to your Risenexa account
|
|
45
|
+
2. Navigate to your Startup settings
|
|
46
|
+
3. Copy your API key and Startup ID from the SDK Integration section
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Track a Single Lead
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
result = RisenexaLeads.track(
|
|
54
|
+
email: 'john@example.com',
|
|
55
|
+
name: 'John Doe',
|
|
56
|
+
plan_tier: 'pro', # optional
|
|
57
|
+
source: 'signup_form', # optional
|
|
58
|
+
language: 'en', # optional, defaults to 'en'
|
|
59
|
+
external_id: 'user_12345', # optional, for idempotent upserts
|
|
60
|
+
utm_params: { # optional
|
|
61
|
+
source: 'google',
|
|
62
|
+
medium: 'cpc',
|
|
63
|
+
campaign: 'launch'
|
|
64
|
+
},
|
|
65
|
+
properties: { # optional, custom attributes
|
|
66
|
+
signup_reason: 'productivity',
|
|
67
|
+
team_size: 10
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if result.success?
|
|
72
|
+
puts "Lead created: #{result.lead['id']}"
|
|
73
|
+
else
|
|
74
|
+
puts "Error: #{result.error}"
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Track Multiple Leads (Batch)
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
leads = [
|
|
82
|
+
{ email: 'john@example.com', name: 'John Doe', plan_tier: 'pro' },
|
|
83
|
+
{ email: 'jane@example.com', name: 'Jane Smith', plan_tier: 'starter' }
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
result = RisenexaLeads.batch(leads)
|
|
87
|
+
|
|
88
|
+
if result.success?
|
|
89
|
+
puts "Created #{result.created.length} leads"
|
|
90
|
+
elsif result.partial_success?
|
|
91
|
+
puts "Created #{result.created.length} leads"
|
|
92
|
+
puts "Failed: #{result.errors.length}"
|
|
93
|
+
result.errors.each do |error|
|
|
94
|
+
puts " - #{error['email']}: #{error['errors'].join(', ')}"
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
puts "Error: #{result.error}"
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Note:** Batch requests are limited to 100 leads per request.
|
|
102
|
+
|
|
103
|
+
### Rails Integration Example
|
|
104
|
+
|
|
105
|
+
Track leads when users sign up:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# app/controllers/registrations_controller.rb
|
|
109
|
+
class RegistrationsController < ApplicationController
|
|
110
|
+
def create
|
|
111
|
+
@user = User.new(user_params)
|
|
112
|
+
|
|
113
|
+
if @user.save
|
|
114
|
+
# Track the lead asynchronously
|
|
115
|
+
TrackLeadJob.perform_later(@user.id)
|
|
116
|
+
redirect_to dashboard_path
|
|
117
|
+
else
|
|
118
|
+
render :new
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# app/jobs/track_lead_job.rb
|
|
124
|
+
class TrackLeadJob < ApplicationJob
|
|
125
|
+
queue_as :default
|
|
126
|
+
|
|
127
|
+
def perform(user_id)
|
|
128
|
+
user = User.find(user_id)
|
|
129
|
+
|
|
130
|
+
RisenexaLeads.track(
|
|
131
|
+
email: user.email,
|
|
132
|
+
name: user.name,
|
|
133
|
+
external_id: "user_#{user.id}",
|
|
134
|
+
plan_tier: user.plan,
|
|
135
|
+
source: user.signup_source,
|
|
136
|
+
utm_params: user.utm_params,
|
|
137
|
+
properties: {
|
|
138
|
+
company: user.company_name,
|
|
139
|
+
role: user.job_title
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Response Objects
|
|
147
|
+
|
|
148
|
+
### Result (for single leads)
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
result = RisenexaLeads.track(...)
|
|
152
|
+
|
|
153
|
+
result.success? # true if lead was created
|
|
154
|
+
result.failure? # true if there was an error
|
|
155
|
+
result.lead # Hash with lead data on success
|
|
156
|
+
result.error # Error message string on failure
|
|
157
|
+
result.errors # Array of validation errors (for 422 responses)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### BatchResult (for batch operations)
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
result = RisenexaLeads.batch(...)
|
|
164
|
+
|
|
165
|
+
result.success? # true if all leads were created
|
|
166
|
+
result.partial_success? # true if some leads were created
|
|
167
|
+
result.failure? # true if no leads were created
|
|
168
|
+
result.created # Array of created lead hashes
|
|
169
|
+
result.errors # Array of error hashes with index, email, and errors
|
|
170
|
+
result.error # General error message (for auth/rate limit errors)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
The SDK handles various error scenarios gracefully:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
result = RisenexaLeads.track(email: 'test@example.com', name: 'Test')
|
|
179
|
+
|
|
180
|
+
case
|
|
181
|
+
when result.success?
|
|
182
|
+
# Lead created successfully
|
|
183
|
+
when result.errors.any?
|
|
184
|
+
# Validation errors (e.g., invalid email, duplicate)
|
|
185
|
+
result.errors.each { |e| puts e }
|
|
186
|
+
when result.error.include?('Rate limit')
|
|
187
|
+
# Too many requests, implement backoff
|
|
188
|
+
when result.error.include?('Invalid API token')
|
|
189
|
+
# Check your API key configuration
|
|
190
|
+
else
|
|
191
|
+
# Other error
|
|
192
|
+
puts result.error
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Configuration Errors
|
|
197
|
+
|
|
198
|
+
Missing configuration will raise `RisenexaLeads::ConfigurationError`:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
RisenexaLeads.reset_configuration!
|
|
202
|
+
RisenexaLeads.track(email: 'test@example.com', name: 'Test')
|
|
203
|
+
# => raises RisenexaLeads::ConfigurationError: api_key is required
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Idempotency
|
|
207
|
+
|
|
208
|
+
Use `external_id` to make lead creation idempotent:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
# First call creates the lead
|
|
212
|
+
RisenexaLeads.track(
|
|
213
|
+
email: 'john@example.com',
|
|
214
|
+
name: 'John Doe',
|
|
215
|
+
external_id: 'user_123'
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Second call with same external_id updates the existing lead
|
|
219
|
+
RisenexaLeads.track(
|
|
220
|
+
email: 'john.updated@example.com',
|
|
221
|
+
name: 'John Doe Updated',
|
|
222
|
+
external_id: 'user_123'
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Rate Limiting
|
|
227
|
+
|
|
228
|
+
The Risenexa API is rate limited to 100 requests per minute per API token. The SDK will return a failure result with a rate limit error message when exceeded.
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
bundle install
|
|
236
|
+
bundle exec rspec
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Contributing
|
|
240
|
+
|
|
241
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/patrickespake/risenexa-leads-gem.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/retry"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
require_relative "leads/version"
|
|
8
|
+
|
|
9
|
+
module Risenexa
|
|
10
|
+
module Leads
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Top-level module for convenient SDK access
|
|
16
|
+
module RisenexaLeads
|
|
17
|
+
class ConfigurationError < StandardError; end
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
attr_accessor :configuration
|
|
21
|
+
|
|
22
|
+
def configure
|
|
23
|
+
self.configuration ||= Configuration.new
|
|
24
|
+
yield(configuration)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset_configuration!
|
|
28
|
+
self.configuration = nil
|
|
29
|
+
@client = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def track(attributes)
|
|
33
|
+
validate_configuration!
|
|
34
|
+
client.track(attributes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def batch(leads)
|
|
38
|
+
raise ArgumentError, "Batch requires at least one lead" if leads.empty?
|
|
39
|
+
raise ArgumentError, "Batch maximum is 100 leads" if leads.length > 100
|
|
40
|
+
|
|
41
|
+
validate_configuration!
|
|
42
|
+
client.batch(leads)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def validate_configuration!
|
|
48
|
+
raise ConfigurationError, "api_key is required" unless configuration&.api_key
|
|
49
|
+
raise ConfigurationError, "startup_id is required" unless configuration&.startup_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def client
|
|
53
|
+
@client ||= Client.new(configuration)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Configuration
|
|
58
|
+
attr_accessor :api_key, :startup_id, :base_url, :timeout
|
|
59
|
+
|
|
60
|
+
def initialize
|
|
61
|
+
@base_url = "https://app.risenexa.com"
|
|
62
|
+
@timeout = 30
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Client
|
|
67
|
+
def initialize(configuration)
|
|
68
|
+
@configuration = configuration
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def track(attributes)
|
|
72
|
+
response = connection.post("/api/v1/leads") do |req|
|
|
73
|
+
req.body = build_lead_payload(attributes).to_json
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
handle_response(response)
|
|
77
|
+
rescue Faraday::TimeoutError
|
|
78
|
+
Result.failure("Connection timeout")
|
|
79
|
+
rescue Faraday::ConnectionFailed => e
|
|
80
|
+
Result.failure("Connection failed: #{e.message}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def batch(leads)
|
|
84
|
+
response = connection.post("/api/v1/leads/batch") do |req|
|
|
85
|
+
req.body = build_batch_payload(leads).to_json
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
handle_batch_response(response)
|
|
89
|
+
rescue Faraday::TimeoutError
|
|
90
|
+
BatchResult.failure("Connection timeout")
|
|
91
|
+
rescue Faraday::ConnectionFailed => e
|
|
92
|
+
BatchResult.failure("Connection failed: #{e.message}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
attr_reader :configuration
|
|
98
|
+
|
|
99
|
+
def connection
|
|
100
|
+
@connection ||= Faraday.new(url: configuration.base_url) do |conn|
|
|
101
|
+
conn.request :retry, max: 2, interval: 0.5, backoff_factor: 2
|
|
102
|
+
conn.headers["Authorization"] = "Bearer #{configuration.api_key}"
|
|
103
|
+
conn.headers["Content-Type"] = "application/json"
|
|
104
|
+
conn.headers["Accept"] = "application/json"
|
|
105
|
+
conn.options.timeout = configuration.timeout
|
|
106
|
+
conn.adapter Faraday.default_adapter
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_lead_payload(attributes)
|
|
111
|
+
lead = {
|
|
112
|
+
startup_id: configuration.startup_id,
|
|
113
|
+
email: attributes[:email],
|
|
114
|
+
name: attributes[:name]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Optional attributes
|
|
118
|
+
lead[:plan_tier] = attributes[:plan_tier] if attributes[:plan_tier]
|
|
119
|
+
lead[:source] = attributes[:source] if attributes[:source]
|
|
120
|
+
lead[:language] = attributes[:language] if attributes[:language]
|
|
121
|
+
lead[:external_id] = attributes[:external_id] if attributes[:external_id]
|
|
122
|
+
lead[:utm_params] = attributes[:utm_params] if attributes[:utm_params]
|
|
123
|
+
lead[:properties] = attributes[:properties] if attributes[:properties]
|
|
124
|
+
|
|
125
|
+
{ lead: lead }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_batch_payload(leads)
|
|
129
|
+
{
|
|
130
|
+
startup_id: configuration.startup_id,
|
|
131
|
+
leads: leads.map { |lead| normalize_lead(lead) }
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def normalize_lead(lead)
|
|
136
|
+
normalized = {
|
|
137
|
+
email: lead[:email],
|
|
138
|
+
name: lead[:name]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
%i[plan_tier source language external_id utm_params properties].each do |key|
|
|
142
|
+
normalized[key] = lead[key] if lead[key]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
normalized
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def handle_response(response)
|
|
149
|
+
case response.status
|
|
150
|
+
when 200, 201
|
|
151
|
+
data = parse_json(response.body)
|
|
152
|
+
Result.success(data["data"] || data)
|
|
153
|
+
when 401
|
|
154
|
+
data = parse_json(response.body)
|
|
155
|
+
Result.failure(data["error"] || "Unauthorized")
|
|
156
|
+
when 422
|
|
157
|
+
data = parse_json(response.body)
|
|
158
|
+
Result.validation_failure(data["errors"] || [])
|
|
159
|
+
when 429
|
|
160
|
+
data = parse_json(response.body)
|
|
161
|
+
Result.failure(data["error"] || "Rate limit exceeded")
|
|
162
|
+
when 500..599
|
|
163
|
+
Result.failure("Server error (#{response.status})")
|
|
164
|
+
else
|
|
165
|
+
Result.failure("Unexpected response: #{response.status}")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def handle_batch_response(response)
|
|
170
|
+
case response.status
|
|
171
|
+
when 200, 201
|
|
172
|
+
data = parse_json(response.body)
|
|
173
|
+
result_data = data["data"] || data
|
|
174
|
+
BatchResult.new(
|
|
175
|
+
created: result_data["created"] || 0,
|
|
176
|
+
failed: result_data["failed"] || 0,
|
|
177
|
+
errors: result_data["errors"] || []
|
|
178
|
+
)
|
|
179
|
+
when 401
|
|
180
|
+
data = parse_json(response.body)
|
|
181
|
+
BatchResult.failure(data["error"] || "Unauthorized")
|
|
182
|
+
when 422
|
|
183
|
+
data = parse_json(response.body)
|
|
184
|
+
BatchResult.failure(data["errors"]&.join(", ") || "Validation failed")
|
|
185
|
+
when 429
|
|
186
|
+
data = parse_json(response.body)
|
|
187
|
+
BatchResult.failure(data["error"] || "Rate limit exceeded")
|
|
188
|
+
when 500..599
|
|
189
|
+
BatchResult.failure("Server error (#{response.status})")
|
|
190
|
+
else
|
|
191
|
+
BatchResult.failure("Unexpected response: #{response.status}")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def parse_json(body)
|
|
196
|
+
JSON.parse(body)
|
|
197
|
+
rescue JSON::ParserError
|
|
198
|
+
{}
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class Result
|
|
203
|
+
attr_reader :lead, :error, :errors
|
|
204
|
+
|
|
205
|
+
def initialize(lead: nil, error: nil, errors: nil)
|
|
206
|
+
@lead = lead
|
|
207
|
+
@error = error
|
|
208
|
+
@errors = errors || []
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def success?
|
|
212
|
+
@lead && @error.nil?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def failure?
|
|
216
|
+
!success?
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class << self
|
|
220
|
+
def success(lead_data)
|
|
221
|
+
new(lead: lead_data)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def failure(error_message)
|
|
225
|
+
new(error: error_message)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def validation_failure(errors)
|
|
229
|
+
new(error: errors.first, errors: errors)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class BatchResult
|
|
235
|
+
attr_reader :created, :failed, :errors, :error
|
|
236
|
+
|
|
237
|
+
def initialize(created: 0, failed: 0, errors: [], error: nil)
|
|
238
|
+
@created = created
|
|
239
|
+
@failed = failed
|
|
240
|
+
@errors = errors
|
|
241
|
+
@error = error
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def success?
|
|
245
|
+
@error.nil? && @errors.empty?
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def partial_success?
|
|
249
|
+
@error.nil? && @created.positive? && @errors.any?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def failure?
|
|
253
|
+
@error.present? || (@created.zero? && @errors.any?)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
class << self
|
|
257
|
+
def failure(error_message)
|
|
258
|
+
new(error: error_message)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: risenexa-leads
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Patrick Espake
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: faraday-retry
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '2.0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.0'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.0'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: webmock
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.0'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '3.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: rake
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '13.0'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '13.0'
|
|
88
|
+
description: A Ruby gem to track user signups and send lead data to Risenexa for AI-powered
|
|
89
|
+
customer discovery.
|
|
90
|
+
email:
|
|
91
|
+
- patrickespake@gmail.com
|
|
92
|
+
executables: []
|
|
93
|
+
extensions: []
|
|
94
|
+
extra_rdoc_files: []
|
|
95
|
+
files:
|
|
96
|
+
- ".rspec"
|
|
97
|
+
- CHANGELOG.md
|
|
98
|
+
- LICENSE.txt
|
|
99
|
+
- README.md
|
|
100
|
+
- Rakefile
|
|
101
|
+
- lib/risenexa/leads.rb
|
|
102
|
+
- lib/risenexa/leads/version.rb
|
|
103
|
+
- sig/risenexa/leads.rbs
|
|
104
|
+
homepage: https://github.com/envixo/risenexa-leads-gem
|
|
105
|
+
licenses:
|
|
106
|
+
- MIT
|
|
107
|
+
metadata:
|
|
108
|
+
allowed_push_host: https://rubygems.org
|
|
109
|
+
homepage_uri: https://github.com/envixo/risenexa-leads-gem
|
|
110
|
+
source_code_uri: https://github.com/envixo/risenexa-leads-gem
|
|
111
|
+
changelog_uri: https://github.com/envixo/risenexa-leads-gem/blob/main/CHANGELOG.md
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 3.1.0
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubygems_version: 3.6.9
|
|
127
|
+
specification_version: 4
|
|
128
|
+
summary: Ruby SDK for capturing leads and sending them to Risenexa
|
|
129
|
+
test_files: []
|