bundleup-sdk 0.1.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 +0 -20
- data/README.md +913 -143
- data/lib/bundleup/client.rb +10 -39
- data/lib/bundleup/proxy.rb +42 -84
- data/lib/bundleup/resources/base.rb +78 -0
- data/lib/bundleup/resources/connection.rb +16 -0
- data/lib/bundleup/resources/integration.rb +16 -0
- data/lib/bundleup/resources/webhook.rb +16 -0
- data/lib/bundleup/unify/base.rb +13 -51
- data/lib/bundleup/unify/chat.rb +9 -10
- data/lib/bundleup/unify/git.rb +39 -43
- data/lib/bundleup/unify/pm.rb +9 -10
- data/lib/bundleup/unify.rb +16 -0
- data/lib/bundleup/version.rb +1 -1
- data/lib/bundleup.rb +10 -19
- metadata +40 -20
- data/lib/bundleup/base.rb +0 -110
- data/lib/bundleup/connection.rb +0 -12
- data/lib/bundleup/errors.rb +0 -15
- data/lib/bundleup/integration.rb +0 -12
- data/lib/bundleup/webhook.rb +0 -12
data/README.md
CHANGED
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
# BundleUp Ruby SDK
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/bundleup)
|
|
3
|
+
[](https://badge.fury.io/rb/bundleup-sdk)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Official Ruby
|
|
6
|
+
Official Ruby SDK for the [BundleUp](https://bundleup.io) API. Connect to 100+ integrations with a single, unified API. Build once, integrate everywhere.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Requirements](#requirements)
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Authentication](#authentication)
|
|
15
|
+
- [Core Concepts](#core-concepts)
|
|
16
|
+
- [API Reference](#api-reference)
|
|
17
|
+
- [Connections](#connections)
|
|
18
|
+
- [Integrations](#integrations)
|
|
19
|
+
- [Webhooks](#webhooks)
|
|
20
|
+
- [Proxy API](#proxy-api)
|
|
21
|
+
- [Unify API](#unify-api)
|
|
22
|
+
- [Error Handling](#error-handling)
|
|
23
|
+
- [Development](#development)
|
|
24
|
+
- [Contributing](#contributing)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
- [Support](#support)
|
|
7
27
|
|
|
8
28
|
## Installation
|
|
9
29
|
|
|
30
|
+
Install the SDK using Bundler or RubyGems:
|
|
31
|
+
|
|
32
|
+
**Using Bundler (recommended):**
|
|
33
|
+
|
|
10
34
|
Add this line to your application's Gemfile:
|
|
11
35
|
|
|
12
36
|
```ruby
|
|
13
|
-
gem 'bundleup'
|
|
37
|
+
gem 'bundleup-sdk'
|
|
14
38
|
```
|
|
15
39
|
|
|
16
40
|
And then execute:
|
|
@@ -19,41 +43,81 @@ And then execute:
|
|
|
19
43
|
bundle install
|
|
20
44
|
```
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
**Using RubyGems:**
|
|
23
47
|
|
|
24
48
|
```bash
|
|
25
|
-
gem install bundleup
|
|
49
|
+
gem install bundleup-sdk
|
|
26
50
|
```
|
|
27
51
|
|
|
28
52
|
## Requirements
|
|
29
53
|
|
|
30
|
-
- Ruby 2.7 or higher
|
|
31
|
-
-
|
|
54
|
+
- **Ruby**: 2.7.0 or higher
|
|
55
|
+
- **Faraday**: ~> 2.0 (automatically installed as a dependency)
|
|
56
|
+
|
|
57
|
+
### Ruby Compatibility
|
|
58
|
+
|
|
59
|
+
The BundleUp SDK is tested and supported on:
|
|
60
|
+
|
|
61
|
+
- Ruby 2.7.x
|
|
62
|
+
- Ruby 3.0.x
|
|
63
|
+
- Ruby 3.1.x
|
|
64
|
+
- Ruby 3.2.x
|
|
65
|
+
- Ruby 3.3.x
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- ๐ **Ruby Idiomatic** - Follows Ruby best practices and conventions
|
|
70
|
+
- ๐ฆ **Easy Integration** - Simple, intuitive API design
|
|
71
|
+
- โก **HTTP/2 Support** - Built on Faraday for modern HTTP features
|
|
72
|
+
- ๐ **100+ Integrations** - Connect to Slack, GitHub, Jira, Linear, and many more
|
|
73
|
+
- ๐ฏ **Unified API** - Consistent interface across all integrations via Unify API
|
|
74
|
+
- ๐ **Proxy API** - Direct access to underlying integration APIs
|
|
75
|
+
- ๐ชถ **Lightweight** - Minimal dependencies
|
|
76
|
+
- ๐ก๏ธ **Error Handling** - Comprehensive error messages and validation
|
|
77
|
+
- ๐ **Well Documented** - Extensive documentation and examples
|
|
78
|
+
- ๐งช **Tested** - Comprehensive test suite with RSpec
|
|
32
79
|
|
|
33
80
|
## Quick Start
|
|
34
81
|
|
|
82
|
+
Get started with BundleUp in just a few lines of code:
|
|
83
|
+
|
|
35
84
|
```ruby
|
|
36
85
|
require 'bundleup'
|
|
37
86
|
|
|
38
|
-
# Initialize the client
|
|
87
|
+
# Initialize the client
|
|
39
88
|
client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
|
|
40
89
|
|
|
41
|
-
# List all connections
|
|
90
|
+
# List all active connections
|
|
42
91
|
connections = client.connections.list
|
|
43
|
-
puts connections
|
|
92
|
+
puts "You have #{connections.length} active connections"
|
|
44
93
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
94
|
+
# Use the Proxy API to make requests to integrated services
|
|
95
|
+
proxy = client.proxy('conn_123')
|
|
96
|
+
response = proxy.get('/api/users')
|
|
97
|
+
puts "Users: #{response.body}"
|
|
98
|
+
|
|
99
|
+
# Use the Unify API for standardized data across integrations
|
|
100
|
+
unify = client.unify('conn_456')
|
|
101
|
+
channels = unify.chat.channels(limit: 10)
|
|
102
|
+
puts "Chat channels: #{channels['data']}"
|
|
50
103
|
```
|
|
51
104
|
|
|
52
105
|
## Authentication
|
|
53
106
|
|
|
54
107
|
The BundleUp SDK uses API keys for authentication. You can obtain your API key from the [BundleUp Dashboard](https://app.bundleup.io).
|
|
55
108
|
|
|
109
|
+
### Getting Your API Key
|
|
110
|
+
|
|
111
|
+
1. Sign in to your [BundleUp Dashboard](https://app.bundleup.io)
|
|
112
|
+
2. Navigate to **API Keys**
|
|
113
|
+
3. Click **Create API Key**
|
|
114
|
+
4. Copy your API key and store it securely
|
|
115
|
+
|
|
116
|
+
### Initializing the SDK
|
|
117
|
+
|
|
56
118
|
```ruby
|
|
119
|
+
require 'bundleup'
|
|
120
|
+
|
|
57
121
|
# Initialize with API key
|
|
58
122
|
client = Bundleup::Client.new('your_api_key_here')
|
|
59
123
|
|
|
@@ -61,252 +125,958 @@ client = Bundleup::Client.new('your_api_key_here')
|
|
|
61
125
|
client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
|
|
62
126
|
```
|
|
63
127
|
|
|
64
|
-
|
|
128
|
+
### Security Best Practices
|
|
65
129
|
|
|
66
|
-
|
|
130
|
+
- โ
**DO** store API keys in environment variables
|
|
131
|
+
- โ
**DO** use a secrets management service in production
|
|
132
|
+
- โ
**DO** rotate API keys regularly
|
|
133
|
+
- โ **DON'T** commit API keys to version control
|
|
134
|
+
- โ **DON'T** hardcode API keys in your source code
|
|
135
|
+
- โ **DON'T** share API keys in public channels
|
|
136
|
+
|
|
137
|
+
**Example `.env` file:**
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
BUNDLEUP_API_KEY=bu_live_1234567890abcdefghijklmnopqrstuvwxyz
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Loading environment variables (using dotenv):**
|
|
144
|
+
|
|
145
|
+
Add to your Gemfile:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
gem 'dotenv'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then in your application:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
require 'dotenv/load'
|
|
155
|
+
require 'bundleup'
|
|
156
|
+
|
|
157
|
+
client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**For Rails applications:**
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# config/initializers/bundleup.rb
|
|
164
|
+
BUNDLEUP_CLIENT = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Core Concepts
|
|
168
|
+
|
|
169
|
+
### Platform API
|
|
170
|
+
|
|
171
|
+
The **Platform API** provides access to core BundleUp features like managing connections and integrations. Use this API to list, retrieve, and delete connections, as well as discover available integrations.
|
|
172
|
+
|
|
173
|
+
### Proxy API
|
|
174
|
+
|
|
175
|
+
The **Proxy API** allows you to make direct HTTP requests to the underlying integration's API through BundleUp. This is useful when you need access to integration-specific features not covered by the Unify API.
|
|
176
|
+
|
|
177
|
+
### Unify API
|
|
178
|
+
|
|
179
|
+
The **Unify API** provides a standardized, normalized interface across different integrations. For example, you can fetch chat channels from Slack, Discord, or Microsoft Teams using the same API call.
|
|
180
|
+
|
|
181
|
+
## API Reference
|
|
67
182
|
|
|
68
183
|
### Connections
|
|
69
184
|
|
|
70
|
-
Manage your integration connections
|
|
185
|
+
Manage your integration connections.
|
|
186
|
+
|
|
187
|
+
#### List Connections
|
|
188
|
+
|
|
189
|
+
Retrieve a list of all connections in your account.
|
|
71
190
|
|
|
72
191
|
```ruby
|
|
73
|
-
# List all connections
|
|
74
192
|
connections = client.connections.list
|
|
193
|
+
```
|
|
75
194
|
|
|
76
|
-
|
|
77
|
-
connections = client.connections.list(limit: 10, page: 1)
|
|
195
|
+
**With query parameters:**
|
|
78
196
|
|
|
79
|
-
|
|
80
|
-
|
|
197
|
+
```ruby
|
|
198
|
+
connections = client.connections.list(
|
|
199
|
+
integration_id: 'int_slack',
|
|
200
|
+
limit: 50,
|
|
201
|
+
offset: 0,
|
|
202
|
+
external_id: 'user_123'
|
|
203
|
+
)
|
|
204
|
+
```
|
|
81
205
|
|
|
82
|
-
|
|
83
|
-
connection = client.connections.create({
|
|
84
|
-
name: 'GitHub Connection',
|
|
85
|
-
integration_id: 'int_github'
|
|
86
|
-
})
|
|
206
|
+
**Query Parameters:**
|
|
87
207
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
208
|
+
- `integration_id` (String): Filter by integration ID
|
|
209
|
+
- `integration_identifier` (String): Filter by integration identifier (e.g., 'slack', 'github')
|
|
210
|
+
- `external_id` (String): Filter by external user/account ID
|
|
211
|
+
- `limit` (Integer): Maximum number of results (default: 50, max: 100)
|
|
212
|
+
- `offset` (Integer): Number of results to skip for pagination
|
|
213
|
+
|
|
214
|
+
**Response:**
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
[
|
|
218
|
+
{
|
|
219
|
+
'id' => 'conn_123abc',
|
|
220
|
+
'external_id' => 'user_456',
|
|
221
|
+
'integration_id' => 'int_slack',
|
|
222
|
+
'is_valid' => true,
|
|
223
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
224
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
225
|
+
'refreshed_at' => '2024-01-20T14:22:00Z',
|
|
226
|
+
'expires_at' => '2024-04-20T14:22:00Z'
|
|
227
|
+
},
|
|
228
|
+
# ... more connections
|
|
229
|
+
]
|
|
230
|
+
```
|
|
92
231
|
|
|
93
|
-
|
|
94
|
-
|
|
232
|
+
#### Retrieve a Connection
|
|
233
|
+
|
|
234
|
+
Get details of a specific connection by ID.
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
connection = client.connections.retrieve('conn_123abc')
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Response:**
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
{
|
|
244
|
+
'id' => 'conn_123abc',
|
|
245
|
+
'external_id' => 'user_456',
|
|
246
|
+
'integration_id' => 'int_slack',
|
|
247
|
+
'is_valid' => true,
|
|
248
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
249
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
250
|
+
'refreshed_at' => '2024-01-20T14:22:00Z',
|
|
251
|
+
'expires_at' => '2024-04-20T14:22:00Z'
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Delete a Connection
|
|
256
|
+
|
|
257
|
+
Remove a connection from your account.
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
client.connections.delete('conn_123abc')
|
|
95
261
|
```
|
|
96
262
|
|
|
263
|
+
**Note:** Deleting a connection will revoke access to the integration and cannot be undone.
|
|
264
|
+
|
|
97
265
|
### Integrations
|
|
98
266
|
|
|
99
|
-
|
|
267
|
+
Discover and work with available integrations.
|
|
268
|
+
|
|
269
|
+
#### List Integrations
|
|
270
|
+
|
|
271
|
+
Get a list of all available integrations.
|
|
100
272
|
|
|
101
273
|
```ruby
|
|
102
|
-
# List all integrations
|
|
103
274
|
integrations = client.integrations.list
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**With query parameters:**
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
integrations = client.integrations.list(
|
|
281
|
+
status: 'active',
|
|
282
|
+
limit: 100,
|
|
283
|
+
offset: 0
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Query Parameters:**
|
|
104
288
|
|
|
105
|
-
|
|
106
|
-
|
|
289
|
+
- `status` (String): Filter by status ('active', 'inactive', 'beta')
|
|
290
|
+
- `limit` (Integer): Maximum number of results
|
|
291
|
+
- `offset` (Integer): Number of results to skip for pagination
|
|
292
|
+
|
|
293
|
+
**Response:**
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
[
|
|
297
|
+
{
|
|
298
|
+
'id' => 'int_slack',
|
|
299
|
+
'identifier' => 'slack',
|
|
300
|
+
'name' => 'Slack',
|
|
301
|
+
'category' => 'chat',
|
|
302
|
+
'created_at' => '2023-01-01T00:00:00Z',
|
|
303
|
+
'updated_at' => '2024-01-15T10:00:00Z'
|
|
304
|
+
},
|
|
305
|
+
# ... more integrations
|
|
306
|
+
]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Retrieve an Integration
|
|
310
|
+
|
|
311
|
+
Get details of a specific integration.
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
integration = client.integrations.retrieve('int_slack')
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Response:**
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
{
|
|
321
|
+
'id' => 'int_slack',
|
|
322
|
+
'identifier' => 'slack',
|
|
323
|
+
'name' => 'Slack',
|
|
324
|
+
'category' => 'chat',
|
|
325
|
+
'created_at' => '2023-01-01T00:00:00Z',
|
|
326
|
+
'updated_at' => '2024-01-15T10:00:00Z'
|
|
327
|
+
}
|
|
107
328
|
```
|
|
108
329
|
|
|
109
330
|
### Webhooks
|
|
110
331
|
|
|
111
|
-
Manage webhook subscriptions
|
|
332
|
+
Manage webhook subscriptions for real-time event notifications.
|
|
333
|
+
|
|
334
|
+
#### List Webhooks
|
|
335
|
+
|
|
336
|
+
Get all registered webhooks.
|
|
112
337
|
|
|
113
338
|
```ruby
|
|
114
|
-
# List all webhooks
|
|
115
339
|
webhooks = client.webhooks.list
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**With pagination:**
|
|
116
343
|
|
|
117
|
-
|
|
118
|
-
|
|
344
|
+
```ruby
|
|
345
|
+
webhooks = client.webhooks.list(
|
|
346
|
+
limit: 50,
|
|
347
|
+
offset: 0
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Response:**
|
|
352
|
+
|
|
353
|
+
```ruby
|
|
354
|
+
[
|
|
355
|
+
{
|
|
356
|
+
'id' => 'webhook_123',
|
|
357
|
+
'name' => 'My Webhook',
|
|
358
|
+
'url' => 'https://example.com/webhook',
|
|
359
|
+
'events' => {
|
|
360
|
+
'connection.created' => true,
|
|
361
|
+
'connection.deleted' => true
|
|
362
|
+
},
|
|
363
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
364
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
365
|
+
'last_triggered_at' => '2024-01-20T14:22:00Z'
|
|
366
|
+
}
|
|
367
|
+
]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
#### Create a Webhook
|
|
371
|
+
|
|
372
|
+
Register a new webhook endpoint.
|
|
373
|
+
|
|
374
|
+
```ruby
|
|
375
|
+
webhook = client.webhooks.create(
|
|
376
|
+
name: 'Connection Events Webhook',
|
|
119
377
|
url: 'https://example.com/webhook',
|
|
120
|
-
events:
|
|
121
|
-
|
|
378
|
+
events: {
|
|
379
|
+
'connection.created' => true,
|
|
380
|
+
'connection.deleted' => true,
|
|
381
|
+
'connection.updated' => true
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Webhook Events:**
|
|
387
|
+
|
|
388
|
+
- `connection.created` - Triggered when a new connection is established
|
|
389
|
+
- `connection.deleted` - Triggered when a connection is removed
|
|
390
|
+
- `connection.updated` - Triggered when a connection is modified
|
|
391
|
+
|
|
392
|
+
**Request Body:**
|
|
122
393
|
|
|
123
|
-
|
|
394
|
+
- `name` (String): Friendly name for the webhook
|
|
395
|
+
- `url` (String): Your webhook endpoint URL
|
|
396
|
+
- `events` (Hash): Events to subscribe to
|
|
397
|
+
|
|
398
|
+
**Response:**
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
{
|
|
402
|
+
'id' => 'webhook_123',
|
|
403
|
+
'name' => 'Connection Events Webhook',
|
|
404
|
+
'url' => 'https://example.com/webhook',
|
|
405
|
+
'events' => {
|
|
406
|
+
'connection.created' => true,
|
|
407
|
+
'connection.deleted' => true,
|
|
408
|
+
'connection.updated' => true
|
|
409
|
+
},
|
|
410
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
411
|
+
'updated_at' => '2024-01-15T10:30:00Z'
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### Retrieve a Webhook
|
|
416
|
+
|
|
417
|
+
Get details of a specific webhook.
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
124
420
|
webhook = client.webhooks.retrieve('webhook_123')
|
|
421
|
+
```
|
|
125
422
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
423
|
+
#### Update a Webhook
|
|
424
|
+
|
|
425
|
+
Modify an existing webhook.
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
updated = client.webhooks.update('webhook_123',
|
|
429
|
+
name: 'Updated Webhook Name',
|
|
430
|
+
url: 'https://example.com/new-webhook',
|
|
431
|
+
events: {
|
|
432
|
+
'connection.created' => true,
|
|
433
|
+
'connection.deleted' => false
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
#### Delete a Webhook
|
|
130
439
|
|
|
131
|
-
|
|
440
|
+
Remove a webhook subscription.
|
|
441
|
+
|
|
442
|
+
```ruby
|
|
132
443
|
client.webhooks.delete('webhook_123')
|
|
133
444
|
```
|
|
134
445
|
|
|
446
|
+
#### Webhook Payload Example
|
|
447
|
+
|
|
448
|
+
When an event occurs, BundleUp sends a POST request to your webhook URL with the following payload:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"id": "evt_1234567890",
|
|
453
|
+
"type": "connection.created",
|
|
454
|
+
"created_at": "2024-01-15T10:30:00Z",
|
|
455
|
+
"data": {
|
|
456
|
+
"id": "conn_123abc",
|
|
457
|
+
"external_id": "user_456",
|
|
458
|
+
"integration_id": "int_slack",
|
|
459
|
+
"is_valid": true,
|
|
460
|
+
"created_at": "2024-01-15T10:30:00Z"
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Webhook Security (Rails Example)
|
|
466
|
+
|
|
467
|
+
To verify webhook signatures in a Rails application:
|
|
468
|
+
|
|
469
|
+
```ruby
|
|
470
|
+
# app/controllers/webhooks_controller.rb
|
|
471
|
+
class WebhooksController < ApplicationController
|
|
472
|
+
skip_before_action :verify_authenticity_token
|
|
473
|
+
|
|
474
|
+
def create
|
|
475
|
+
signature = request.headers['X-Bundleup-Signature']
|
|
476
|
+
payload = request.body.read
|
|
477
|
+
|
|
478
|
+
unless verify_signature(payload, signature)
|
|
479
|
+
render json: { error: 'Invalid signature' }, status: :unauthorized
|
|
480
|
+
return
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
event = JSON.parse(payload)
|
|
484
|
+
process_webhook_event(event)
|
|
485
|
+
|
|
486
|
+
head :ok
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
private
|
|
490
|
+
|
|
491
|
+
def verify_signature(payload, signature)
|
|
492
|
+
secret = ENV['BUNDLEUP_WEBHOOK_SECRET']
|
|
493
|
+
computed = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
|
|
494
|
+
ActiveSupport::SecurityUtils.secure_compare(computed, signature)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def process_webhook_event(event)
|
|
498
|
+
case event['type']
|
|
499
|
+
when 'connection.created'
|
|
500
|
+
handle_connection_created(event['data'])
|
|
501
|
+
when 'connection.deleted'
|
|
502
|
+
handle_connection_deleted(event['data'])
|
|
503
|
+
# ... more event handlers
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
```
|
|
508
|
+
|
|
135
509
|
### Proxy API
|
|
136
510
|
|
|
137
|
-
Make direct
|
|
511
|
+
Make direct HTTP requests to integration APIs through BundleUp.
|
|
512
|
+
|
|
513
|
+
#### Creating a Proxy Instance
|
|
138
514
|
|
|
139
515
|
```ruby
|
|
140
|
-
|
|
141
|
-
|
|
516
|
+
proxy = client.proxy('conn_123abc')
|
|
517
|
+
```
|
|
142
518
|
|
|
143
|
-
|
|
144
|
-
users = proxy.get('/api/users')
|
|
519
|
+
#### GET Request
|
|
145
520
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
521
|
+
```ruby
|
|
522
|
+
response = proxy.get('/api/users')
|
|
523
|
+
data = response.body
|
|
524
|
+
puts data
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**With custom headers:**
|
|
528
|
+
|
|
529
|
+
```ruby
|
|
530
|
+
response = proxy.get('/api/users', headers: {
|
|
531
|
+
'X-Custom-Header' => 'value',
|
|
532
|
+
'Accept' => 'application/json'
|
|
150
533
|
})
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
#### POST Request
|
|
151
537
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
name: '
|
|
538
|
+
```ruby
|
|
539
|
+
response = proxy.post('/api/users', body: {
|
|
540
|
+
name: 'John Doe',
|
|
541
|
+
email: 'john@example.com',
|
|
542
|
+
role: 'developer'
|
|
155
543
|
})
|
|
156
544
|
|
|
157
|
-
|
|
158
|
-
|
|
545
|
+
new_user = response.body
|
|
546
|
+
puts "Created user: #{new_user}"
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**With custom headers:**
|
|
550
|
+
|
|
551
|
+
```ruby
|
|
552
|
+
response = proxy.post(
|
|
553
|
+
'/api/users',
|
|
554
|
+
body: { name: 'John Doe' },
|
|
555
|
+
headers: {
|
|
556
|
+
'Content-Type' => 'application/json',
|
|
557
|
+
'X-API-Version' => '2.0'
|
|
558
|
+
}
|
|
559
|
+
)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### PUT Request
|
|
563
|
+
|
|
564
|
+
```ruby
|
|
565
|
+
response = proxy.put('/api/users/123', body: {
|
|
566
|
+
name: 'Jane Doe',
|
|
159
567
|
email: 'jane@example.com'
|
|
160
568
|
})
|
|
161
569
|
|
|
162
|
-
|
|
163
|
-
|
|
570
|
+
updated_user = response.body
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### PATCH Request
|
|
574
|
+
|
|
575
|
+
```ruby
|
|
576
|
+
response = proxy.patch('/api/users/123', body: {
|
|
577
|
+
email: 'newemail@example.com'
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
partially_updated = response.body
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
#### DELETE Request
|
|
584
|
+
|
|
585
|
+
```ruby
|
|
586
|
+
response = proxy.delete('/api/users/123')
|
|
587
|
+
|
|
588
|
+
if response.success?
|
|
589
|
+
puts 'User deleted successfully'
|
|
590
|
+
end
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### Working with Response Objects
|
|
594
|
+
|
|
595
|
+
The Proxy API returns Faraday response objects:
|
|
596
|
+
|
|
597
|
+
```ruby
|
|
598
|
+
response = proxy.get('/api/users')
|
|
599
|
+
|
|
600
|
+
# Access response body
|
|
601
|
+
data = response.body
|
|
602
|
+
|
|
603
|
+
# Check status code
|
|
604
|
+
puts response.status # => 200
|
|
605
|
+
|
|
606
|
+
# Check if successful
|
|
607
|
+
puts response.success? # => true
|
|
608
|
+
|
|
609
|
+
# Access headers
|
|
610
|
+
puts response.headers['content-type']
|
|
611
|
+
|
|
612
|
+
# Handle errors
|
|
613
|
+
begin
|
|
614
|
+
response = proxy.get('/api/invalid')
|
|
615
|
+
rescue Faraday::Error => e
|
|
616
|
+
puts "Request failed: #{e.message}"
|
|
617
|
+
end
|
|
164
618
|
```
|
|
165
619
|
|
|
166
620
|
### Unify API
|
|
167
621
|
|
|
168
|
-
Access unified, normalized data across different integrations
|
|
622
|
+
Access unified, normalized data across different integrations with a consistent interface.
|
|
169
623
|
|
|
170
|
-
####
|
|
624
|
+
#### Creating a Unify Instance
|
|
171
625
|
|
|
172
626
|
```ruby
|
|
173
|
-
|
|
174
|
-
|
|
627
|
+
unify = client.unify('conn_123abc')
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### Chat API
|
|
631
|
+
|
|
632
|
+
The Chat API provides a unified interface for chat platforms like Slack, Discord, and Microsoft Teams.
|
|
175
633
|
|
|
176
|
-
|
|
177
|
-
channels = unify[:chat].channels(limit: 100)
|
|
634
|
+
##### List Channels
|
|
178
635
|
|
|
179
|
-
|
|
180
|
-
channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
|
|
636
|
+
Retrieve a list of channels from the connected chat platform.
|
|
181
637
|
|
|
182
|
-
|
|
183
|
-
|
|
638
|
+
```ruby
|
|
639
|
+
result = unify.chat.channels(
|
|
640
|
+
limit: 100,
|
|
641
|
+
after: nil,
|
|
642
|
+
include_raw: false
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
puts "Channels: #{result['data']}"
|
|
646
|
+
puts "Next cursor: #{result['metadata']['next']}"
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Parameters:**
|
|
650
|
+
|
|
651
|
+
- `limit` (Integer, optional): Maximum number of channels to return (default: 100, max: 1000)
|
|
652
|
+
- `after` (String, optional): Pagination cursor from previous response
|
|
653
|
+
- `include_raw` (Boolean, optional): Include raw API response from the integration (default: false)
|
|
654
|
+
|
|
655
|
+
**Response:**
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
{
|
|
659
|
+
'data' => [
|
|
660
|
+
{
|
|
661
|
+
'id' => 'C1234567890',
|
|
662
|
+
'name' => 'general'
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
'id' => 'C0987654321',
|
|
666
|
+
'name' => 'engineering'
|
|
667
|
+
}
|
|
668
|
+
],
|
|
669
|
+
'metadata' => {
|
|
670
|
+
'next' => 'cursor_abc123' # Use this for pagination
|
|
671
|
+
},
|
|
672
|
+
'_raw' => { # Only present if include_raw: true
|
|
673
|
+
# Original response from the integration API
|
|
674
|
+
}
|
|
675
|
+
}
|
|
184
676
|
```
|
|
185
677
|
|
|
186
|
-
|
|
678
|
+
**Pagination example:**
|
|
187
679
|
|
|
188
680
|
```ruby
|
|
189
|
-
|
|
681
|
+
all_channels = []
|
|
682
|
+
cursor = nil
|
|
683
|
+
|
|
684
|
+
loop do
|
|
685
|
+
result = unify.chat.channels(limit: 100, after: cursor)
|
|
686
|
+
all_channels.concat(result['data'])
|
|
687
|
+
cursor = result['metadata']['next']
|
|
688
|
+
break if cursor.nil?
|
|
689
|
+
end
|
|
190
690
|
|
|
191
|
-
#
|
|
192
|
-
|
|
691
|
+
puts "Fetched #{all_channels.length} total channels"
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Git API
|
|
193
695
|
|
|
194
|
-
|
|
195
|
-
pulls = unify[:git].pulls('owner/repo', limit: 20)
|
|
696
|
+
The Git API provides a unified interface for version control platforms like GitHub, GitLab, and Bitbucket.
|
|
196
697
|
|
|
197
|
-
|
|
198
|
-
tags = unify[:git].tags('owner/repo')
|
|
698
|
+
##### List Repositories
|
|
199
699
|
|
|
200
|
-
|
|
201
|
-
|
|
700
|
+
```ruby
|
|
701
|
+
result = unify.git.repos(
|
|
702
|
+
limit: 50,
|
|
703
|
+
after: nil,
|
|
704
|
+
include_raw: false
|
|
705
|
+
)
|
|
202
706
|
|
|
203
|
-
|
|
204
|
-
repos = unify[:git].repos(include_raw: true)
|
|
707
|
+
puts "Repositories: #{result['data']}"
|
|
205
708
|
```
|
|
206
709
|
|
|
207
|
-
|
|
710
|
+
**Response:**
|
|
208
711
|
|
|
209
712
|
```ruby
|
|
210
|
-
|
|
713
|
+
{
|
|
714
|
+
'data' => [
|
|
715
|
+
{
|
|
716
|
+
'id' => '123456',
|
|
717
|
+
'name' => 'my-awesome-project',
|
|
718
|
+
'full_name' => 'organization/my-awesome-project',
|
|
719
|
+
'description' => 'An awesome project',
|
|
720
|
+
'url' => 'https://github.com/organization/my-awesome-project',
|
|
721
|
+
'created_at' => '2023-01-15T10:30:00Z',
|
|
722
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
723
|
+
'pushed_at' => '2024-01-20T14:22:00Z'
|
|
724
|
+
}
|
|
725
|
+
],
|
|
726
|
+
'metadata' => {
|
|
727
|
+
'next' => 'cursor_xyz789'
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
```
|
|
211
731
|
|
|
212
|
-
|
|
213
|
-
issues = unify[:pm].issues(limit: 100)
|
|
732
|
+
##### List Pull Requests
|
|
214
733
|
|
|
215
|
-
|
|
216
|
-
|
|
734
|
+
```ruby
|
|
735
|
+
result = unify.git.pulls('organization/repo-name',
|
|
736
|
+
limit: 20,
|
|
737
|
+
after: nil,
|
|
738
|
+
include_raw: false
|
|
739
|
+
)
|
|
217
740
|
|
|
218
|
-
|
|
219
|
-
issues = unify[:pm].issues(include_raw: true)
|
|
741
|
+
puts "Pull Requests: #{result['data']}"
|
|
220
742
|
```
|
|
221
743
|
|
|
222
|
-
|
|
744
|
+
**Parameters:**
|
|
745
|
+
|
|
746
|
+
- `repo_name` (String, required): Repository name in the format 'owner/repo'
|
|
747
|
+
- `limit` (Integer, optional): Maximum number of PRs to return
|
|
748
|
+
- `after` (String, optional): Pagination cursor
|
|
749
|
+
- `include_raw` (Boolean, optional): Include raw API response
|
|
223
750
|
|
|
224
|
-
|
|
751
|
+
**Response:**
|
|
225
752
|
|
|
226
753
|
```ruby
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
754
|
+
{
|
|
755
|
+
'data' => [
|
|
756
|
+
{
|
|
757
|
+
'id' => '12345',
|
|
758
|
+
'number' => 42,
|
|
759
|
+
'title' => 'Add new feature',
|
|
760
|
+
'description' => 'This PR adds an awesome new feature',
|
|
761
|
+
'draft' => false,
|
|
762
|
+
'state' => 'open',
|
|
763
|
+
'url' => 'https://github.com/org/repo/pull/42',
|
|
764
|
+
'user' => 'john-doe',
|
|
765
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
766
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
767
|
+
'merged_at' => nil
|
|
768
|
+
}
|
|
769
|
+
],
|
|
770
|
+
'metadata' => {
|
|
771
|
+
'next' => nil
|
|
772
|
+
}
|
|
773
|
+
}
|
|
239
774
|
```
|
|
240
775
|
|
|
241
|
-
|
|
776
|
+
##### List Tags
|
|
242
777
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
- `Bundleup::AuthenticationError` - Raised when authentication fails
|
|
246
|
-
- `Bundleup::InvalidRequestError` - Raised when a request is invalid
|
|
778
|
+
```ruby
|
|
779
|
+
result = unify.git.tags('organization/repo-name', limit: 50)
|
|
247
780
|
|
|
248
|
-
|
|
781
|
+
puts "Tags: #{result['data']}"
|
|
782
|
+
```
|
|
249
783
|
|
|
250
|
-
|
|
784
|
+
**Response:**
|
|
251
785
|
|
|
252
786
|
```ruby
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
787
|
+
{
|
|
788
|
+
'data' => [
|
|
789
|
+
{
|
|
790
|
+
'name' => 'v1.0.0',
|
|
791
|
+
'commit_sha' => 'abc123def456'
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
'name' => 'v0.9.0',
|
|
795
|
+
'commit_sha' => 'def456ghi789'
|
|
796
|
+
}
|
|
797
|
+
],
|
|
798
|
+
'metadata' => {
|
|
799
|
+
'next' => nil
|
|
800
|
+
}
|
|
801
|
+
}
|
|
258
802
|
```
|
|
259
803
|
|
|
260
|
-
|
|
804
|
+
##### List Releases
|
|
805
|
+
|
|
806
|
+
```ruby
|
|
807
|
+
result = unify.git.releases('organization/repo-name', limit: 10)
|
|
808
|
+
|
|
809
|
+
puts "Releases: #{result['data']}"
|
|
810
|
+
```
|
|
261
811
|
|
|
262
|
-
|
|
812
|
+
**Response:**
|
|
263
813
|
|
|
264
814
|
```ruby
|
|
265
|
-
|
|
266
|
-
|
|
815
|
+
{
|
|
816
|
+
'data' => [
|
|
817
|
+
{
|
|
818
|
+
'id' => '54321',
|
|
819
|
+
'name' => 'Version 1.0.0',
|
|
820
|
+
'tag_name' => 'v1.0.0',
|
|
821
|
+
'description' => 'Initial release with all the features',
|
|
822
|
+
'prerelease' => false,
|
|
823
|
+
'url' => 'https://github.com/org/repo/releases/tag/v1.0.0',
|
|
824
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
825
|
+
'released_at' => '2024-01-15T10:30:00Z'
|
|
826
|
+
}
|
|
827
|
+
],
|
|
828
|
+
'metadata' => {
|
|
829
|
+
'next' => nil
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
```
|
|
267
833
|
|
|
268
|
-
|
|
269
|
-
|
|
834
|
+
#### Project Management API
|
|
835
|
+
|
|
836
|
+
The PM API provides a unified interface for project management platforms like Jira, Linear, and Asana.
|
|
837
|
+
|
|
838
|
+
##### List Issues
|
|
839
|
+
|
|
840
|
+
```ruby
|
|
841
|
+
result = unify.pm.issues(
|
|
842
|
+
limit: 100,
|
|
843
|
+
after: nil,
|
|
844
|
+
include_raw: false
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
puts "Issues: #{result['data']}"
|
|
270
848
|
```
|
|
271
849
|
|
|
272
|
-
|
|
850
|
+
**Response:**
|
|
273
851
|
|
|
274
|
-
|
|
852
|
+
```ruby
|
|
853
|
+
{
|
|
854
|
+
'data' => [
|
|
855
|
+
{
|
|
856
|
+
'id' => 'PROJ-123',
|
|
857
|
+
'url' => 'https://jira.example.com/browse/PROJ-123',
|
|
858
|
+
'title' => 'Fix login bug',
|
|
859
|
+
'status' => 'in_progress',
|
|
860
|
+
'description' => 'Users are unable to log in',
|
|
861
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
862
|
+
'updated_at' => '2024-01-20T14:22:00Z'
|
|
863
|
+
}
|
|
864
|
+
],
|
|
865
|
+
'metadata' => {
|
|
866
|
+
'next' => 'cursor_def456'
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
```
|
|
275
870
|
|
|
276
|
-
|
|
871
|
+
**Filtering and sorting:**
|
|
277
872
|
|
|
278
|
-
```
|
|
279
|
-
|
|
873
|
+
```ruby
|
|
874
|
+
open_issues = result['data'].select { |issue| issue['status'] == 'open' }
|
|
875
|
+
sorted_by_date = result['data'].sort_by { |issue| Time.parse(issue['created_at']) }.reverse
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
## Error Handling
|
|
879
|
+
|
|
880
|
+
The SDK raises exceptions for errors. Always wrap SDK calls in rescue blocks for proper error handling.
|
|
881
|
+
|
|
882
|
+
```ruby
|
|
883
|
+
begin
|
|
884
|
+
connections = client.connections.list
|
|
885
|
+
rescue StandardError => e
|
|
886
|
+
puts "Failed to fetch connections: #{e.message}"
|
|
887
|
+
end
|
|
280
888
|
```
|
|
281
889
|
|
|
282
|
-
|
|
890
|
+
## Development
|
|
891
|
+
|
|
892
|
+
### Setting Up Development Environment
|
|
283
893
|
|
|
284
894
|
```bash
|
|
285
|
-
|
|
895
|
+
# Clone the repository
|
|
896
|
+
git clone https://github.com/bundleup/bundleup-sdk-ruby.git
|
|
897
|
+
cd bundleup-sdk-ruby
|
|
898
|
+
|
|
899
|
+
# Install dependencies
|
|
900
|
+
bundle install
|
|
901
|
+
|
|
902
|
+
# Run tests
|
|
903
|
+
bundle exec rspec
|
|
904
|
+
|
|
905
|
+
# Run RuboCop
|
|
906
|
+
bundle exec rubocop
|
|
907
|
+
|
|
908
|
+
# Run tests with coverage
|
|
909
|
+
COVERAGE=true bundle exec rspec
|
|
286
910
|
```
|
|
287
911
|
|
|
288
|
-
|
|
912
|
+
### Project Structure
|
|
913
|
+
|
|
914
|
+
```
|
|
915
|
+
lib/
|
|
916
|
+
โโโ bundleup.rb # Main entry point
|
|
917
|
+
โโโ bundleup/
|
|
918
|
+
โ โโโ client.rb # Main client class
|
|
919
|
+
โ โโโ proxy.rb # Proxy API implementation
|
|
920
|
+
โ โโโ unify.rb # Unify API client wrapper
|
|
921
|
+
โ โโโ version.rb # Gem version
|
|
922
|
+
โ โโโ resources/
|
|
923
|
+
โ โ โโโ base.rb # Base resource class
|
|
924
|
+
โ โ โโโ connection.rb # Connections API
|
|
925
|
+
โ โ โโโ integration.rb # Integrations API
|
|
926
|
+
โ โ โโโ webhook.rb # Webhooks API
|
|
927
|
+
โ โโโ unify/
|
|
928
|
+
โ โโโ base.rb # Base Unify class
|
|
929
|
+
โ โโโ chat.rb # Chat Unify API
|
|
930
|
+
โ โโโ git.rb # Git Unify API
|
|
931
|
+
โ โโโ pm.rb # PM Unify API
|
|
932
|
+
spec/ # Test files
|
|
933
|
+
```
|
|
289
934
|
|
|
290
|
-
|
|
935
|
+
### Running Tests
|
|
291
936
|
|
|
292
937
|
```bash
|
|
938
|
+
# Run all tests
|
|
293
939
|
bundle exec rspec
|
|
940
|
+
|
|
941
|
+
# Run specific test file
|
|
942
|
+
bundle exec rspec spec/bundleup/proxy_spec.rb
|
|
943
|
+
|
|
944
|
+
# Run with documentation format
|
|
945
|
+
bundle exec rspec --format documentation
|
|
946
|
+
|
|
947
|
+
# Run with coverage
|
|
948
|
+
COVERAGE=true bundle exec rspec
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### Building the Gem
|
|
952
|
+
|
|
953
|
+
```bash
|
|
954
|
+
# Build the gem
|
|
955
|
+
gem build bundleup-sdk.gemspec
|
|
956
|
+
|
|
957
|
+
# Install locally
|
|
958
|
+
gem install bundleup-sdk-0.1.0.gem
|
|
959
|
+
|
|
960
|
+
# Push to RubyGems (requires credentials)
|
|
961
|
+
gem push bundleup-sdk-0.1.0.gem
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### Linting
|
|
965
|
+
|
|
966
|
+
```bash
|
|
967
|
+
# Run RuboCop
|
|
968
|
+
bundle exec rubocop
|
|
969
|
+
|
|
970
|
+
# Auto-correct offenses
|
|
971
|
+
bundle exec rubocop -a
|
|
972
|
+
|
|
973
|
+
# Check specific file
|
|
974
|
+
bundle exec rubocop lib/bundleup/client.rb
|
|
294
975
|
```
|
|
295
976
|
|
|
296
977
|
## Contributing
|
|
297
978
|
|
|
298
|
-
|
|
979
|
+
We welcome contributions to the BundleUp Ruby SDK! Here's how you can help:
|
|
980
|
+
|
|
981
|
+
### Reporting Bugs
|
|
982
|
+
|
|
983
|
+
1. Check if the bug has already been reported in [GitHub Issues](https://github.com/bundleup/bundleup-sdk-ruby/issues)
|
|
984
|
+
2. If not, create a new issue with:
|
|
985
|
+
- Clear title and description
|
|
986
|
+
- Steps to reproduce
|
|
987
|
+
- Expected vs actual behavior
|
|
988
|
+
- Gem version and Ruby version
|
|
989
|
+
|
|
990
|
+
### Suggesting Features
|
|
991
|
+
|
|
992
|
+
1. Open a new issue with the "feature request" label
|
|
993
|
+
2. Describe the feature and its use case
|
|
994
|
+
3. Explain why this feature would be useful
|
|
995
|
+
|
|
996
|
+
### Pull Requests
|
|
997
|
+
|
|
998
|
+
1. Fork the repository
|
|
999
|
+
2. Create a new branch: `git checkout -b feature/my-new-feature`
|
|
1000
|
+
3. Make your changes
|
|
1001
|
+
4. Write or update tests
|
|
1002
|
+
5. Ensure all tests pass: `bundle exec rspec`
|
|
1003
|
+
6. Run RuboCop: `bundle exec rubocop`
|
|
1004
|
+
7. Commit your changes: `git commit -am 'Add new feature'`
|
|
1005
|
+
8. Push to the branch: `git push origin feature/my-new-feature`
|
|
1006
|
+
9. Submit a pull request
|
|
1007
|
+
|
|
1008
|
+
### Development Guidelines
|
|
1009
|
+
|
|
1010
|
+
- Follow Ruby style guide and RuboCop rules
|
|
1011
|
+
- Add RSpec tests for new features
|
|
1012
|
+
- Update documentation for API changes
|
|
1013
|
+
- Keep commits focused and atomic
|
|
1014
|
+
- Write clear commit messages
|
|
1015
|
+
- Maintain backward compatibility when possible
|
|
299
1016
|
|
|
300
1017
|
## License
|
|
301
1018
|
|
|
302
|
-
|
|
1019
|
+
This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
1020
|
+
|
|
1021
|
+
```
|
|
1022
|
+
Copyright (c) 2024 BundleUp
|
|
1023
|
+
|
|
1024
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1025
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
1026
|
+
in the Software without restriction, including without limitation the rights
|
|
1027
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1028
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
1029
|
+
furnished to do so, subject to the following conditions:
|
|
1030
|
+
|
|
1031
|
+
The above copyright notice and this permission notice shall be included in all
|
|
1032
|
+
copies or substantial portions of the Software.
|
|
1033
|
+
|
|
1034
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1035
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1036
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1037
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1038
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1039
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1040
|
+
SOFTWARE.
|
|
1041
|
+
```
|
|
303
1042
|
|
|
304
1043
|
## Support
|
|
305
1044
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
1045
|
+
Need help? We're here for you!
|
|
1046
|
+
|
|
1047
|
+
### Documentation
|
|
1048
|
+
|
|
1049
|
+
- **Official Docs**: [https://docs.bundleup.io](https://docs.bundleup.io)
|
|
1050
|
+
- **API Reference**: [https://docs.bundleup.io/api](https://docs.bundleup.io/api)
|
|
1051
|
+
- **SDK Guides**: [https://docs.bundleup.io/sdk/ruby](https://docs.bundleup.io/sdk/ruby)
|
|
1052
|
+
|
|
1053
|
+
### Community
|
|
1054
|
+
|
|
1055
|
+
- **Discord**: [https://discord.gg/bundleup](https://discord.gg/bundleup)
|
|
1056
|
+
- **GitHub Discussions**: [https://github.com/bundleup/bundleup-sdk-ruby/discussions](https://github.com/bundleup/bundleup-sdk-ruby/discussions)
|
|
1057
|
+
- **Stack Overflow**: Tag your questions with `bundleup`
|
|
1058
|
+
|
|
1059
|
+
### Direct Support
|
|
1060
|
+
|
|
1061
|
+
- **Email**: [support@bundleup.io](mailto:support@bundleup.io)
|
|
1062
|
+
- **GitHub Issues**: [https://github.com/bundleup/bundleup-sdk-ruby/issues](https://github.com/bundleup/bundleup-sdk-ruby/issues)
|
|
1063
|
+
- **Twitter**: [@bundleup_io](https://twitter.com/bundleup_io)
|
|
1064
|
+
|
|
1065
|
+
### Enterprise Support
|
|
1066
|
+
|
|
1067
|
+
For enterprise customers, we offer:
|
|
1068
|
+
|
|
1069
|
+
- Priority support with SLA
|
|
1070
|
+
- Dedicated support channel
|
|
1071
|
+
- Architecture consultation
|
|
1072
|
+
- Custom integration assistance
|
|
1073
|
+
|
|
1074
|
+
Contact [enterprise@bundleup.io](mailto:enterprise@bundleup.io) for more information.
|
|
309
1075
|
|
|
310
1076
|
## Code of Conduct
|
|
311
1077
|
|
|
312
|
-
Everyone interacting in the BundleUp project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bundleup/bundleup-sdk-ruby/blob/main/CODE_OF_CONDUCT
|
|
1078
|
+
Everyone interacting in the BundleUp project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bundleup/bundleup-sdk-ruby/blob/main/CODE_OF_CONDUCT).
|
|
1079
|
+
|
|
1080
|
+
---
|
|
1081
|
+
|
|
1082
|
+
Made with โค๏ธ by the [BundleUp](https://bundleup.io) team
|