accessgrid 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15b5d0cfdff13339d74d78a8204778a5eb0d36df704b6e2b67899dc4d9799d82
4
- data.tar.gz: c2429ef868beb9ce1e45d2535451ee8c1909bafbd446555d04be72c989ba2770
3
+ metadata.gz: ab77e5bbf4a0a163fb5990dd7a0c6872c31bdbeb750bd7906b394d0d86e70d59
4
+ data.tar.gz: 2e6dff141d2eb8bd4c605c5363b12d0494e587cd4bcd1dc7e8a856d745dfa4ef
5
5
  SHA512:
6
- metadata.gz: c43a8a49895d402425966639e9b43f66daa745d29f8c26f00429cc42b364a4e6835c3c9277ddd675743bfa223d2b34848edcf666cd37aa1a2e6b4383088c2918
7
- data.tar.gz: 9d3de9854fd135763cb15035655abac79078ef709d257cab99e8850f1a23821aa08004b80a47d94abfd78b90467af353b560b2a266bc0aa6cf940e088d9f587b
6
+ metadata.gz: 2516e380d23a8b0e0bc988c5fb47c322707c0f2b98e5d82f1df4cfb500353bb56b131347e4bb9f899aa853d93d3acd6512edabe70f8c06b35d7a893804b3b1cb
7
+ data.tar.gz: 94cd560394836b1be4b73541557715867dc3f92979563778448c93e26994a3ea93d2681a6704d01f1c585b7d5ffa8b8380a00efb2a25913cdb7a42da90ed5415
data/README.md CHANGED
@@ -37,14 +37,14 @@ client = AccessGrid.new(account_id, secret_key)
37
37
 
38
38
  ### Access Cards
39
39
 
40
- #### Provision a new card
40
+ #### Issue a new card
41
41
 
42
42
  ```ruby
43
- card = client.access_cards.provision(
44
- card_template_id: "0xd3adb00b5",
43
+ card = client.access_cards.issue(
44
+ card_template_id: card_template_id,
45
45
  employee_id: "123456789",
46
+ card_number: "16187",
46
47
  tag_id: "DDEADB33FB00B5",
47
- allow_on_multiple_devices: true,
48
48
  full_name: "Employee name",
49
49
  email: "employee@yourwebsite.com",
50
50
  phone_number: "+19547212241",
@@ -54,39 +54,71 @@ card = client.access_cards.provision(
54
54
  employee_photo: "[image_in_base64_encoded_format]"
55
55
  )
56
56
 
57
+ # Provision is an alias for issue (for backwards compatibility)
58
+ card = client.access_cards.provision(
59
+ card_template_id: card_template_id,
60
+ employee_id: "123456789",
61
+ # ...other parameters
62
+ )
63
+
57
64
  puts "Install URL: #{card.url}"
58
65
  ```
59
66
 
67
+ #### Get a card
68
+
69
+ ```ruby
70
+ card = client.access_cards.get(card_id: "0xc4rd1d")
71
+
72
+ puts "Card ID: #{card.id}"
73
+ puts "State: #{card.state}"
74
+ puts "Full Name: #{card.full_name}"
75
+ puts "Install URL: #{card.install_url}"
76
+ puts "Expiration Date: #{card.expiration_date}"
77
+ puts "Card Number: #{card.card_number}"
78
+ puts "Site Code: #{card.site_code}"
79
+ puts "Devices: #{card.devices.length}"
80
+ puts "Metadata: #{card.metadata}"
81
+ ```
82
+
60
83
  #### Update a card
61
84
 
62
85
  ```ruby
63
86
  card = client.access_cards.update(
64
- card_id: "0xc4rd1d",
65
- employee_id: "987654321",
66
- full_name: "Updated Employee Name",
67
- classification: "contractor",
68
- expiration_date: "2025-02-22T21:04:03.664Z",
69
- employee_photo: "[image_in_base64_encoded_format]"
87
+ "0xc4rd1d",
88
+ {
89
+ employee_id: "987654321",
90
+ full_name: "Updated Employee Name",
91
+ classification: "contractor",
92
+ expiration_date: "2025-02-22T21:04:03.664Z",
93
+ employee_photo: "[image_in_base64_encoded_format]"
94
+ }
70
95
  )
71
96
  ```
72
97
 
98
+ #### List cards
99
+
100
+ ```ruby
101
+ # List all cards for a template
102
+ cards = client.access_cards.list("template_id")
103
+
104
+ # List cards filtered by state
105
+ active_cards = client.access_cards.list("template_id", "active")
106
+ ```
107
+
73
108
  #### Manage card states
74
109
 
75
110
  ```ruby
76
111
  # Suspend a card
77
- client.access_cards.suspend(
78
- card_id: "0xc4rd1d"
79
- )
112
+ client.access_cards.suspend("0xc4rd1d")
80
113
 
81
114
  # Resume a card
82
- client.access_cards.resume(
83
- card_id: "0xc4rd1d"
84
- )
115
+ client.access_cards.resume("0xc4rd1d")
85
116
 
86
117
  # Unlink a card
87
- client.access_cards.unlink(
88
- card_id: "0xc4rd1d"
89
- )
118
+ client.access_cards.unlink("0xc4rd1d")
119
+
120
+ # Delete a card
121
+ client.access_cards.delete("0xc4rd1d")
90
122
  ```
91
123
 
92
124
  ### Enterprise Console
@@ -124,17 +156,19 @@ template = client.console.create_template(
124
156
 
125
157
  ```ruby
126
158
  template = client.console.update_template(
127
- card_template_id: "0xd3adb00b5",
128
- name: "Updated Employee NFC key",
129
- allow_on_multiple_devices: true,
130
- watch_count: 2,
131
- iphone_count: 3,
132
- support_info: {
133
- support_url: "https://help.yourcompany.com",
134
- support_phone_number: "+1-555-123-4567",
135
- support_email: "support@yourcompany.com",
136
- privacy_policy_url: "https://yourcompany.com/privacy",
137
- terms_and_conditions_url: "https://yourcompany.com/terms"
159
+ "0xd3adb00b5",
160
+ {
161
+ name: "Updated Employee NFC key",
162
+ allow_on_multiple_devices: true,
163
+ watch_count: 2,
164
+ iphone_count: 3,
165
+ support_info: {
166
+ support_url: "https://help.yourcompany.com",
167
+ support_phone_number: "+1-555-123-4567",
168
+ support_email: "support@yourcompany.com",
169
+ privacy_policy_url: "https://yourcompany.com/privacy",
170
+ terms_and_conditions_url: "https://yourcompany.com/terms"
171
+ }
138
172
  }
139
173
  )
140
174
  ```
@@ -142,14 +176,24 @@ template = client.console.update_template(
142
176
  #### Read a template
143
177
 
144
178
  ```ruby
145
- template = client.console.read_template(
146
- card_template_id: "0xd3adb00b5"
147
- )
179
+ template = client.console.read_template("0xd3adb00b5")
148
180
  ```
149
181
 
150
182
  #### Get event logs
151
183
 
152
184
  ```ruby
185
+ # New method
186
+ logs = client.console.get_logs(
187
+ "0xd3adb00b5",
188
+ {
189
+ device: "mobile", # "mobile" or "watch"
190
+ start_date: (Time.now - 30*24*60*60).iso8601,
191
+ end_date: Time.now.iso8601,
192
+ event_type: "install"
193
+ }
194
+ )
195
+
196
+ # Legacy method still works
153
197
  events = client.console.event_log(
154
198
  card_template_id: "0xd3adb00b5",
155
199
  filters: {
@@ -179,6 +223,7 @@ The SDK throws specific errors for various scenarios:
179
223
  - `AccessGrid::AuthenticationError` - Invalid credentials
180
224
  - `AccessGrid::ResourceNotFoundError` - Requested resource not found
181
225
  - `AccessGrid::ValidationError` - Invalid parameters
226
+ - `AccessGrid::AccessGridError` - Base exception for AccessGrid-specific errors
182
227
  - `AccessGrid::Error` - Generic API errors
183
228
 
184
229
  Example error handling:
@@ -5,17 +5,32 @@ module AccessGrid
5
5
  @client = client
6
6
  end
7
7
 
8
- def provision(params)
8
+ def issue(params)
9
9
  response = @client.make_request(:post, '/v1/key-cards', params)
10
10
  Card.new(response)
11
11
  end
12
+
13
+ # Alias provision to issue for backward compatibility
14
+ alias provision issue
12
15
 
13
- def update(params)
14
- card_id = params.delete(:card_id)
15
- response = @client.make_request(:put, "/v1/key-cards/#{card_id}", params)
16
+ def get(card_id:)
17
+ response = @client.make_request(:get, "/v1/key-cards/#{card_id}")
16
18
  Card.new(response)
17
19
  end
18
20
 
21
+ def update(card_id, params)
22
+ response = @client.make_request(:patch, "/v1/key-cards/#{card_id}", params)
23
+ Card.new(response)
24
+ end
25
+
26
+ def list(template_id, state = nil)
27
+ params = { template_id: template_id }
28
+ params[:state] = state if state
29
+
30
+ response = @client.make_request(:get, '/v1/key-cards', nil, params)
31
+ response.fetch('keys', []).map { |item| Card.new(item) }
32
+ end
33
+
19
34
  private def manage_state(card_id, action)
20
35
  response = @client.make_request(
21
36
  :post,
@@ -25,28 +40,50 @@ module AccessGrid
25
40
  Card.new(response)
26
41
  end
27
42
 
28
- def suspend(params)
29
- manage_state(params[:card_id], 'suspend')
43
+ def suspend(card_id)
44
+ manage_state(card_id, 'suspend')
30
45
  end
31
46
 
32
- def resume(params)
33
- manage_state(params[:card_id], 'resume')
47
+ def resume(card_id)
48
+ manage_state(card_id, 'resume')
34
49
  end
35
50
 
36
- def unlink(params)
37
- manage_state(params[:card_id], 'unlink')
51
+ def unlink(card_id)
52
+ manage_state(card_id, 'unlink')
53
+ end
54
+
55
+ def delete(card_id)
56
+ manage_state(card_id, 'delete')
38
57
  end
39
58
  end
40
59
 
41
60
  class Card
42
- attr_reader :id, :state, :url, :full_name, :expiration_date
61
+ attr_reader :id, :state, :url, :install_url, :details, :full_name,
62
+ :expiration_date, :card_template_id, :card_number, :site_code,
63
+ :file_data, :direct_install_url, :devices, :metadata
43
64
 
44
65
  def initialize(data)
45
- @id = data['id']
46
- @state = data['state']
47
- @url = data['install_url']
48
- @full_name = data['full_name']
49
- @expiration_date = data['expiration_date']
66
+ data ||= {}
67
+ @id = data.fetch('id', nil)
68
+ @state = data.fetch('state', nil)
69
+ @url = data.fetch('install_url', nil)
70
+ @install_url = data.fetch('install_url', nil)
71
+ @details = data.fetch('details', nil)
72
+ @full_name = data.fetch('full_name', nil)
73
+ @expiration_date = data.fetch('expiration_date', nil)
74
+ @card_template_id = data.fetch('card_template_id', nil)
75
+ @card_number = data.fetch('card_number', nil)
76
+ @site_code = data.fetch('site_code', nil)
77
+ @file_data = data.fetch('file_data', nil)
78
+ @direct_install_url = data.fetch('direct_install_url', nil)
79
+ @devices = data.fetch('devices', [])
80
+ @metadata = data.fetch('metadata', {})
81
+ end
82
+
83
+ def to_s
84
+ "Card(name='#{@full_name}', id='#{@id}', state='#{@state}')"
50
85
  end
86
+
87
+ alias inspect to_s
51
88
  end
52
89
  end
@@ -11,22 +11,33 @@ module AccessGrid
11
11
  Template.new(response)
12
12
  end
13
13
 
14
- def update_template(params)
15
- card_template_id = params.delete(:card_template_id)
14
+ def update_template(template_id, params)
16
15
  transformed_params = transform_template_params(params)
17
- response = @client.make_request(:put, "/v1/console/card-templates/#{card_template_id}", transformed_params)
16
+ response = @client.make_request(:put, "/v1/console/card-templates/#{template_id}", transformed_params)
18
17
  Template.new(response)
19
18
  end
20
19
 
21
- def read_template(params)
22
- response = @client.make_request(:get, "/v1/console/card-templates/#{params[:card_template_id]}")
20
+ def read_template(template_id)
21
+ response = @client.make_request(:get, "/v1/console/card-templates/#{template_id}")
23
22
  Template.new(response)
24
23
  end
25
24
 
25
+ def get_logs(template_id, params = {})
26
+ response = @client.make_request(:get, "/v1/console/card-templates/#{template_id}/logs", nil, params)
27
+
28
+ # 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
+
33
+ response
34
+ end
35
+
36
+ # Keep event_log for backwards compatibility
26
37
  def event_log(params)
27
- card_template_id = params.delete(:card_template_id)
28
- response = @client.make_request(:get, "/v1/console/card-templates/#{card_template_id}/logs", params)
29
- response['logs'].map { |log| Event.new(log) }
38
+ template_id = params.delete(:card_template_id)
39
+ response = get_logs(template_id, params)
40
+ response['logs'] || []
30
41
  end
31
42
 
32
43
  private
@@ -49,18 +60,23 @@ module AccessGrid
49
60
  end
50
61
 
51
62
  class Template
52
- attr_reader :id, :name, :platform, :protocol, :allow_on_multiple_devices,
53
- :watch_count, :iphone_count, :support_info, :style_settings
63
+ attr_reader :id, :name, :platform, :protocol, :use_case, :created_at,
64
+ :last_published_at, :issued_keys_count, :active_keys_count,
65
+ :allowed_device_counts, :support_settings, :terms_settings, :style_settings
54
66
 
55
67
  def initialize(data)
56
68
  @id = data['id']
57
69
  @name = data['name']
58
70
  @platform = data['platform']
59
71
  @protocol = data['protocol']
60
- @allow_on_multiple_devices = data['allowed_device_counts']['allow_on_multiple_devices']
61
- @watch_count = data['allowed_device_counts']['watch']
62
- @iphone_count = data['allowed_device_counts']['iphone']
63
- @support_info = data['support_settings']
72
+ @use_case = data['use_case']
73
+ @created_at = data['created_at']
74
+ @last_published_at = data['last_published_at']
75
+ @issued_keys_count = data['issued_keys_count']
76
+ @active_keys_count = data['active_keys_count']
77
+ @allowed_device_counts = data['allowed_device_counts']
78
+ @support_settings = data['support_settings']
79
+ @terms_settings = data['terms_settings']
64
80
  @style_settings = data['style_settings']
65
81
  end
66
82
  end
@@ -71,7 +87,7 @@ module AccessGrid
71
87
  def initialize(data)
72
88
  @type = data['event']
73
89
  @timestamp = data['created_at']
74
- @user_id = data['metadata']['user_id']
90
+ @user_id = data['metadata']['user_id'] if data['metadata'] && data['metadata']['user_id']
75
91
  @ip_address = data['ip_address']
76
92
  @user_agent = data['user_agent']
77
93
  @metadata = data['metadata']
@@ -1,4 +1,4 @@
1
1
  # lib/accessgrid/version.rb
2
2
  module AccessGrid
3
- VERSION = '0.1.2'
3
+ VERSION = '0.2.0'
4
4
  end
data/lib/accessgrid.rb CHANGED
@@ -15,13 +15,24 @@ module AccessGrid
15
15
  class ResourceNotFoundError < Error; end
16
16
  class ValidationError < Error; end
17
17
 
18
+ # Additional error classes to match Python version
19
+ class AccessGridError < Error; end
20
+
18
21
  class Client
19
22
  attr_reader :account_id, :api_secret, :api_host
20
23
 
21
24
  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
+
22
33
  @account_id = account_id
23
34
  @api_secret = api_secret
24
- @api_host = api_host
35
+ @api_host = api_host.chomp('/')
25
36
  end
26
37
 
27
38
  def access_cards
@@ -32,8 +43,14 @@ module AccessGrid
32
43
  @console ||= Console.new(self)
33
44
  end
34
45
 
35
- def make_request(method, path, body = nil)
46
+ def make_request(method, path, body = nil, params = nil)
36
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
+
37
54
  http = Net::HTTP.new(uri.host, uri.port)
38
55
  http.use_ssl = uri.scheme == 'https'
39
56
 
@@ -45,6 +62,8 @@ module AccessGrid
45
62
  Net::HTTP::Post.new(uri.request_uri)
46
63
  when :put
47
64
  Net::HTTP::Put.new(uri.request_uri)
65
+ when :patch
66
+ Net::HTTP::Patch.new(uri.request_uri)
48
67
  else
49
68
  raise ArgumentError, "Unsupported HTTP method: #{method}"
50
69
  end
@@ -52,12 +71,56 @@ module AccessGrid
52
71
  # Set headers
53
72
  request['Content-Type'] = 'application/json'
54
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
+ # Generate signature
119
+ request['X-PAYLOAD-SIG'] = generate_signature(payload)
55
120
 
56
- # Generate signature if body present
57
- if body
58
- json_body = body.to_json
59
- request['X-PAYLOAD-SIG'] = generate_signature(json_body)
60
- request.body = json_body
121
+ # Add the body to the request
122
+ if body && method != :get
123
+ request.body = body.to_json
61
124
  end
62
125
 
63
126
  # Make request
@@ -71,7 +134,7 @@ module AccessGrid
71
134
 
72
135
  def generate_signature(payload)
73
136
  # Base64 encode the payload
74
- encoded_payload = Base64.strict_encode64(payload)
137
+ encoded_payload = Base64.strict_encode64(payload.to_s)
75
138
 
76
139
  # Generate SHA256 hash
77
140
  OpenSSL::HMAC.hexdigest(
@@ -87,12 +150,21 @@ module AccessGrid
87
150
  JSON.parse(response.body)
88
151
  when 401
89
152
  raise AuthenticationError, 'Invalid credentials'
153
+ when 402
154
+ raise Error, 'Insufficient account balance'
90
155
  when 404
91
156
  raise ResourceNotFoundError, 'Resource not found'
92
157
  when 422
93
158
  raise ValidationError, JSON.parse(response.body)['message']
94
159
  else
95
- raise Error, "API request failed with status #{response.code}: #{response.body}"
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}"
96
168
  end
97
169
  end
98
170
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accessgrid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.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-01 00:00:00.000000000 Z
11
+ date: 2025-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler