bundleup-sdk 0.1.1 โ 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 -23
- data/README.md +914 -127
- data/lib/bundleup/client.rb +10 -39
- data/lib/bundleup/proxy.rb +40 -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
|
@@ -3,10 +3,34 @@
|
|
|
3
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
|
|
@@ -19,7 +43,7 @@ And then execute:
|
|
|
19
43
|
bundle install
|
|
20
44
|
```
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
**Using RubyGems:**
|
|
23
47
|
|
|
24
48
|
```bash
|
|
25
49
|
gem install bundleup-sdk
|
|
@@ -27,27 +51,73 @@ gem install bundleup-sdk
|
|
|
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
|
-
require 'bundleup
|
|
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"
|
|
93
|
+
|
|
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']}"
|
|
44
103
|
```
|
|
45
104
|
|
|
46
105
|
## Authentication
|
|
47
106
|
|
|
48
107
|
The BundleUp SDK uses API keys for authentication. You can obtain your API key from the [BundleUp Dashboard](https://app.bundleup.io).
|
|
49
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
|
+
|
|
50
118
|
```ruby
|
|
119
|
+
require 'bundleup'
|
|
120
|
+
|
|
51
121
|
# Initialize with API key
|
|
52
122
|
client = Bundleup::Client.new('your_api_key_here')
|
|
53
123
|
|
|
@@ -55,241 +125,958 @@ client = Bundleup::Client.new('your_api_key_here')
|
|
|
55
125
|
client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
|
|
56
126
|
```
|
|
57
127
|
|
|
58
|
-
|
|
128
|
+
### Security Best Practices
|
|
129
|
+
|
|
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
|
+
```
|
|
59
166
|
|
|
60
|
-
##
|
|
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
|
|
61
182
|
|
|
62
183
|
### Connections
|
|
63
184
|
|
|
64
|
-
Manage your integration connections
|
|
185
|
+
Manage your integration connections.
|
|
186
|
+
|
|
187
|
+
#### List Connections
|
|
188
|
+
|
|
189
|
+
Retrieve a list of all connections in your account.
|
|
65
190
|
|
|
66
191
|
```ruby
|
|
67
|
-
# List all connections
|
|
68
192
|
connections = client.connections.list
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**With query parameters:**
|
|
196
|
+
|
|
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
|
+
```
|
|
205
|
+
|
|
206
|
+
**Query Parameters:**
|
|
69
207
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
213
|
|
|
73
|
-
|
|
74
|
-
connection = client.connections.retrieve('conn_123')
|
|
214
|
+
**Response:**
|
|
75
215
|
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
]
|
|
78
230
|
```
|
|
79
231
|
|
|
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')
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Note:** Deleting a connection will revoke access to the integration and cannot be undone.
|
|
264
|
+
|
|
80
265
|
### Integrations
|
|
81
266
|
|
|
82
|
-
|
|
267
|
+
Discover and work with available integrations.
|
|
268
|
+
|
|
269
|
+
#### List Integrations
|
|
270
|
+
|
|
271
|
+
Get a list of all available integrations.
|
|
83
272
|
|
|
84
273
|
```ruby
|
|
85
|
-
# List all integrations
|
|
86
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:**
|
|
288
|
+
|
|
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
|
+
```
|
|
87
316
|
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
}
|
|
90
328
|
```
|
|
91
329
|
|
|
92
330
|
### Webhooks
|
|
93
331
|
|
|
94
|
-
Manage webhook subscriptions
|
|
332
|
+
Manage webhook subscriptions for real-time event notifications.
|
|
333
|
+
|
|
334
|
+
#### List Webhooks
|
|
335
|
+
|
|
336
|
+
Get all registered webhooks.
|
|
95
337
|
|
|
96
338
|
```ruby
|
|
97
|
-
# List all webhooks
|
|
98
339
|
webhooks = client.webhooks.list
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**With pagination:**
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
webhooks = client.webhooks.list(
|
|
346
|
+
limit: 50,
|
|
347
|
+
offset: 0
|
|
348
|
+
)
|
|
349
|
+
```
|
|
99
350
|
|
|
100
|
-
|
|
101
|
-
|
|
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',
|
|
102
377
|
url: 'https://example.com/webhook',
|
|
103
|
-
events:
|
|
104
|
-
|
|
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:**
|
|
393
|
+
|
|
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
|
+
```
|
|
105
414
|
|
|
106
|
-
|
|
415
|
+
#### Retrieve a Webhook
|
|
416
|
+
|
|
417
|
+
Get details of a specific webhook.
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
107
420
|
webhook = client.webhooks.retrieve('webhook_123')
|
|
421
|
+
```
|
|
108
422
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
439
|
+
|
|
440
|
+
Remove a webhook subscription.
|
|
113
441
|
|
|
114
|
-
|
|
442
|
+
```ruby
|
|
115
443
|
client.webhooks.delete('webhook_123')
|
|
116
444
|
```
|
|
117
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
|
+
|
|
118
509
|
### Proxy API
|
|
119
510
|
|
|
120
|
-
Make direct
|
|
511
|
+
Make direct HTTP requests to integration APIs through BundleUp.
|
|
512
|
+
|
|
513
|
+
#### Creating a Proxy Instance
|
|
121
514
|
|
|
122
515
|
```ruby
|
|
123
|
-
|
|
124
|
-
|
|
516
|
+
proxy = client.proxy('conn_123abc')
|
|
517
|
+
```
|
|
125
518
|
|
|
126
|
-
|
|
127
|
-
users = proxy.get('/api/users')
|
|
519
|
+
#### GET Request
|
|
128
520
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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'
|
|
133
533
|
})
|
|
534
|
+
```
|
|
134
535
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
536
|
+
#### POST Request
|
|
537
|
+
|
|
538
|
+
```ruby
|
|
539
|
+
response = proxy.post('/api/users', body: {
|
|
540
|
+
name: 'John Doe',
|
|
541
|
+
email: 'john@example.com',
|
|
542
|
+
role: 'developer'
|
|
138
543
|
})
|
|
139
544
|
|
|
140
|
-
|
|
141
|
-
|
|
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',
|
|
142
567
|
email: 'jane@example.com'
|
|
143
568
|
})
|
|
144
569
|
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
618
|
```
|
|
148
619
|
|
|
149
620
|
### Unify API
|
|
150
621
|
|
|
151
|
-
Access unified, normalized data across different integrations
|
|
622
|
+
Access unified, normalized data across different integrations with a consistent interface.
|
|
152
623
|
|
|
153
|
-
####
|
|
624
|
+
#### Creating a Unify Instance
|
|
154
625
|
|
|
155
626
|
```ruby
|
|
156
|
-
|
|
157
|
-
|
|
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.
|
|
158
633
|
|
|
159
|
-
|
|
160
|
-
channels = unify[:chat].channels(limit: 100)
|
|
634
|
+
##### List Channels
|
|
161
635
|
|
|
162
|
-
|
|
163
|
-
channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
|
|
636
|
+
Retrieve a list of channels from the connected chat platform.
|
|
164
637
|
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
676
|
```
|
|
168
677
|
|
|
169
|
-
|
|
678
|
+
**Pagination example:**
|
|
170
679
|
|
|
171
680
|
```ruby
|
|
172
|
-
|
|
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
|
|
173
690
|
|
|
174
|
-
#
|
|
175
|
-
|
|
691
|
+
puts "Fetched #{all_channels.length} total channels"
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Git API
|
|
176
695
|
|
|
177
|
-
|
|
178
|
-
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.
|
|
179
697
|
|
|
180
|
-
|
|
181
|
-
tags = unify[:git].tags('owner/repo')
|
|
698
|
+
##### List Repositories
|
|
182
699
|
|
|
183
|
-
|
|
184
|
-
|
|
700
|
+
```ruby
|
|
701
|
+
result = unify.git.repos(
|
|
702
|
+
limit: 50,
|
|
703
|
+
after: nil,
|
|
704
|
+
include_raw: false
|
|
705
|
+
)
|
|
185
706
|
|
|
186
|
-
|
|
187
|
-
repos = unify[:git].repos(include_raw: true)
|
|
707
|
+
puts "Repositories: #{result['data']}"
|
|
188
708
|
```
|
|
189
709
|
|
|
190
|
-
|
|
710
|
+
**Response:**
|
|
191
711
|
|
|
192
712
|
```ruby
|
|
193
|
-
|
|
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
|
+
```
|
|
194
731
|
|
|
195
|
-
|
|
196
|
-
issues = unify[:pm].issues(limit: 100)
|
|
732
|
+
##### List Pull Requests
|
|
197
733
|
|
|
198
|
-
|
|
199
|
-
|
|
734
|
+
```ruby
|
|
735
|
+
result = unify.git.pulls('organization/repo-name',
|
|
736
|
+
limit: 20,
|
|
737
|
+
after: nil,
|
|
738
|
+
include_raw: false
|
|
739
|
+
)
|
|
200
740
|
|
|
201
|
-
|
|
202
|
-
issues = unify[:pm].issues(include_raw: true)
|
|
741
|
+
puts "Pull Requests: #{result['data']}"
|
|
203
742
|
```
|
|
204
743
|
|
|
205
|
-
|
|
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
|
|
206
750
|
|
|
207
|
-
|
|
751
|
+
**Response:**
|
|
208
752
|
|
|
209
753
|
```ruby
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
##### List Tags
|
|
777
|
+
|
|
778
|
+
```ruby
|
|
779
|
+
result = unify.git.tags('organization/repo-name', limit: 50)
|
|
780
|
+
|
|
781
|
+
puts "Tags: #{result['data']}"
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
**Response:**
|
|
785
|
+
|
|
786
|
+
```ruby
|
|
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
|
+
}
|
|
222
802
|
```
|
|
223
803
|
|
|
224
|
-
|
|
804
|
+
##### List Releases
|
|
225
805
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- `Bundleup::AuthenticationError` - Raised when authentication fails
|
|
229
|
-
- `Bundleup::InvalidRequestError` - Raised when a request is invalid
|
|
806
|
+
```ruby
|
|
807
|
+
result = unify.git.releases('organization/repo-name', limit: 10)
|
|
230
808
|
|
|
231
|
-
|
|
809
|
+
puts "Releases: #{result['data']}"
|
|
810
|
+
```
|
|
232
811
|
|
|
233
|
-
|
|
812
|
+
**Response:**
|
|
234
813
|
|
|
235
814
|
```ruby
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
}
|
|
241
832
|
```
|
|
242
833
|
|
|
243
|
-
|
|
834
|
+
#### Project Management API
|
|
835
|
+
|
|
836
|
+
The PM API provides a unified interface for project management platforms like Jira, Linear, and Asana.
|
|
244
837
|
|
|
245
|
-
|
|
838
|
+
##### List Issues
|
|
246
839
|
|
|
247
840
|
```ruby
|
|
248
|
-
|
|
249
|
-
|
|
841
|
+
result = unify.pm.issues(
|
|
842
|
+
limit: 100,
|
|
843
|
+
after: nil,
|
|
844
|
+
include_raw: false
|
|
845
|
+
)
|
|
250
846
|
|
|
251
|
-
|
|
252
|
-
channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
|
|
847
|
+
puts "Issues: #{result['data']}"
|
|
253
848
|
```
|
|
254
849
|
|
|
255
|
-
|
|
850
|
+
**Response:**
|
|
256
851
|
|
|
257
|
-
|
|
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
|
+
```
|
|
258
870
|
|
|
259
|
-
|
|
871
|
+
**Filtering and sorting:**
|
|
260
872
|
|
|
261
|
-
```
|
|
262
|
-
|
|
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
|
|
263
888
|
```
|
|
264
889
|
|
|
265
|
-
|
|
890
|
+
## Development
|
|
891
|
+
|
|
892
|
+
### Setting Up Development Environment
|
|
266
893
|
|
|
267
894
|
```bash
|
|
268
|
-
|
|
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
|
|
269
910
|
```
|
|
270
911
|
|
|
271
|
-
|
|
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
|
+
```
|
|
272
934
|
|
|
273
|
-
|
|
935
|
+
### Running Tests
|
|
274
936
|
|
|
275
937
|
```bash
|
|
938
|
+
# Run all tests
|
|
276
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
|
|
277
975
|
```
|
|
278
976
|
|
|
279
977
|
## Contributing
|
|
280
978
|
|
|
281
|
-
|
|
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
|
|
282
1016
|
|
|
283
1017
|
## License
|
|
284
1018
|
|
|
285
|
-
|
|
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
|
+
```
|
|
286
1042
|
|
|
287
1043
|
## Support
|
|
288
1044
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
292
1075
|
|
|
293
1076
|
## Code of Conduct
|
|
294
1077
|
|
|
295
|
-
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
|