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.
data/README.md CHANGED
@@ -3,10 +3,34 @@
3
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
@@ -19,7 +43,7 @@ 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
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
- - 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
- require 'bundleup-sdk'
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"
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
- **Security Best Practice:** Never commit your API keys to version control. Use environment variables or a secure credential management system.
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
- ## Usage
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
- # List with pagination
71
- connections = client.connections.list(limit: 10, page: 1)
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
- # Retrieve a specific connection
74
- connection = client.connections.retrieve('conn_123')
214
+ **Response:**
75
215
 
76
- # Delete a connection
77
- client.connections.delete('conn_123')
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
- Work with available integrations:
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
- # Retrieve a specific integration
89
- integration = client.integrations.retrieve('int_123')
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
- # Create a webhook
101
- webhook = client.webhooks.create({
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: ['connection.created', 'connection.deleted']
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
- # Retrieve a webhook
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
- # Update a webhook
110
- updated = client.webhooks.update('webhook_123', {
111
- url: 'https://example.com/new-webhook'
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
- # Delete a webhook
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 calls to the underlying integration APIs:
511
+ Make direct HTTP requests to integration APIs through BundleUp.
512
+
513
+ #### Creating a Proxy Instance
121
514
 
122
515
  ```ruby
123
- # Initialize proxy for a connection
124
- proxy = client.proxy('conn_123')
516
+ proxy = client.proxy('conn_123abc')
517
+ ```
125
518
 
126
- # Make GET request
127
- users = proxy.get('/api/users')
519
+ #### GET Request
128
520
 
129
- # Make POST request
130
- new_user = proxy.post('/api/users', {
131
- name: 'John Doe',
132
- 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'
133
533
  })
534
+ ```
134
535
 
135
- # Make PUT request
136
- updated_user = proxy.put('/api/users/123', {
137
- name: 'Jane Doe'
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
- # Make PATCH request
141
- 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',
142
567
  email: 'jane@example.com'
143
568
  })
144
569
 
145
- # Make DELETE request
146
- 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
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
- #### Chat (Slack, Discord, Microsoft Teams, etc.)
624
+ #### Creating a Unify Instance
154
625
 
155
626
  ```ruby
156
- # Get unified API instances for a connection
157
- 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.
158
633
 
159
- # List channels
160
- channels = unify[:chat].channels(limit: 100)
634
+ ##### List Channels
161
635
 
162
- # List channels with pagination
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
- # Include raw response from the integration
166
- 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
+ }
167
676
  ```
168
677
 
169
- #### Git (GitHub, GitLab, Bitbucket, etc.)
678
+ **Pagination example:**
170
679
 
171
680
  ```ruby
172
- 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
173
690
 
174
- # List repositories
175
- repos = unify[:git].repos(limit: 50)
691
+ puts "Fetched #{all_channels.length} total channels"
692
+ ```
693
+
694
+ #### Git API
176
695
 
177
- # List pull requests for a repository
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
- # List tags for a repository
181
- tags = unify[:git].tags('owner/repo')
698
+ ##### List Repositories
182
699
 
183
- # List releases for a repository
184
- 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
+ )
185
706
 
186
- # Include raw response
187
- repos = unify[:git].repos(include_raw: true)
707
+ puts "Repositories: #{result['data']}"
188
708
  ```
189
709
 
190
- #### Project Management (Jira, Linear, Asana, etc.)
710
+ **Response:**
191
711
 
192
712
  ```ruby
193
- 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
+ ```
194
731
 
195
- # List issues
196
- issues = unify[:pm].issues(limit: 100)
732
+ ##### List Pull Requests
197
733
 
198
- # List with pagination
199
- 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
+ )
200
740
 
201
- # Include raw response
202
- issues = unify[:pm].issues(include_raw: true)
741
+ puts "Pull Requests: #{result['data']}"
203
742
  ```
204
743
 
205
- ## 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
206
750
 
207
- The SDK provides custom exception classes for different error scenarios:
751
+ **Response:**
208
752
 
209
753
  ```ruby
210
- begin
211
- client = Bundleup::Client.new('invalid_key')
212
- connections = client.connections.list
213
- rescue Bundleup::AuthenticationError => e
214
- puts "Authentication failed: #{e.message}"
215
- rescue Bundleup::InvalidRequestError => e
216
- puts "Invalid request: #{e.message}"
217
- rescue Bundleup::APIError => e
218
- puts "API error: #{e.message}"
219
- rescue Bundleup::Error => e
220
- puts "General error: #{e.message}"
221
- 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
+ }
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
- ### Exception Classes
804
+ ##### List Releases
225
805
 
226
- - `Bundleup::Error` - Base class for all BundleUp errors
227
- - `Bundleup::APIError` - Raised when an API error occurs
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
- ## Advanced Usage
809
+ puts "Releases: #{result['data']}"
810
+ ```
232
811
 
233
- ### Custom Connection Configuration
812
+ **Response:**
234
813
 
235
814
  ```ruby
236
- # The SDK uses Faraday under the hood with automatic retries
237
- # Retry logic is configured with:
238
- # - Max retries: 3
239
- # - Initial interval: 0.5 seconds
240
- # - Backoff factor: 2
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
- ### Pagination
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
- Most list endpoints support pagination:
838
+ ##### List Issues
246
839
 
247
840
  ```ruby
248
- # Using limit and page
249
- connections = client.connections.list(limit: 10, page: 1)
841
+ result = unify.pm.issues(
842
+ limit: 100,
843
+ after: nil,
844
+ include_raw: false
845
+ )
250
846
 
251
- # Using cursor-based pagination (for Unify API)
252
- channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
847
+ puts "Issues: #{result['data']}"
253
848
  ```
254
849
 
255
- ## Development
850
+ **Response:**
256
851
 
257
- 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
+ ```
258
870
 
259
- To install this gem onto your local machine, run:
871
+ **Filtering and sorting:**
260
872
 
261
- ```bash
262
- 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
263
888
  ```
264
889
 
265
- 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
266
893
 
267
894
  ```bash
268
- 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
269
910
  ```
270
911
 
271
- ## 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
+ ```
272
934
 
273
- Run the test suite:
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
- 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
282
1016
 
283
1017
  ## License
284
1018
 
285
- 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
+ ```
286
1042
 
287
1043
  ## Support
288
1044
 
289
- - Documentation: [https://docs.bundleup.io](https://docs.bundleup.io)
290
- - Email: [support@bundleup.io](mailto:support@bundleup.io)
291
- - 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.
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.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