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.
data/README.md CHANGED
@@ -1,16 +1,40 @@
1
1
  # BundleUp Ruby SDK
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/bundleup.svg)](https://badge.fury.io/rb/bundleup)
3
+ [![Gem Version](https://badge.fury.io/rb/bundleup-sdk.svg)](https://badge.fury.io/rb/bundleup-sdk)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Official Ruby client library for the [BundleUp](https://bundleup.io) API. Connect to 100+ integrations with a single, unified API.
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
- Or install it yourself as:
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
- - Bundler
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 with your API key
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
- # Create a new connection
46
- new_connection = client.connections.create({
47
- name: 'My Connection',
48
- integration_id: 'integration_123'
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
- **Security Best Practice:** Never commit your API keys to version control. Use environment variables or a secure credential management system.
128
+ ### Security Best Practices
65
129
 
66
- ## Usage
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
- # List with pagination
77
- connections = client.connections.list(limit: 10, page: 1)
195
+ **With query parameters:**
78
196
 
79
- # Retrieve a specific connection
80
- connection = client.connections.retrieve('conn_123')
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
- # Create a new connection
83
- connection = client.connections.create({
84
- name: 'GitHub Connection',
85
- integration_id: 'int_github'
86
- })
206
+ **Query Parameters:**
87
207
 
88
- # Update a connection
89
- updated = client.connections.update('conn_123', {
90
- name: 'Updated GitHub Connection'
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
- # Delete a connection
94
- client.connections.delete('conn_123')
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
- Work with available integrations:
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
- # Retrieve a specific integration
106
- integration = client.integrations.retrieve('int_123')
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
- # Create a webhook
118
- webhook = client.webhooks.create({
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: ['connection.created', 'connection.deleted']
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
- # Retrieve a webhook
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
- # Update a webhook
127
- updated = client.webhooks.update('webhook_123', {
128
- url: 'https://example.com/new-webhook'
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
- # Delete a webhook
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 calls to the underlying integration APIs:
511
+ Make direct HTTP requests to integration APIs through BundleUp.
512
+
513
+ #### Creating a Proxy Instance
138
514
 
139
515
  ```ruby
140
- # Initialize proxy for a connection
141
- proxy = client.proxy('conn_123')
516
+ proxy = client.proxy('conn_123abc')
517
+ ```
142
518
 
143
- # Make GET request
144
- users = proxy.get('/api/users')
519
+ #### GET Request
145
520
 
146
- # Make POST request
147
- new_user = proxy.post('/api/users', {
148
- name: 'John Doe',
149
- email: 'john@example.com'
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
- # Make PUT request
153
- updated_user = proxy.put('/api/users/123', {
154
- name: 'Jane Doe'
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
- # Make PATCH request
158
- patched_user = proxy.patch('/api/users/123', {
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
- # Make DELETE request
163
- proxy.delete('/api/users/123')
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
- #### Chat (Slack, Discord, Microsoft Teams, etc.)
624
+ #### Creating a Unify Instance
171
625
 
172
626
  ```ruby
173
- # Get unified API instances for a connection
174
- unify = client.unify('conn_123')
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
- # List channels
177
- channels = unify[:chat].channels(limit: 100)
634
+ ##### List Channels
178
635
 
179
- # List channels with pagination
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
- # Include raw response from the integration
183
- channels = unify[:chat].channels(limit: 100, include_raw: true)
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
- #### Git (GitHub, GitLab, Bitbucket, etc.)
678
+ **Pagination example:**
187
679
 
188
680
  ```ruby
189
- unify = client.unify('conn_123')
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
- # List repositories
192
- repos = unify[:git].repos(limit: 50)
691
+ puts "Fetched #{all_channels.length} total channels"
692
+ ```
693
+
694
+ #### Git API
193
695
 
194
- # List pull requests for a repository
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
- # List tags for a repository
198
- tags = unify[:git].tags('owner/repo')
698
+ ##### List Repositories
199
699
 
200
- # List releases for a repository
201
- releases = unify[:git].releases('owner/repo', limit: 10)
700
+ ```ruby
701
+ result = unify.git.repos(
702
+ limit: 50,
703
+ after: nil,
704
+ include_raw: false
705
+ )
202
706
 
203
- # Include raw response
204
- repos = unify[:git].repos(include_raw: true)
707
+ puts "Repositories: #{result['data']}"
205
708
  ```
206
709
 
207
- #### Project Management (Jira, Linear, Asana, etc.)
710
+ **Response:**
208
711
 
209
712
  ```ruby
210
- unify = client.unify('conn_123')
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
- # List issues
213
- issues = unify[:pm].issues(limit: 100)
732
+ ##### List Pull Requests
214
733
 
215
- # List with pagination
216
- issues = unify[:pm].issues(limit: 50, cursor: 'next_page_token')
734
+ ```ruby
735
+ result = unify.git.pulls('organization/repo-name',
736
+ limit: 20,
737
+ after: nil,
738
+ include_raw: false
739
+ )
217
740
 
218
- # Include raw response
219
- issues = unify[:pm].issues(include_raw: true)
741
+ puts "Pull Requests: #{result['data']}"
220
742
  ```
221
743
 
222
- ## Error Handling
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
- The SDK provides custom exception classes for different error scenarios:
751
+ **Response:**
225
752
 
226
753
  ```ruby
227
- begin
228
- client = Bundleup::Client.new('invalid_key')
229
- connections = client.connections.list
230
- rescue Bundleup::AuthenticationError => e
231
- puts "Authentication failed: #{e.message}"
232
- rescue Bundleup::InvalidRequestError => e
233
- puts "Invalid request: #{e.message}"
234
- rescue Bundleup::APIError => e
235
- puts "API error: #{e.message}"
236
- rescue Bundleup::Error => e
237
- puts "General error: #{e.message}"
238
- end
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
- ### Exception Classes
776
+ ##### List Tags
242
777
 
243
- - `Bundleup::Error` - Base class for all BundleUp errors
244
- - `Bundleup::APIError` - Raised when an API error occurs
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
- ## Advanced Usage
781
+ puts "Tags: #{result['data']}"
782
+ ```
249
783
 
250
- ### Custom Connection Configuration
784
+ **Response:**
251
785
 
252
786
  ```ruby
253
- # The SDK uses Faraday under the hood with automatic retries
254
- # Retry logic is configured with:
255
- # - Max retries: 3
256
- # - Initial interval: 0.5 seconds
257
- # - Backoff factor: 2
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
- ### Pagination
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
- Most list endpoints support pagination:
812
+ **Response:**
263
813
 
264
814
  ```ruby
265
- # Using limit and page
266
- connections = client.connections.list(limit: 10, page: 1)
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
- # Using cursor-based pagination (for Unify API)
269
- channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
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
- ## Development
850
+ **Response:**
273
851
 
274
- After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
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
- To install this gem onto your local machine, run:
871
+ **Filtering and sorting:**
277
872
 
278
- ```bash
279
- bundle exec rake install
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
- To release a new version, update the version number in `lib/bundleup/version.rb`, and then run:
890
+ ## Development
891
+
892
+ ### Setting Up Development Environment
283
893
 
284
894
  ```bash
285
- bundle exec rake release
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
- ## Testing
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
- Run the test suite:
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/bundleup/bundleup-sdk-ruby.
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
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
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
- - Documentation: [https://docs.bundleup.io](https://docs.bundleup.io)
307
- - Email: [support@bundleup.io](mailto:support@bundleup.io)
308
- - GitHub Issues: [https://github.com/bundleup/bundleup-sdk-ruby/issues](https://github.com/bundleup/bundleup-sdk-ruby/issues)
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.md).
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