accessgrid 0.1.3 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f95e77022beec6a20cd5a55350ff8cd5ad15233ec74d13b22d49587d8fafed9
4
- data.tar.gz: 9c20b6923726a61c5865b9b58c7f55fe008e7756e0d99f800e2a6efaca3e18aa
3
+ metadata.gz: 05f4bc4f464d21efa501041653ae1252c64f6d5cca1a29b30394b51ea0cdd486
4
+ data.tar.gz: e26bba2fb9ccb37ecfaa5f32676453c9b5a5369b49d11f6cc2385a26345eead6
5
5
  SHA512:
6
- metadata.gz: 5d48b2324e989be2ecb79c152b27cda9aae991990aa3de176cf080aee89ffed78c262fd06116fdfa3eb65da719031554a03065e7c6862d5c27c9329bc6104020
7
- data.tar.gz: 9d5585bc8bccb446f0432fa6116d30ddfbac4a358638c5a3b9ee31e7f0879dfe3a55e46cde0793a282c8abc10cec12d0689ceb9a9a09ca75ea53193afce7eceb
6
+ metadata.gz: 181d498ca94e2514ecf4c5296acf2b4cb5d975efbd77cfa0915280dcc610b7860740cb58ad1d3909f5ca137a30ca610347e10de6d85608c72fbe022e31923eb4
7
+ data.tar.gz: d9ccd5c9bf41ba8f68415ea1ad9f72b68b48fa2799f76ce53726cd3fca93466a56e38fe6320b907c57cb67dbb7f8aac06674cd7cba7ac97a31ab44fb32d6ea11
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # AccessGrid SDK
1
+ # ![AccessGrid Logo](accessgrid.png)
2
2
 
3
- A Ruby SDK for interacting with the [AccessGrid.com](https://www.accessgrid.com) API. This SDK provides a simple interface for managing NFC key cards and enterprise templates. Full docs at https://www.accessgrid.com/docs
3
+ AccessGrid is a Ruby SDK for interacting with the [AccessGrid.com](https://www.accessgrid.com) API. This SDK provides a simple interface for managing NFC key cards and enterprise templates. Full docs at https://www.accessgrid.com/docs
4
4
 
5
5
  ## Installation
6
6
 
@@ -43,15 +43,25 @@ client = AccessGrid.new(account_id, secret_key)
43
43
  card = client.access_cards.issue(
44
44
  card_template_id: card_template_id,
45
45
  employee_id: "123456789",
46
- card_number: "16187",
47
46
  tag_id: "DDEADB33FB00B5",
48
47
  full_name: "Employee name",
49
48
  email: "employee@yourwebsite.com",
50
49
  phone_number: "+19547212241",
51
50
  classification: "full_time",
52
- start_date: "2025-01-31T22:46:25.601Z",
53
- expiration_date: "2025-04-30T22:46:25.601Z",
54
- employee_photo: "[image_in_base64_encoded_format]"
51
+ department: "Engineering",
52
+ location: "San Francisco",
53
+ site_name: "HQ Building A",
54
+ workstation: "4F-207",
55
+ mail_stop: "MS-401",
56
+ company_address: "123 Main St, San Francisco, CA 94105",
57
+ start_date: Time.now.utc.iso8601(3),
58
+ expiration_date: 3.months.from_now.utc.iso8601(3),
59
+ employee_photo: "[image_in_base64_encoded_format]",
60
+ title: "Engineering Manager",
61
+ metadata: {
62
+ "department": "engineering",
63
+ "badge_type": "contractor"
64
+ }
55
65
  )
56
66
 
57
67
  # Provision is an alias for issue (for backwards compatibility)
@@ -64,6 +74,22 @@ card = client.access_cards.provision(
64
74
  puts "Install URL: #{card.url}"
65
75
  ```
66
76
 
77
+ #### Get a card
78
+
79
+ ```ruby
80
+ card = client.access_cards.get(card_id: "0xc4rd1d")
81
+
82
+ puts "Card ID: #{card.id}"
83
+ puts "State: #{card.state}"
84
+ puts "Full Name: #{card.full_name}"
85
+ puts "Install URL: #{card.install_url}"
86
+ puts "Expiration Date: #{card.expiration_date}"
87
+ puts "Card Number: #{card.card_number}"
88
+ puts "Site Code: #{card.site_code}"
89
+ puts "Devices: #{card.devices.length}"
90
+ puts "Metadata: #{card.metadata}"
91
+ ```
92
+
67
93
  #### Update a card
68
94
 
69
95
  ```ruby
@@ -111,27 +137,24 @@ client.access_cards.delete("0xc4rd1d")
111
137
 
112
138
  ```ruby
113
139
  template = client.console.create_template(
114
- name: "Employee NFC key",
140
+ name: "Employee Access Pass",
115
141
  platform: "apple",
116
142
  use_case: "employee_badge",
117
143
  protocol: "desfire",
118
144
  allow_on_multiple_devices: true,
119
145
  watch_count: 2,
120
146
  iphone_count: 3,
121
- design: {
122
- background_color: "#FFFFFF",
123
- label_color: "#000000",
124
- label_secondary_color: "#333333",
125
- background_image: "[image_in_base64_encoded_format]",
126
- logo_image: "[image_in_base64_encoded_format]",
127
- icon_image: "[image_in_base64_encoded_format]"
128
- },
129
- support_info: {
130
- support_url: "https://help.yourcompany.com",
131
- support_phone_number: "+1-555-123-4567",
132
- support_email: "support@yourcompany.com",
133
- privacy_policy_url: "https://yourcompany.com/privacy",
134
- terms_and_conditions_url: "https://yourcompany.com/terms"
147
+ background_color: "#FFFFFF",
148
+ label_color: "#000000",
149
+ label_secondary_color: "#333333",
150
+ support_url: "https://help.yourcompany.com",
151
+ support_phone_number: "+1-555-123-4567",
152
+ support_email: "support@yourcompany.com",
153
+ privacy_policy_url: "https://yourcompany.com/privacy",
154
+ terms_and_conditions_url: "https://yourcompany.com/terms",
155
+ metadata: {
156
+ version: "2.1",
157
+ approval_status: "approved"
135
158
  }
136
159
  )
137
160
  ```
@@ -143,7 +166,6 @@ template = client.console.update_template(
143
166
  "0xd3adb00b5",
144
167
  {
145
168
  name: "Updated Employee NFC key",
146
- allow_on_multiple_devices: true,
147
169
  watch_count: 2,
148
170
  iphone_count: 3,
149
171
  support_info: {
@@ -189,6 +211,199 @@ events = client.console.event_log(
189
211
  )
190
212
  ```
191
213
 
214
+ #### List pass template pairs
215
+
216
+ ```ruby
217
+ response = client.console.list_pass_template_pairs(page: 1, per_page: 20)
218
+
219
+ response['card_template_pairs'].each do |pair|
220
+ puts "#{pair.name} (#{pair.id})"
221
+ puts " iOS: #{pair.ios_template&.name}"
222
+ puts " Android: #{pair.android_template&.name}"
223
+ end
224
+
225
+ puts response['pagination'] # { "current_page" => 1, "total_pages" => 5, ... }
226
+ ```
227
+
228
+ #### List ledger items
229
+
230
+ ```ruby
231
+ response = client.console.list_ledger_items(
232
+ page: 1,
233
+ per_page: 50,
234
+ start_date: '2025-01-01T00:00:00Z',
235
+ end_date: '2025-06-30T23:59:59Z'
236
+ )
237
+
238
+ response['ledger_items'].each do |item|
239
+ puts "#{item.kind}: #{item.amount} (#{item.created_at})"
240
+
241
+ if item.access_pass
242
+ puts " Pass: #{item.access_pass.full_name} (#{item.access_pass.state})"
243
+ if item.access_pass.pass_template
244
+ puts " Template: #{item.access_pass.pass_template.name}"
245
+ end
246
+ end
247
+ end
248
+
249
+ puts response['pagination'] # { "current_page" => 1, "total_pages" => 3, ... }
250
+ ```
251
+
252
+ #### iOS In-App Provisioning Preflight
253
+
254
+ ```ruby
255
+ response = client.console.ios_preflight(
256
+ card_template_id: "0xt3mp14t3-3x1d",
257
+ access_pass_ex_id: "0xp455-3x1d"
258
+ )
259
+
260
+ puts "Provisioning Credential ID: #{response.provisioning_credential_identifier}"
261
+ puts "Sharing Instance ID: #{response.sharing_instance_identifier}"
262
+ puts "Card Template ID: #{response.card_template_identifier}"
263
+ puts "Environment ID: #{response.environment_identifier}"
264
+ ```
265
+
266
+ ### Webhooks
267
+
268
+ #### Create a webhook
269
+
270
+ ```ruby
271
+ webhook = client.console.webhooks.create(
272
+ name: 'Production',
273
+ url: 'https://example.com/webhooks',
274
+ subscribed_events: ['ag.access_pass.issued']
275
+ )
276
+
277
+ puts "Webhook created: #{webhook.id}"
278
+ puts "Private key: #{webhook.private_key}"
279
+ ```
280
+
281
+ #### List webhooks
282
+
283
+ ```ruby
284
+ webhooks = client.console.webhooks.list
285
+
286
+ webhooks.each do |webhook|
287
+ puts "ID: #{webhook.id}, Name: #{webhook.name}"
288
+ end
289
+ ```
290
+
291
+ #### Delete a webhook
292
+
293
+ ```ruby
294
+ client.console.webhooks.delete('abc123')
295
+ ```
296
+
297
+ ### HID Organizations
298
+
299
+ #### Create an HID org
300
+
301
+ ```ruby
302
+ org = client.console.hid.orgs.create(
303
+ name: 'My Org',
304
+ full_address: '1 Main St, NY NY',
305
+ phone: '+1-555-0000',
306
+ first_name: 'Ada',
307
+ last_name: 'Lovelace'
308
+ )
309
+
310
+ puts "Created org: #{org.name} (ID: #{org.id})"
311
+ puts "Slug: #{org.slug}"
312
+ ```
313
+
314
+ #### List HID orgs
315
+
316
+ ```ruby
317
+ orgs = client.console.hid.orgs.list
318
+
319
+ orgs.each do |org|
320
+ puts "Org ID: #{org.id}, Name: #{org.name}, Slug: #{org.slug}"
321
+ end
322
+ ```
323
+
324
+ #### Activate an HID org
325
+
326
+ ```ruby
327
+ result = client.console.hid.orgs.activate(
328
+ email: 'admin@example.com',
329
+ password: 'hid-password-123'
330
+ )
331
+
332
+ puts "Completed registration for org: #{result.name}"
333
+ puts "Status: #{result.status}"
334
+ ```
335
+
336
+ ### Landing Pages
337
+
338
+ #### List landing pages
339
+
340
+ ```ruby
341
+ landing_pages = client.console.list_landing_pages
342
+
343
+ landing_pages.each do |page|
344
+ puts "ID: #{page.id}, Name: #{page.name}, Kind: #{page.kind}"
345
+ puts " Password Protected: #{page.password_protected}"
346
+ puts " Logo URL: #{page.logo_url}" if page.logo_url
347
+ end
348
+ ```
349
+
350
+ #### Create a landing page
351
+
352
+ ```ruby
353
+ landing_page = client.console.create_landing_page(
354
+ name: "Miami Office Access Pass",
355
+ kind: "universal",
356
+ additional_text: "Welcome to the Miami Office",
357
+ bg_color: "#f1f5f9",
358
+ allow_immediate_download: true
359
+ )
360
+
361
+ puts "Landing page created: #{landing_page.id}"
362
+ puts "Name: #{landing_page.name}, Kind: #{landing_page.kind}"
363
+ ```
364
+
365
+ #### Update a landing page
366
+
367
+ ```ruby
368
+ landing_page = client.console.update_landing_page(
369
+ landing_page_id: "0xlandingpage1d",
370
+ name: "Updated Miami Office Access Pass",
371
+ additional_text: "Welcome! Tap below to get your access pass.",
372
+ bg_color: "#e2e8f0"
373
+ )
374
+
375
+ puts "Landing page updated: #{landing_page.id}"
376
+ puts "Name: #{landing_page.name}"
377
+ ```
378
+
379
+ ### Credential Profiles
380
+
381
+ #### List credential profiles
382
+
383
+ ```ruby
384
+ profiles = client.console.credential_profiles.list
385
+
386
+ profiles.each do |profile|
387
+ puts "ID: #{profile.id}, Name: #{profile.name}, AID: #{profile.aid}"
388
+ end
389
+ ```
390
+
391
+ #### Create a credential profile
392
+
393
+ ```ruby
394
+ profile = client.console.credential_profiles.create(
395
+ name: 'Main Office Profile',
396
+ app_name: 'KEY-ID-main',
397
+ keys: [
398
+ { value: 'your_32_char_hex_master_key_here' },
399
+ { value: 'your_32_char_hex__read_key__here' }
400
+ ]
401
+ )
402
+
403
+ puts "Profile created: #{profile.id}"
404
+ puts "AID: #{profile.aid}"
405
+ ```
406
+
192
407
  ## Configuration
193
408
 
194
409
  The SDK can be configured with a custom API endpoint:
@@ -226,7 +441,7 @@ end
226
441
 
227
442
  ## Requirements
228
443
 
229
- - Ruby 2.6 or higher
444
+ - Ruby 2.19 or higher
230
445
 
231
446
  ## Security
232
447
 
@@ -246,6 +461,38 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
246
461
 
247
462
  Bug reports and pull requests are welcome on GitHub at https://github.com/access-grid/accessgrid-rb.
248
463
 
464
+ ## Feature Matrix
465
+
466
+ | Endpoint | Method | Supported |
467
+ |---|---|:---:|
468
+ | POST /v1/key-cards | `access_cards.issue()` | Y |
469
+ | GET /v1/key-cards/{id} | `access_cards.get()` | Y |
470
+ | PATCH /v1/key-cards/{id} | `access_cards.update()` | Y |
471
+ | GET /v1/key-cards | `access_cards.list()` | Y |
472
+ | POST /v1/key-cards/{id}/suspend | `access_cards.suspend()` | Y |
473
+ | POST /v1/key-cards/{id}/resume | `access_cards.resume()` | Y |
474
+ | POST /v1/key-cards/{id}/unlink | `access_cards.unlink()` | Y |
475
+ | POST /v1/key-cards/{id}/delete | `access_cards.delete()` | Y |
476
+ | POST /v1/console/card-templates | `console.create_template()` | Y |
477
+ | PUT /v1/console/card-templates/{id} | `console.update_template()` | Y |
478
+ | GET /v1/console/card-templates/{id} | `console.read_template()` | Y |
479
+ | GET /v1/console/card-templates/{id}/logs | `console.get_logs()` / `console.event_log()` | Y |
480
+ | GET /v1/console/card-template-pairs | `console.list_pass_template_pairs()` | Y |
481
+ | POST /v1/console/card-template-pairs | `console.create_pass_template_pair()` | Y |
482
+ | POST /v1/console/card-templates/{id}/ios_preflight | `console.ios_preflight()` | Y |
483
+ | GET /v1/console/ledger-items | `console.list_ledger_items()` / `console.ledger_items()` | Y |
484
+ | GET /v1/console/webhooks | `console.webhooks.list()` | Y |
485
+ | POST /v1/console/webhooks | `console.webhooks.create()` | Y |
486
+ | DELETE /v1/console/webhooks/{id} | `console.webhooks.delete()` | Y |
487
+ | GET /v1/console/landing-pages | `console.list_landing_pages()` | Y |
488
+ | POST /v1/console/landing-pages | `console.create_landing_page()` | Y |
489
+ | PUT /v1/console/landing-pages/{id} | `console.update_landing_page()` | Y |
490
+ | GET /v1/console/credential-profiles | `console.credential_profiles.list()` | Y |
491
+ | POST /v1/console/credential-profiles | `console.credential_profiles.create()` | Y |
492
+ | POST /v1/console/hid/orgs | `console.hid.orgs.create()` | Y |
493
+ | POST /v1/console/hid/orgs/activate | `console.hid.orgs.activate()` | Y |
494
+ | GET /v1/console/hid/orgs | `console.hid.orgs.list()` | Y |
495
+
249
496
  ## License
250
497
 
251
- The gem is available as open source under the terms of the MIT License.
498
+ The gem is available as open source under the terms of the MIT License.
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/accessgrid/access_cards.rb
2
4
  module AccessGrid
5
+ # Manages NFC key card lifecycle operations.
3
6
  class AccessCards
4
7
  def initialize(client)
5
8
  @client = client
@@ -9,32 +12,28 @@ module AccessGrid
9
12
  response = @client.make_request(:post, '/v1/key-cards', params)
10
13
  Card.new(response)
11
14
  end
12
-
15
+
13
16
  # Alias provision to issue for backward compatibility
14
17
  alias provision issue
15
18
 
19
+ def get(card_id:)
20
+ response = @client.make_request(:get, "/v1/key-cards/#{card_id}")
21
+ Card.new(response)
22
+ end
23
+
16
24
  def update(card_id, params)
17
25
  response = @client.make_request(:patch, "/v1/key-cards/#{card_id}", params)
18
26
  Card.new(response)
19
27
  end
20
-
28
+
21
29
  def list(template_id, state = nil)
22
30
  params = { template_id: template_id }
23
31
  params[:state] = state if state
24
-
32
+
25
33
  response = @client.make_request(:get, '/v1/key-cards', nil, params)
26
34
  response.fetch('keys', []).map { |item| Card.new(item) }
27
35
  end
28
36
 
29
- private def manage_state(card_id, action)
30
- response = @client.make_request(
31
- :post,
32
- "/v1/key-cards/#{card_id}/#{action}",
33
- {}
34
- )
35
- Card.new(response)
36
- end
37
-
38
37
  def suspend(card_id)
39
38
  manage_state(card_id, 'suspend')
40
39
  end
@@ -46,27 +45,56 @@ module AccessGrid
46
45
  def unlink(card_id)
47
46
  manage_state(card_id, 'unlink')
48
47
  end
49
-
48
+
50
49
  def delete(card_id)
51
50
  manage_state(card_id, 'delete')
52
51
  end
52
+
53
+ private
54
+
55
+ def manage_state(card_id, action)
56
+ response = @client.make_request(
57
+ :post,
58
+ "/v1/key-cards/#{card_id}/#{action}"
59
+ )
60
+ Card.new(response)
61
+ end
53
62
  end
54
63
 
64
+ # Represents an NFC key card with its attributes and state.
55
65
  class Card
56
- attr_reader :id, :state, :url, :full_name, :expiration_date
66
+ attr_reader :id, :state, :url, :install_url, :details, :full_name,
67
+ :expiration_date, :card_template_id, :card_number, :site_code,
68
+ :file_data, :direct_install_url, :devices, :metadata, :temporary,
69
+ :employee_id, :organization_name, :created_at
57
70
 
58
71
  def initialize(data)
59
- @id = data['id']
60
- @state = data['state']
61
- @url = data['install_url']
62
- @full_name = data['full_name']
63
- @expiration_date = data['expiration_date']
72
+ data ||= {}
73
+ install_url = data.fetch('install_url', nil)
74
+ @id = data.fetch('id', nil)
75
+ @state = data.fetch('state', nil)
76
+ @url = install_url
77
+ @install_url = install_url
78
+ @details = data.fetch('details', nil)
79
+ @full_name = data.fetch('full_name', nil)
80
+ @expiration_date = data.fetch('expiration_date', nil)
81
+ @card_template_id = data.fetch('card_template_id', nil)
82
+ @card_number = data.fetch('card_number', nil)
83
+ @site_code = data.fetch('site_code', nil)
84
+ @file_data = data.fetch('file_data', nil)
85
+ @direct_install_url = data.fetch('direct_install_url', nil)
86
+ @devices = data.fetch('devices', [])
87
+ @metadata = data.fetch('metadata', {})
88
+ @temporary = data.fetch('temporary', nil)
89
+ @employee_id = data.fetch('employee_id', nil)
90
+ @organization_name = data.fetch('organization_name', nil)
91
+ @created_at = data.fetch('created_at', nil)
64
92
  end
65
-
93
+
66
94
  def to_s
67
95
  "Card(name='#{@full_name}', id='#{@id}', state='#{@state}')"
68
96
  end
69
-
97
+
70
98
  alias inspect to_s
71
99
  end
72
- end
100
+ end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/accessgrid/console.rb
2
4
  module AccessGrid
5
+ # Manages enterprise template and logging operations.
3
6
  class Console
7
+ attr_reader :webhooks, :hid, :credential_profiles
8
+
4
9
  def initialize(client)
5
10
  @client = client
11
+ @webhooks = Webhooks.new(client)
12
+ @hid = HID.new(client)
13
+ @credential_profiles = CredentialProfiles.new(client)
6
14
  end
7
15
 
8
16
  def create_template(params)
@@ -24,15 +32,13 @@ module AccessGrid
24
32
 
25
33
  def get_logs(template_id, params = {})
26
34
  response = @client.make_request(:get, "/v1/console/card-templates/#{template_id}/logs", nil, params)
27
-
35
+
28
36
  # Return full response to match Python's behavior
29
- if response['logs']
30
- response['logs'] = response['logs'].map { |log| Event.new(log) }
31
- end
32
-
37
+ response['logs'] = response['logs'].map { |log| Event.new(log) } if response['logs']
38
+
33
39
  response
34
40
  end
35
-
41
+
36
42
  # Keep event_log for backwards compatibility
37
43
  def event_log(params)
38
44
  template_id = params.delete(:card_template_id)
@@ -40,29 +46,75 @@ module AccessGrid
40
46
  response['logs'] || []
41
47
  end
42
48
 
49
+ def list_pass_template_pairs(params = {})
50
+ response = @client.make_request(:get, '/v1/console/card-template-pairs', nil, params)
51
+
52
+ if response['card_template_pairs']
53
+ response['card_template_pairs'] = response['card_template_pairs'].map { |pair| PassTemplatePair.new(pair) }
54
+ end
55
+
56
+ response
57
+ end
58
+
59
+ def create_pass_template_pair(params)
60
+ response = @client.make_request(:post, '/v1/console/card-template-pairs', params)
61
+ PassTemplatePair.new(response)
62
+ end
63
+
64
+ def list_ledger_items(params = {})
65
+ response = @client.make_request(:get, '/v1/console/ledger-items', nil, params)
66
+
67
+ if response['ledger_items']
68
+ response['ledger_items'] = response['ledger_items'].map { |item| LedgerItem.new(item) }
69
+ end
70
+
71
+ response
72
+ end
73
+
74
+ alias ledger_items list_ledger_items
75
+
76
+ def ios_preflight(card_template_id:, access_pass_ex_id:)
77
+ data = { access_pass_ex_id: access_pass_ex_id }
78
+ response = @client.make_request(:post, "/v1/console/card-templates/#{card_template_id}/ios_preflight", data)
79
+ IosPreflight.new(response)
80
+ end
81
+
82
+ def list_landing_pages
83
+ response = @client.make_request(:get, '/v1/console/landing-pages')
84
+ pages = response.is_a?(Array) ? response : response.fetch('landing_pages', [])
85
+ pages.map { |page| LandingPage.new(page) }
86
+ end
87
+
88
+ def create_landing_page(**params)
89
+ response = @client.make_request(:post, '/v1/console/landing-pages', params)
90
+ LandingPage.new(response)
91
+ end
92
+
93
+ def update_landing_page(landing_page_id:, **params)
94
+ response = @client.make_request(:put, "/v1/console/landing-pages/#{landing_page_id}", params)
95
+ LandingPage.new(response)
96
+ end
97
+
43
98
  private
44
99
 
45
100
  def transform_template_params(params)
46
- design = params.delete(:design) || {}
47
- support_info = params.delete(:support_info) || {}
48
-
49
- params.merge(
50
- background_color: design[:background_color],
51
- label_color: design[:label_color],
52
- label_secondary_color: design[:label_secondary_color],
53
- support_url: support_info[:support_url],
54
- support_phone_number: support_info[:support_phone_number],
55
- support_email: support_info[:support_email],
56
- privacy_policy_url: support_info[:privacy_policy_url],
57
- terms_and_conditions_url: support_info[:terms_and_conditions_url]
58
- )
101
+ design = params.delete(:design)
102
+ support_info = params.delete(:support_info)
103
+
104
+ # Only merge nested keys if they were provided (backward compat)
105
+ params.merge!(design) if design
106
+ params.merge!(support_info) if support_info
107
+
108
+ params
59
109
  end
60
110
  end
61
111
 
112
+ # Represents a card template configuration.
62
113
  class Template
63
- attr_reader :id, :name, :platform, :protocol, :use_case, :created_at,
114
+ attr_reader :id, :name, :platform, :protocol, :use_case, :created_at,
64
115
  :last_published_at, :issued_keys_count, :active_keys_count,
65
- :allowed_device_counts, :support_settings, :terms_settings, :style_settings
116
+ :allowed_device_counts, :support_settings, :terms_settings, :style_settings,
117
+ :metadata
66
118
 
67
119
  def initialize(data)
68
120
  @id = data['id']
@@ -78,19 +130,251 @@ module AccessGrid
78
130
  @support_settings = data['support_settings']
79
131
  @terms_settings = data['terms_settings']
80
132
  @style_settings = data['style_settings']
133
+ @metadata = data['metadata'] || {}
81
134
  end
82
135
  end
83
136
 
137
+ # Represents a template activity log event.
84
138
  class Event
85
139
  attr_reader :type, :timestamp, :user_id, :ip_address, :user_agent, :metadata
86
140
 
87
141
  def initialize(data)
142
+ metadata = data['metadata']
88
143
  @type = data['event']
89
144
  @timestamp = data['created_at']
90
- @user_id = data['metadata']['user_id'] if data['metadata'] && data['metadata']['user_id']
145
+ @user_id = metadata['user_id'] if metadata && metadata['user_id']
91
146
  @ip_address = data['ip_address']
92
147
  @user_agent = data['user_agent']
148
+ @metadata = metadata
149
+ end
150
+ end
151
+
152
+ # Represents a paired iOS and Android template configuration.
153
+ class PassTemplatePair
154
+ attr_reader :id, :name, :created_at, :android_template, :ios_template
155
+
156
+ def initialize(data)
157
+ android_template = data['android_template']
158
+ ios_template = data['ios_template']
159
+ @id = data['id']
160
+ @name = data['name']
161
+ @created_at = data['created_at']
162
+ @android_template = android_template ? TemplateInfo.new(android_template) : nil
163
+ @ios_template = ios_template ? TemplateInfo.new(ios_template) : nil
164
+ end
165
+ end
166
+
167
+ # Minimal template info used within PassTemplatePair.
168
+ class TemplateInfo
169
+ attr_reader :id, :name, :platform
170
+
171
+ def initialize(data)
172
+ @id = data['id']
173
+ @name = data['name']
174
+ @platform = data['platform']
175
+ end
176
+ end
177
+
178
+ # Represents an iOS In-App Provisioning preflight response.
179
+ class IosPreflight
180
+ attr_reader :provisioning_credential_identifier, :sharing_instance_identifier,
181
+ :card_template_identifier, :environment_identifier
182
+
183
+ def initialize(data)
184
+ @provisioning_credential_identifier = data['provisioningCredentialIdentifier']
185
+ @sharing_instance_identifier = data['sharingInstanceIdentifier']
186
+ @card_template_identifier = data['cardTemplateIdentifier']
187
+ @environment_identifier = data['environmentIdentifier']
188
+ end
189
+ end
190
+
191
+ # Represents a billing ledger item.
192
+ class LedgerItem
193
+ attr_reader :created_at, :amount, :id, :kind, :metadata, :access_pass
194
+
195
+ def initialize(data)
196
+ @created_at = data['created_at']
197
+ @amount = data['amount']
198
+ @id = data['id']
199
+ @kind = data['kind']
200
+ @metadata = data['metadata']
201
+ @access_pass = data['access_pass'] ? LedgerItemAccessPass.new(data['access_pass']) : nil
202
+ end
203
+ end
204
+
205
+ # Represents an access pass reference within a ledger item.
206
+ class LedgerItemAccessPass
207
+ attr_reader :id, :full_name, :state, :metadata, :unified_access_pass_ex_id, :pass_template
208
+
209
+ def initialize(data)
210
+ @id = data['id']
211
+ @full_name = data['full_name']
212
+ @state = data['state']
93
213
  @metadata = data['metadata']
214
+ @unified_access_pass_ex_id = data['unified_access_pass_ex_id']
215
+ @pass_template = data['pass_template'] ? LedgerItemPassTemplate.new(data['pass_template']) : nil
216
+ end
217
+ end
218
+
219
+ # Represents a pass template reference within a ledger item's access pass.
220
+ class LedgerItemPassTemplate
221
+ attr_reader :id, :name, :protocol, :platform, :use_case
222
+
223
+ def initialize(data)
224
+ @id = data['id']
225
+ @name = data['name']
226
+ @protocol = data['protocol']
227
+ @platform = data['platform']
228
+ @use_case = data['use_case']
229
+ end
230
+ end
231
+
232
+ # Represents a landing page configuration.
233
+ class LandingPage
234
+ attr_reader :id, :name, :created_at, :kind, :password_protected, :logo_url
235
+
236
+ def initialize(data)
237
+ @id = data['id']
238
+ @name = data['name']
239
+ @created_at = data['created_at']
240
+ @kind = data['kind']
241
+ @password_protected = data['password_protected']
242
+ @logo_url = data['logo_url']
243
+ end
244
+ end
245
+
246
+ # Represents a credential profile configuration.
247
+ class CredentialProfile
248
+ attr_reader :id, :aid, :name, :apple_id, :created_at, :card_storage, :keys, :files
249
+
250
+ def initialize(data)
251
+ @id = data['id']
252
+ @aid = data['aid']
253
+ @name = data['name']
254
+ @apple_id = data['apple_id']
255
+ @created_at = data['created_at']
256
+ @card_storage = data['card_storage']
257
+ @keys = data['keys'] || []
258
+ @files = data['files'] || []
259
+ end
260
+ end
261
+
262
+ # Manages credential profile operations.
263
+ class CredentialProfiles
264
+ def initialize(client)
265
+ @client = client
266
+ end
267
+
268
+ def create(**params)
269
+ response = @client.make_request(:post, '/v1/console/credential-profiles', params)
270
+ CredentialProfile.new(response)
271
+ end
272
+
273
+ def list
274
+ response = @client.make_request(:get, '/v1/console/credential-profiles')
275
+ profiles = response.is_a?(Array) ? response : response.fetch('credential_profiles', [])
276
+ profiles.map { |profile| CredentialProfile.new(profile) }
277
+ end
278
+ end
279
+
280
+ # Manages webhook operations.
281
+ class Webhooks
282
+ def initialize(client)
283
+ @client = client
284
+ end
285
+
286
+ def create(name:, url:, subscribed_events:, auth_method: 'bearer_token')
287
+ data = {
288
+ name: name,
289
+ url: url,
290
+ subscribed_events: subscribed_events,
291
+ auth_method: auth_method
292
+ }
293
+ response = @client.make_request(:post, '/v1/console/webhooks', data)
294
+ Webhook.new(response)
295
+ end
296
+
297
+ def list(**params)
298
+ response = @client.make_request(:get, '/v1/console/webhooks', nil, params)
299
+ (response['webhooks'] || []).map { |wh| Webhook.new(wh) }
300
+ end
301
+
302
+ def delete(webhook_id)
303
+ @client.make_request(:delete, "/v1/console/webhooks/#{webhook_id}")
304
+ end
305
+ end
306
+
307
+ # Represents a webhook configuration.
308
+ class Webhook
309
+ attr_reader :id, :name, :url, :auth_method, :subscribed_events,
310
+ :created_at, :private_key, :client_cert, :cert_expires_at
311
+
312
+ def initialize(data)
313
+ @id = data['id']
314
+ @name = data['name']
315
+ @url = data['url']
316
+ @auth_method = data['auth_method']
317
+ @subscribed_events = data['subscribed_events']
318
+ @created_at = data['created_at']
319
+ @private_key = data['private_key']
320
+ @client_cert = data['client_cert']
321
+ @cert_expires_at = data['cert_expires_at']
322
+ end
323
+ end
324
+
325
+ # Provides access to HID-related services.
326
+ class HID
327
+ attr_reader :orgs
328
+
329
+ def initialize(client)
330
+ @orgs = HIDOrgs.new(client)
331
+ end
332
+ end
333
+
334
+ # Manages HID organization operations.
335
+ class HIDOrgs
336
+ def initialize(client)
337
+ @client = client
338
+ end
339
+
340
+ def create(name:, full_address:, phone:, first_name:, last_name:)
341
+ data = {
342
+ name: name,
343
+ full_address: full_address,
344
+ phone: phone,
345
+ first_name: first_name,
346
+ last_name: last_name
347
+ }
348
+ response = @client.make_request(:post, '/v1/console/hid/orgs', data)
349
+ HidOrg.new(response)
350
+ end
351
+
352
+ def list
353
+ response = @client.make_request(:get, '/v1/console/hid/orgs')
354
+ response.map { |org| HidOrg.new(org) }
355
+ end
356
+
357
+ def activate(email:, password:)
358
+ data = { email: email, password: password }
359
+ response = @client.make_request(:post, '/v1/console/hid/orgs/activate', data)
360
+ HidOrg.new(response)
361
+ end
362
+ end
363
+
364
+ # Represents an HID organization.
365
+ class HidOrg
366
+ attr_reader :id, :name, :slug, :first_name, :last_name, :phone, :full_address, :status, :created_at
367
+
368
+ def initialize(data)
369
+ @id = data['id']
370
+ @name = data['name']
371
+ @slug = data['slug']
372
+ @first_name = data['first_name']
373
+ @last_name = data['last_name']
374
+ @phone = data['phone']
375
+ @full_address = data['full_address']
376
+ @status = data['status']
377
+ @created_at = data['created_at']
94
378
  end
95
379
  end
96
- end
380
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AccessGrid
4
+ # base error
5
+ class Error < StandardError; end
6
+
7
+ # Raised when API credentials are invalid.
8
+ class AuthenticationError < Error; end
9
+
10
+ # Raised when a requested resource does not exist.
11
+ class ResourceNotFoundError < Error; end
12
+
13
+ # Raised when request parameters fail validation.
14
+ class ValidationError < Error; end
15
+
16
+ # additional error classes to match Python version
17
+ class AccessGridError < Error; end
18
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module AccessGrid
6
+ # Builds and configures HTTP requests for the AccessGrid API.
7
+ class Request
8
+ PAYLOAD_SIGNATURE_PARAM = :sig_payload
9
+ HTTP_METHODS = {
10
+ get: Net::HTTP::Get,
11
+ post: Net::HTTP::Post,
12
+ put: Net::HTTP::Put,
13
+ patch: Net::HTTP::Patch,
14
+ delete: Net::HTTP::Delete
15
+ }.freeze
16
+
17
+ attr_reader :account_id, :body, :http_method, :params, :payload, :uri
18
+
19
+ def initialize(attrs)
20
+ # required attributes
21
+ @http_method = attrs.fetch(:http_method)
22
+ @provided_host = attrs.fetch(:host)
23
+ @provided_path = attrs.fetch(:path)
24
+
25
+ # optional attributes
26
+ @account_id = attrs.fetch(:account_id, nil)
27
+ @body = attrs.fetch(:body, nil)
28
+ @params = attrs.fetch(:params, nil) || {}
29
+
30
+ # computed attributes
31
+ initialize_payload
32
+ initialize_uri
33
+ end
34
+
35
+ def net_http_request
36
+ return @net_http_request if defined?(@net_http_request)
37
+
38
+ @net_http_request = generate_net_http_request!.tap do |req|
39
+ req['Content-Type'] = 'application/json'
40
+ req['X-ACCT-ID'] = account_id
41
+ req['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
42
+
43
+ req.body = body.to_json if body && !get?
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def generate_net_http_request!
50
+ klass = HTTP_METHODS.fetch(http_method) do
51
+ raise ArgumentError, "Unsupported HTTP method: #{http_method}"
52
+ end
53
+ klass.new(uri.request_uri)
54
+ end
55
+
56
+ def initialize_payload
57
+ return @payload if defined?(@payload)
58
+ return @payload = default_payload unless post_without_body_or_get?
59
+
60
+ if resource_id
61
+ @payload = { id: resource_id }.to_json
62
+ @params[PAYLOAD_SIGNATURE_PARAM] = @payload
63
+ else
64
+ @payload = '{}'
65
+ end
66
+
67
+ @payload
68
+ end
69
+
70
+ def initialize_uri
71
+ @uri = URI.parse("#{@provided_host}#{@provided_path}").tap do |parsed_uri|
72
+ parsed_uri.query = URI.encode_www_form(@params) if @params.any?
73
+ end
74
+ end
75
+
76
+ def resource_id
77
+ return @resource_id if defined?(@resource_id)
78
+ return @resource_id = nil unless post_without_body_or_get?
79
+
80
+ parts = @provided_path.strip.split('/')
81
+ return @resource_id = nil unless parts.length >= 2
82
+
83
+ last_part = parts.last
84
+ second_to_last_part = parts[-2]
85
+ @resource_id = %w[suspend resume unlink delete].include?(last_part) ? second_to_last_part : last_part
86
+ end
87
+
88
+ def get?
89
+ http_method == :get
90
+ end
91
+
92
+ def delete?
93
+ http_method == :delete
94
+ end
95
+
96
+ def post?
97
+ http_method == :post
98
+ end
99
+
100
+ def empty_body?
101
+ body.nil? || body.empty?
102
+ end
103
+
104
+ def post_without_body_or_get?
105
+ get? || delete? || (post? && empty_body?)
106
+ end
107
+
108
+ def default_payload
109
+ body&.to_json || ''
110
+ end
111
+ end
112
+ end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/accessgrid/version.rb
2
4
  module AccessGrid
3
- VERSION = '0.1.3'
4
- end
5
+ VERSION = '0.4.0'
6
+ end
data/lib/accessgrid.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/accessgrid.rb
2
4
  require 'base64'
3
5
  require 'json'
@@ -5,171 +7,97 @@ require 'net/http'
5
7
  require 'openssl'
6
8
  require 'uri'
7
9
 
8
- require_relative 'accessgrid/version'
9
10
  require_relative 'accessgrid/access_cards'
10
11
  require_relative 'accessgrid/console'
12
+ require_relative 'accessgrid/error'
13
+ require_relative 'accessgrid/request'
14
+ require_relative 'accessgrid/version'
11
15
 
16
+ # Ruby SDK for the AccessGrid API.
12
17
  module AccessGrid
13
- class Error < StandardError; end
14
- class AuthenticationError < Error; end
15
- class ResourceNotFoundError < Error; end
16
- class ValidationError < Error; end
17
-
18
- # Additional error classes to match Python version
19
- class AccessGridError < Error; end
20
-
18
+ # API client for AccessGrid key card and template management.
21
19
  class Client
22
- attr_reader :account_id, :api_secret, :api_host
23
-
20
+ attr_reader :access_cards, :account_id, :api_secret, :api_host, :console
21
+
24
22
  def initialize(account_id, api_secret, api_host = 'https://api.accessgrid.com')
25
- if account_id.nil? || account_id.empty?
26
- raise ArgumentError, "Account ID is required"
27
- end
28
-
29
- if api_secret.nil? || api_secret.empty?
30
- raise ArgumentError, "API Secret is required"
31
- end
32
-
23
+ raise ArgumentError, 'Account ID is required' if account_id.nil? || account_id.empty?
24
+ raise ArgumentError, 'API Secret is required' if api_secret.nil? || api_secret.empty?
25
+
33
26
  @account_id = account_id
34
27
  @api_secret = api_secret
35
28
  @api_host = api_host.chomp('/')
29
+ @access_cards = AccessCards.new(self)
30
+ @console = Console.new(self)
36
31
  end
37
32
 
38
- def access_cards
39
- @access_cards ||= AccessCards.new(self)
40
- end
33
+ def make_request(method, path, body = nil, params = nil)
34
+ request = Request.new(
35
+ account_id: account_id,
36
+ body: body,
37
+ host: api_host,
38
+ http_method: method,
39
+ params: params,
40
+ path: path
41
+ )
41
42
 
42
- def console
43
- @console ||= Console.new(self)
43
+ execute_and_process_request!(request)
44
44
  end
45
45
 
46
- def make_request(method, path, body = nil, params = nil)
47
- uri = URI.parse("#{api_host}#{path}")
48
-
49
- # Add query parameters if present
50
- if params && !params.empty?
51
- uri.query = URI.encode_www_form(params)
52
- end
53
-
54
- http = Net::HTTP.new(uri.host, uri.port)
55
- http.use_ssl = uri.scheme == 'https'
56
-
57
- # Create request object based on method
58
- request = case method
59
- when :get
60
- Net::HTTP::Get.new(uri.request_uri)
61
- when :post
62
- Net::HTTP::Post.new(uri.request_uri)
63
- when :put
64
- Net::HTTP::Put.new(uri.request_uri)
65
- when :patch
66
- Net::HTTP::Patch.new(uri.request_uri)
67
- else
68
- raise ArgumentError, "Unsupported HTTP method: #{method}"
69
- end
46
+ private
47
+
48
+ def execute_and_process_request!(request)
49
+ # set up net http
50
+ uri = request.uri
51
+ http = Net::HTTP.new(uri.host, uri.port).tap { |http| http.use_ssl = uri.scheme == 'https' }
70
52
 
71
- # Set headers
72
- request['Content-Type'] = 'application/json'
73
- request['X-ACCT-ID'] = account_id
74
- request['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
75
-
76
- # Extract resource ID from the path if needed for signature
77
- resource_id = nil
78
- if method == :get || (method == :post && (body.nil? || body.empty?))
79
- parts = path.strip.split('/')
80
- if parts.length >= 2
81
- if ['suspend', 'resume', 'unlink', 'delete'].include?(parts.last)
82
- resource_id = parts[-2]
83
- else
84
- resource_id = parts.last
85
- end
86
- end
87
- end
88
-
89
- # Handle signature generation
90
- if method == :get || (method == :post && (body.nil? || body.empty?))
91
- payload = resource_id ? { id: resource_id }.to_json : '{}'
92
-
93
- # Include sig_payload in query params if needed
94
- if resource_id
95
- if params.nil?
96
- params = {}
97
- end
98
- params[:sig_payload] = { id: resource_id }.to_json
99
-
100
- # Update the URI with the new params
101
- uri.query = URI.encode_www_form(params)
102
- request = case method
103
- when :get
104
- Net::HTTP::Get.new(uri.request_uri)
105
- when :post
106
- Net::HTTP::Post.new(uri.request_uri)
107
- end
108
-
109
- # Reset headers after creating new request
110
- request['Content-Type'] = 'application/json'
111
- request['X-ACCT-ID'] = account_id
112
- request['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
113
- end
114
- else
115
- payload = body ? body.to_json : ""
116
- end
117
-
118
53
  # Generate signature
119
- request['X-PAYLOAD-SIG'] = generate_signature(payload)
120
-
121
- # Add the body to the request
122
- if body && method != :get
123
- request.body = body.to_json
124
- end
54
+ net_http_request = request.net_http_request
55
+ net_http_request['X-PAYLOAD-SIG'] = generate_signature_hash(request.payload)
125
56
 
126
57
  # Make request
127
- response = http.request(request)
128
-
58
+ response = http.request(net_http_request)
59
+
129
60
  # Parse response
130
61
  handle_response(response)
131
62
  end
132
63
 
133
- private
134
-
135
- def generate_signature(payload)
136
- # Base64 encode the payload
137
- encoded_payload = Base64.strict_encode64(payload.to_s)
138
-
139
- # Generate SHA256 hash
140
- OpenSSL::HMAC.hexdigest(
141
- OpenSSL::Digest.new('sha256'),
142
- api_secret,
143
- encoded_payload
144
- )
145
- end
146
-
147
64
  def handle_response(response)
148
65
  case response.code.to_i
149
- when 200, 201, 202
150
- JSON.parse(response.body)
151
- when 401
152
- raise AuthenticationError, 'Invalid credentials'
153
- when 402
154
- raise Error, 'Insufficient account balance'
155
- when 404
156
- raise ResourceNotFoundError, 'Resource not found'
157
- when 422
158
- raise ValidationError, JSON.parse(response.body)['message']
66
+ when 204 then {}
67
+ when 200, 201, 202 then JSON.parse(response.body)
68
+ when 401 then raise AuthenticationError, 'Invalid credentials'
69
+ when 402 then raise Error, 'Insufficient account balance'
70
+ when 404 then raise ResourceNotFoundError, 'Resource not found'
71
+ when 422 then raise ValidationError, JSON.parse(response.body)['message']
159
72
  else
160
- error_message = response.body.empty? ? "HTTP Status #{response.code}" : response.body
161
- begin
162
- error_data = JSON.parse(response.body)
163
- error_message = error_data['message'] if error_data['message']
164
- rescue JSON::ParserError
165
- # If it's not valid JSON, just use the response body
166
- end
167
- raise Error, "API request failed: #{error_message}"
73
+ process_unhandled_response(response)
168
74
  end
169
75
  end
76
+
77
+ def process_unhandled_response(response)
78
+ body = response.body
79
+ error_message = body.empty? ? "HTTP Status #{response.code}" : body
80
+
81
+ begin
82
+ error_data = JSON.parse(body)
83
+ error_message = error_data['message'] if error_data['message']
84
+ rescue JSON::ParserError
85
+ # If it's not valid JSON, just use the response body
86
+ end
87
+
88
+ raise Error, "API request failed: #{error_message}"
89
+ end
90
+
91
+ def generate_signature_hash(value)
92
+ # Base64 encode the value
93
+ encoded_value = Base64.strict_encode64(value.to_s)
94
+
95
+ # Generate SHA256 hash
96
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), api_secret, encoded_value)
97
+ end
170
98
  end
171
99
 
172
100
  def self.new(account_id, api_secret, api_host = 'https://api.accessgrid.com')
173
101
  Client.new(account_id, api_secret, api_host)
174
102
  end
175
- end
103
+ end
metadata CHANGED
@@ -1,71 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accessgrid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Auston Bunsen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-26 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: base64
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :development
19
+ version: '0'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '13.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '13.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.0'
55
- - !ruby/object:Gem::Dependency
56
- name: webmock
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
26
+ version: '0'
69
27
  description: A Ruby client for the AccessGrid API
70
28
  email:
71
29
  - ab@accessgrid.com
@@ -78,6 +36,8 @@ files:
78
36
  - lib/accessgrid.rb
79
37
  - lib/accessgrid/access_cards.rb
80
38
  - lib/accessgrid/console.rb
39
+ - lib/accessgrid/error.rb
40
+ - lib/accessgrid/request.rb
81
41
  - lib/accessgrid/version.rb
82
42
  homepage: https://github.com/access-grid/accessgrid-rb
83
43
  licenses:
@@ -92,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
52
  requirements:
93
53
  - - ">="
94
54
  - !ruby/object:Gem::Version
95
- version: 2.6.0
55
+ version: '3.0'
96
56
  required_rubygems_version: !ruby/object:Gem::Requirement
97
57
  requirements:
98
58
  - - ">="