seam 2.0.0a2 → 2.0.0b0

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.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +54 -51
  3. data/README.md +323 -3
  4. data/Rakefile +4 -1
  5. data/lib/seam/auth.rb +118 -0
  6. data/lib/seam/base_resource.rb +63 -0
  7. data/lib/seam/deep_hash_accessor.rb +37 -0
  8. data/lib/seam/default_endpoint.rb +5 -0
  9. data/lib/seam/helpers/action_attempt.rb +45 -0
  10. data/lib/seam/http.rb +52 -0
  11. data/lib/seam/http_multi_workspace.rb +62 -0
  12. data/lib/seam/http_single_workspace.rb +42 -0
  13. data/lib/seam/options.rb +64 -0
  14. data/lib/seam/parse_options.rb +23 -0
  15. data/lib/seam/request.rb +82 -51
  16. data/lib/seam/routes/clients/access_codes.rb +74 -0
  17. data/lib/seam/routes/clients/access_codes_simulate.rb +18 -0
  18. data/lib/seam/routes/clients/access_codes_unmanaged.rb +42 -0
  19. data/lib/seam/routes/clients/acs.rb +44 -0
  20. data/lib/seam/routes/clients/acs_access_groups.rb +48 -0
  21. data/lib/seam/routes/clients/acs_access_groups_unmanaged.rb +24 -0
  22. data/lib/seam/routes/clients/acs_credential_pools.rb +18 -0
  23. data/lib/seam/routes/clients/acs_credential_provisioning_automations.rb +18 -0
  24. data/lib/seam/routes/clients/acs_credentials.rb +60 -0
  25. data/lib/seam/routes/clients/acs_credentials_unmanaged.rb +24 -0
  26. data/lib/seam/routes/clients/acs_encoders.rb +36 -0
  27. data/lib/seam/routes/clients/acs_entrances.rb +36 -0
  28. data/lib/seam/routes/clients/acs_systems.rb +30 -0
  29. data/lib/seam/routes/clients/acs_users.rb +78 -0
  30. data/lib/seam/routes/clients/acs_users_unmanaged.rb +24 -0
  31. data/lib/seam/routes/clients/action_attempts.rb +28 -0
  32. data/lib/seam/routes/clients/client_sessions.rb +54 -0
  33. data/lib/seam/routes/clients/connect_webviews.rb +36 -0
  34. data/lib/seam/routes/clients/connected_accounts.rb +36 -0
  35. data/lib/seam/routes/clients/devices.rb +50 -0
  36. data/lib/seam/routes/clients/devices_simulate.rb +30 -0
  37. data/lib/seam/routes/clients/devices_unmanaged.rb +30 -0
  38. data/lib/seam/routes/clients/events.rb +24 -0
  39. data/lib/seam/routes/clients/index.rb +38 -0
  40. data/lib/seam/routes/clients/locks.rb +42 -0
  41. data/lib/seam/routes/clients/networks.rb +24 -0
  42. data/lib/seam/routes/clients/noise_sensors.rb +26 -0
  43. data/lib/seam/routes/clients/noise_sensors_noise_thresholds.rb +42 -0
  44. data/lib/seam/routes/clients/noise_sensors_simulate.rb +18 -0
  45. data/lib/seam/routes/clients/phones.rb +28 -0
  46. data/lib/seam/routes/clients/phones_simulate.rb +18 -0
  47. data/lib/seam/routes/clients/thermostats.rb +108 -0
  48. data/lib/seam/routes/clients/thermostats_schedules.rb +42 -0
  49. data/lib/seam/routes/clients/user_identities.rb +88 -0
  50. data/lib/seam/routes/clients/user_identities_enrollment_automations.rb +36 -0
  51. data/lib/seam/routes/clients/webhooks.rb +42 -0
  52. data/lib/seam/routes/clients/workspaces.rb +40 -0
  53. data/lib/seam/routes/resources/access_code.rb +14 -0
  54. data/lib/seam/routes/resources/acs_access_group.rb +11 -0
  55. data/lib/seam/routes/resources/acs_credential.rb +14 -0
  56. data/lib/seam/routes/resources/acs_credential_pool.rb +11 -0
  57. data/lib/seam/routes/resources/acs_credential_provisioning_automation.rb +11 -0
  58. data/lib/seam/routes/resources/acs_entrance.rb +13 -0
  59. data/lib/seam/routes/resources/acs_system.rb +14 -0
  60. data/lib/seam/routes/resources/acs_user.rb +14 -0
  61. data/lib/seam/routes/resources/action_attempt.rb +9 -0
  62. data/lib/seam/routes/resources/client_session.rb +11 -0
  63. data/lib/seam/routes/resources/connect_webview.rb +11 -0
  64. data/lib/seam/routes/resources/connected_account.rb +14 -0
  65. data/lib/seam/routes/resources/device.rb +14 -0
  66. data/lib/seam/routes/resources/device_provider.rb +9 -0
  67. data/lib/seam/routes/resources/enrollment_automation.rb +11 -0
  68. data/lib/seam/routes/resources/event.rb +11 -0
  69. data/lib/seam/routes/resources/index.rb +33 -0
  70. data/lib/seam/routes/resources/network.rb +11 -0
  71. data/lib/seam/routes/resources/noise_threshold.rb +9 -0
  72. data/lib/seam/routes/resources/phone.rb +14 -0
  73. data/lib/seam/routes/resources/resource_error.rb +11 -0
  74. data/lib/seam/routes/resources/resource_errors_support.rb +11 -0
  75. data/lib/seam/routes/resources/resource_warning.rb +11 -0
  76. data/lib/seam/routes/resources/resource_warnings_support.rb +11 -0
  77. data/lib/seam/routes/resources/service_health.rb +9 -0
  78. data/lib/seam/routes/resources/thermostat_schedule.rb +13 -0
  79. data/lib/seam/routes/resources/unmanaged_access_code.rb +14 -0
  80. data/lib/seam/routes/resources/unmanaged_device.rb +14 -0
  81. data/lib/seam/routes/resources/user_identity.rb +11 -0
  82. data/lib/seam/routes/resources/webhook.rb +9 -0
  83. data/lib/seam/routes/resources/workspace.rb +9 -0
  84. data/lib/seam/routes/routes.rb +94 -0
  85. data/lib/seam/token.rb +53 -0
  86. data/lib/seam/version.rb +1 -1
  87. data/lib/seam/wait_for_action_attempt.rb +32 -0
  88. data/lib/seam/webhook.rb +22 -0
  89. data/lib/seam.rb +19 -68
  90. metadata +115 -70
  91. data/lib/seam/client.rb +0 -129
  92. data/lib/seam/clients/access_codes.rb +0 -95
  93. data/lib/seam/clients/access_codes_simulate.rb +0 -17
  94. data/lib/seam/clients/access_codes_unmanaged.rb +0 -57
  95. data/lib/seam/clients/acs.rb +0 -35
  96. data/lib/seam/clients/acs_access_groups.rb +0 -57
  97. data/lib/seam/clients/acs_credential_pools.rb +0 -17
  98. data/lib/seam/clients/acs_credential_provisioning_automations.rb +0 -17
  99. data/lib/seam/clients/acs_credentials.rb +0 -77
  100. data/lib/seam/clients/acs_entrances.rb +0 -47
  101. data/lib/seam/clients/acs_systems.rb +0 -27
  102. data/lib/seam/clients/acs_users.rb +0 -117
  103. data/lib/seam/clients/action_attempts.rb +0 -30
  104. data/lib/seam/clients/base_client.rb +0 -21
  105. data/lib/seam/clients/client_sessions.rb +0 -77
  106. data/lib/seam/clients/connect_webviews.rb +0 -47
  107. data/lib/seam/clients/connected_accounts.rb +0 -47
  108. data/lib/seam/clients/devices.rb +0 -65
  109. data/lib/seam/clients/devices_simulate.rb +0 -17
  110. data/lib/seam/clients/devices_unmanaged.rb +0 -37
  111. data/lib/seam/clients/events.rb +0 -27
  112. data/lib/seam/clients/locks.rb +0 -53
  113. data/lib/seam/clients/networks.rb +0 -27
  114. data/lib/seam/clients/noise_sensors.rb +0 -15
  115. data/lib/seam/clients/noise_sensors_noise_thresholds.rb +0 -57
  116. data/lib/seam/clients/noise_sensors_simulate.rb +0 -17
  117. data/lib/seam/clients/phones.rb +0 -31
  118. data/lib/seam/clients/phones_simulate.rb +0 -17
  119. data/lib/seam/clients/thermostats.rb +0 -106
  120. data/lib/seam/clients/thermostats_climate_setting_schedules.rb +0 -57
  121. data/lib/seam/clients/user_identities.rb +0 -131
  122. data/lib/seam/clients/user_identities_enrollment_automations.rb +0 -47
  123. data/lib/seam/clients/webhooks.rb +0 -57
  124. data/lib/seam/clients/workspaces.rb +0 -50
  125. data/lib/seam/resources/access_code.rb +0 -12
  126. data/lib/seam/resources/acs_access_group.rb +0 -9
  127. data/lib/seam/resources/acs_credential.rb +0 -12
  128. data/lib/seam/resources/acs_credential_pool.rb +0 -9
  129. data/lib/seam/resources/acs_credential_provisioning_automation.rb +0 -9
  130. data/lib/seam/resources/acs_entrance.rb +0 -9
  131. data/lib/seam/resources/acs_system.rb +0 -9
  132. data/lib/seam/resources/acs_user.rb +0 -9
  133. data/lib/seam/resources/action_attempt.rb +0 -46
  134. data/lib/seam/resources/base_resource.rb +0 -58
  135. data/lib/seam/resources/client_session.rb +0 -9
  136. data/lib/seam/resources/climate_setting_schedule.rb +0 -11
  137. data/lib/seam/resources/connect_webview.rb +0 -9
  138. data/lib/seam/resources/connected_account.rb +0 -12
  139. data/lib/seam/resources/device.rb +0 -12
  140. data/lib/seam/resources/device_provider.rb +0 -7
  141. data/lib/seam/resources/enrollment_automation.rb +0 -9
  142. data/lib/seam/resources/event.rb +0 -9
  143. data/lib/seam/resources/network.rb +0 -9
  144. data/lib/seam/resources/noise_threshold.rb +0 -7
  145. data/lib/seam/resources/phone.rb +0 -12
  146. data/lib/seam/resources/resource_error.rb +0 -9
  147. data/lib/seam/resources/resource_errors_support.rb +0 -9
  148. data/lib/seam/resources/resource_warning.rb +0 -9
  149. data/lib/seam/resources/resource_warnings_support.rb +0 -9
  150. data/lib/seam/resources/service_health.rb +0 -7
  151. data/lib/seam/resources/unmanaged_access_code.rb +0 -12
  152. data/lib/seam/resources/unmanaged_device.rb +0 -12
  153. data/lib/seam/resources/user_identity.rb +0 -9
  154. data/lib/seam/resources/webhook.rb +0 -7
  155. data/lib/seam/resources/workspace.rb +0 -7
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "deep_hash_accessor"
4
+
5
+ module Seam
6
+ module Resources
7
+ class BaseResource
8
+ attr_accessor :data, :client
9
+
10
+ def initialize(data, client = nil)
11
+ @data = data
12
+ @client = client
13
+
14
+ @data.each do |key, value|
15
+ value = Seam::DeepHashAccessor.new(value) if value.is_a?(Hash)
16
+ instance_variable_set(:"@#{key}", value)
17
+ end
18
+ end
19
+
20
+ def update_from_response(data)
21
+ @data = data
22
+ @data.each do |key, value|
23
+ instance_variable_set(:"@#{key}", value)
24
+ end
25
+ end
26
+
27
+ def self.load_from_response(data, client = nil)
28
+ if data.is_a?(Array)
29
+ data.map { |d| new(d, client) }
30
+ else
31
+ new(data, client)
32
+ end
33
+ end
34
+
35
+ def inspect
36
+ "<#{self.class.name}:#{"0x00%x" % (object_id << 1)}\n" + # rubocop:disable Style/StringConcatenation, Style/FormatString
37
+ instance_variables
38
+ .map { |k| k.to_s.sub("@", "") }
39
+ .filter { |k| k != "data" and k != "client" and respond_to? k }
40
+ .map { |k| " #{k}=#{send(k).inspect}" }
41
+ .join("\n") + ">"
42
+ end
43
+
44
+ def self.date_accessor(*attrs)
45
+ attrs.each do |attr|
46
+ define_method(attr) do
47
+ value = instance_variable_get(:"@#{attr}")
48
+
49
+ raise "No value for #{attr} set" if value.nil?
50
+
51
+ parse_datetime(value)
52
+ end
53
+ end
54
+ end
55
+
56
+ protected
57
+
58
+ def parse_datetime(value)
59
+ Time.parse(value)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Seam
6
+ class DeepHashAccessor
7
+ def initialize(data)
8
+ @data = data
9
+ create_accessor_methods
10
+ end
11
+
12
+ def [](key)
13
+ instance_variable_get(:"@#{key}")
14
+ end
15
+
16
+ private
17
+
18
+ def create_accessor_methods
19
+ @data.each do |key, value|
20
+ define_singleton_method(key) do
21
+ process_value(value)
22
+ end
23
+ end
24
+ end
25
+
26
+ def process_value(value)
27
+ case value
28
+ when Hash
29
+ DeepHashAccessor.new(value)
30
+ when Array
31
+ value.map { |v| process_value(v) }
32
+ else
33
+ value
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seam
4
+ DEFAULT_ENDPOINT = "https://connect.getseam.com"
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seam
4
+ module Helpers
5
+ module ActionAttempt
6
+ def self.decide_and_wait(action_attempt, client, wait_for_action_attempt)
7
+ if wait_for_action_attempt == true
8
+ return wait_until_finished(action_attempt, client)
9
+ elsif wait_for_action_attempt.is_a?(Hash)
10
+ return wait_until_finished(action_attempt, client, timeout: wait_for_action_attempt[:timeout],
11
+ polling_interval: wait_for_action_attempt[:polling_interval])
12
+ end
13
+
14
+ action_attempt
15
+ end
16
+
17
+ def self.wait_until_finished(action_attempt, client, timeout: nil, polling_interval: nil)
18
+ timeout = timeout.nil? ? 5.0 : timeout
19
+ polling_interval = polling_interval.nil? ? 0.5 : polling_interval
20
+
21
+ time_waiting = 0.0
22
+
23
+ while action_attempt.status == "pending"
24
+ sleep(polling_interval)
25
+ time_waiting += polling_interval
26
+
27
+ raise Seam::ActionAttemptTimeoutError.new(action_attempt, timeout) if time_waiting > timeout
28
+
29
+ action_attempt = update_action_attempt(action_attempt, client)
30
+ end
31
+
32
+ raise Seam::ActionAttemptFailedError.new(action_attempt) if action_attempt.status == "error"
33
+
34
+ action_attempt
35
+ end
36
+
37
+ def self.update_action_attempt(action_attempt, client)
38
+ response = client.get("/action_attempts/get", {action_attempt_id: action_attempt.action_attempt_id})
39
+
40
+ action_attempt.update_from_response(response.body["action_attempt"])
41
+ action_attempt
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/seam/http.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "http_single_workspace"
4
+
5
+ module Seam
6
+ module Http
7
+ def self.new(**args)
8
+ Http::SingleWorkspace.new(**args)
9
+ end
10
+
11
+ def self.from_api_key(api_key, endpoint: nil, wait_for_action_attempt: false)
12
+ Http::SingleWorkspace.from_api_key(api_key, endpoint: endpoint, wait_for_action_attempt: wait_for_action_attempt)
13
+ end
14
+
15
+ def self.from_personal_access_token(personal_access_token, workspace_id, endpoint: nil, wait_for_action_attempt: false)
16
+ Http::SingleWorkspace.from_personal_access_token(personal_access_token, workspace_id, endpoint: endpoint,
17
+ wait_for_action_attempt: wait_for_action_attempt)
18
+ end
19
+
20
+ class ApiError < StandardError
21
+ attr_reader :code, :status_code, :request_id, :data
22
+
23
+ def initialize(error, status_code, request_id)
24
+ super(error[:message])
25
+ @code = error[:type]
26
+ @status_code = status_code
27
+ @request_id = request_id
28
+ @data = error[:data]
29
+ end
30
+ end
31
+
32
+ class UnauthorizedError < ApiError
33
+ def initialize(request_id)
34
+ super({type: "unauthorized", message: "Unauthorized"}, 401, request_id)
35
+ end
36
+ end
37
+
38
+ class InvalidInputError < ApiError
39
+ attr_reader :validation_errors
40
+
41
+ def initialize(error, status_code, request_id)
42
+ super
43
+ @code = "invalid_input"
44
+ @validation_errors = error["validation_errors"] || {}
45
+ end
46
+
47
+ def get_validation_error_messages(param_name)
48
+ @validation_errors.dig(param_name, "_errors") || []
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "parse_options"
5
+ require_relative "lts_version"
6
+ require_relative "auth"
7
+
8
+ module Seam
9
+ module Http
10
+ class MultiWorkspace
11
+ attr_reader :client, :defaults
12
+
13
+ def initialize(personal_access_token:, endpoint: nil, wait_for_action_attempt: true, faraday_options: {},
14
+ faraday_retry_options: {})
15
+ @wait_for_action_attempt = wait_for_action_attempt
16
+ @defaults = {"wait_for_action_attempt" => wait_for_action_attempt}
17
+ @endpoint = Http::Options.get_endpoint(endpoint)
18
+ @auth_headers = Http::Auth.get_auth_headers_for_multi_workspace_personal_access_token(personal_access_token)
19
+ @client = Seam::Http::Request.create_faraday_client(@endpoint, @auth_headers, faraday_options,
20
+ faraday_retry_options)
21
+ end
22
+
23
+ def self.lts_version
24
+ Seam::LTS_VERSION
25
+ end
26
+
27
+ def lts_version
28
+ Seam::LTS_VERSION
29
+ end
30
+
31
+ def workspaces
32
+ @workspaces ||= WorkspacesProxy.new(Seam::Clients::Workspaces.new(client: @client, defaults: @defaults))
33
+ end
34
+
35
+ def self.from_personal_access_token(personal_access_token, endpoint: nil, wait_for_action_attempt: true, faraday_options: {}, faraday_retry_options: {})
36
+ new(
37
+ personal_access_token: personal_access_token,
38
+ endpoint: endpoint,
39
+ wait_for_action_attempt: wait_for_action_attempt,
40
+ faraday_options: faraday_options,
41
+ faraday_retry_options: faraday_retry_options
42
+ )
43
+ end
44
+ end
45
+
46
+ class WorkspacesProxy
47
+ def initialize(workspaces)
48
+ @workspaces = workspaces
49
+ end
50
+
51
+ def list(**kwargs)
52
+ @workspaces.list(**kwargs)
53
+ end
54
+
55
+ def create(**kwargs)
56
+ @workspaces.create(**kwargs)
57
+ end
58
+ end
59
+
60
+ private_constant :WorkspacesProxy
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "parse_options"
5
+ require_relative "routes/routes"
6
+
7
+ module Seam
8
+ module Http
9
+ class SingleWorkspace
10
+ include Seam::Routes
11
+
12
+ attr_reader :client, :defaults
13
+
14
+ def initialize(client: nil, api_key: nil, personal_access_token: nil, workspace_id: nil, endpoint: nil,
15
+ wait_for_action_attempt: true, faraday_options: {}, faraday_retry_options: {})
16
+ options = Http::Options.parse_options(api_key: api_key, personal_access_token: personal_access_token,
17
+ workspace_id: workspace_id, endpoint: endpoint)
18
+ @endpoint = options[:endpoint]
19
+ @auth_headers = options[:auth_headers]
20
+ @defaults = Seam::DeepHashAccessor.new({"wait_for_action_attempt" => wait_for_action_attempt})
21
+ @client = client || Seam::Http::Request.create_faraday_client(@endpoint, @auth_headers, faraday_options,
22
+ faraday_retry_options)
23
+
24
+ initialize_routes(client: @client, defaults: @defaults)
25
+ end
26
+
27
+ def lts_version
28
+ Seam::LTS_VERSION
29
+ end
30
+
31
+ def self.from_api_key(api_key, endpoint: nil, wait_for_action_attempt: false, faraday_options: {}, faraday_retry_options: {})
32
+ new(api_key: api_key, endpoint: endpoint, wait_for_action_attempt: wait_for_action_attempt,
33
+ faraday_options: faraday_options, faraday_retry_options: faraday_retry_options)
34
+ end
35
+
36
+ def self.from_personal_access_token(personal_access_token, workspace_id, endpoint: nil, wait_for_action_attempt: false, faraday_options: {}, faraday_retry_options: {})
37
+ new(personal_access_token: personal_access_token, workspace_id: workspace_id, endpoint: endpoint,
38
+ wait_for_action_attempt: wait_for_action_attempt, faraday_options: faraday_options, faraday_retry_options: faraday_retry_options)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "default_endpoint"
4
+
5
+ module Seam
6
+ module Http
7
+ module Options
8
+ def self.get_endpoint(endpoint = nil)
9
+ endpoint || get_endpoint_from_env || Seam::DEFAULT_ENDPOINT
10
+ end
11
+
12
+ def self.get_endpoint_from_env
13
+ seam_api_url = ENV["SEAM_API_URL"]
14
+ seam_endpoint = ENV["SEAM_ENDPOINT"]
15
+
16
+ if seam_api_url
17
+ warn "\033[93mUsing the SEAM_API_URL environment variable is deprecated. Support will be removed in a later major version. Use SEAM_ENDPOINT instead.\033[0m"
18
+ end
19
+
20
+ if seam_api_url && seam_endpoint
21
+ warn "\033[93mDetected both the SEAM_API_URL and SEAM_ENDPOINT environment variables. Using SEAM_ENDPOINT.\033[0m"
22
+ end
23
+
24
+ seam_endpoint || seam_api_url
25
+ end
26
+
27
+ class SeamInvalidOptionsError < StandardError
28
+ def initialize(message)
29
+ super("Seam received invalid options: #{message}")
30
+ end
31
+ end
32
+
33
+ def self.seam_http_options_with_api_key?(api_key: nil, personal_access_token: nil)
34
+ return false if api_key.nil?
35
+
36
+ if personal_access_token
37
+ raise SeamInvalidOptionsError.new(
38
+ "The personal_access_token option cannot be used with the api_key option"
39
+ )
40
+ end
41
+
42
+ true
43
+ end
44
+
45
+ def self.seam_http_options_with_personal_access_token?(personal_access_token: nil, api_key: nil, workspace_id: nil)
46
+ return false if personal_access_token.nil?
47
+
48
+ if api_key
49
+ raise SeamInvalidOptionsError.new(
50
+ "The api_key option cannot be used with the personal_access_token option"
51
+ )
52
+ end
53
+
54
+ if workspace_id.nil?
55
+ raise SeamInvalidOptionsError.new(
56
+ "Must pass a workspace_id when using a personal_access_token"
57
+ )
58
+ end
59
+
60
+ true
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "auth"
4
+ require_relative "options"
5
+
6
+ module Seam
7
+ module Http
8
+ module Options
9
+ def self.parse_options(api_key: nil, personal_access_token: nil, workspace_id: nil, endpoint: nil)
10
+ api_key ||= ENV["SEAM_API_KEY"] if personal_access_token.nil?
11
+
12
+ auth_headers = Http::Auth.get_auth_headers(
13
+ api_key: api_key,
14
+ personal_access_token: personal_access_token,
15
+ workspace_id: workspace_id
16
+ )
17
+ endpoint = Http::Options.get_endpoint(endpoint)
18
+
19
+ {auth_headers: auth_headers, endpoint: endpoint}
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/seam/request.rb CHANGED
@@ -1,73 +1,104 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http"
3
+ require "faraday"
4
+ require "faraday/retry"
4
5
 
5
6
  module Seam
6
- class Request
7
- attr_reader :base_uri, :api_key, :debug
8
-
9
- class Error < StandardError
10
- attr_reader :status, :response
7
+ module Http
8
+ module Request
9
+ def self.create_faraday_client(endpoint, auth_headers, faraday_options = {}, faraday_retry_options = {})
10
+ default_options = {
11
+ url: endpoint,
12
+ headers: auth_headers.merge(default_headers)
13
+ }
14
+
15
+ options = deep_merge(default_options, faraday_options)
16
+
17
+ default_faraday_retry_options = {
18
+ max: 2,
19
+ backoff_factor: 2
20
+ }
21
+
22
+ faraday_retry_options = default_faraday_retry_options.merge(faraday_retry_options)
23
+
24
+ Faraday.new(options) do |builder|
25
+ builder.request :json
26
+ builder.response :json
27
+ builder.use ResponseMiddleware
28
+ builder.request :retry, faraday_retry_options
29
+ end
30
+ end
11
31
 
12
- def initialize(message, status, response)
13
- super(message)
14
- @status = status
15
- @response = response
32
+ def self.default_headers
33
+ {
34
+ "User-Agent" => "seam-ruby/#{Seam::VERSION}",
35
+ "Content-Type" => "application/json",
36
+ :"seam-sdk-name" => "seamapi/ruby",
37
+ :"seam-sdk-version" => Seam::VERSION,
38
+ :"seam-lts-version" => Seam::LTS_VERSION
39
+ }
16
40
  end
17
- end
18
41
 
19
- def initialize(api_key:, base_uri:, debug: false)
20
- @api_key = api_key
21
- @base_uri = base_uri
22
- @debug = debug
23
- end
42
+ class ResponseMiddleware < Faraday::Response::RaiseError
43
+ def on_complete(env)
44
+ return if env.success?
24
45
 
25
- def perform(method, uri, config = {})
26
- Logger.info("Request: #{method} #{uri} #{config}") if debug
46
+ status_code = env.status
47
+ request_id = env.response_headers["seam-request-id"]
27
48
 
28
- config[:body] = config[:body].to_json if config[:body]
49
+ raise Http::UnauthorizedError.new(request_id) if status_code == 401
29
50
 
30
- response = HTTP.request(
31
- method,
32
- build_url(uri),
33
- {headers: headers}.merge(config)
34
- )
51
+ if seam_api_error_response?(env)
52
+ body = JSON.parse(env.body)
53
+ error = body["error"]
54
+ error_details = {
55
+ type: error["type"] || "unknown_error",
56
+ message: error["message"] || "Unknown error",
57
+ data: error["data"]
58
+ }
35
59
 
36
- return response.parse if response.status.success?
60
+ if error["type"] == "invalid_input"
61
+ error_details["validation_errors"] = error["validation_errors"]
62
+ raise Http::InvalidInputError.new(error_details, status_code, request_id)
63
+ end
37
64
 
38
- handle_error_response(response, method, uri)
39
- end
65
+ raise Http::ApiError.new(error_details, status_code, request_id)
66
+ end
40
67
 
41
- protected
68
+ super
69
+ end
42
70
 
43
- def handle_error_response(response, method, uri)
44
- msg = "Api Error #{response.status.code} #{method} #{uri}"
45
- code = response.status.code
71
+ def seam_api_error_response?(env)
72
+ return false unless env.response_headers
46
73
 
47
- if code >= 400 && code < 500 && (err = response.parse["error"])
48
- msg = "Api Error #{err["type"]}\nrequest_id: #{err["request_id"]}\n#{err["message"]}"
49
- end
74
+ content_type = env.response_headers["Content-Type"]
75
+ return false unless content_type&.start_with?("application/json")
50
76
 
51
- raise Error.new(msg, code, response)
52
- end
77
+ begin
78
+ body = JSON.parse(env.body)
79
+ return false unless body.is_a?(Hash) && body["error"].is_a?(Hash)
53
80
 
54
- def build_url(uri)
55
- "#{base_uri}#{uri}"
56
- end
81
+ error = body["error"]
82
+ error["type"].is_a?(String) && error["message"].is_a?(String)
83
+ rescue JSON::ParserError
84
+ false
85
+ end
86
+ end
87
+ end
57
88
 
58
- def headers
59
- {
60
- "User-Agent" => user_agent,
61
- "Content-Type" => "application/json",
62
- "Authorization" => "Bearer #{api_key}",
63
- :"seam-sdk-name" => "seamapi/ruby",
64
- :"seam-sdk-version" => Seam::VERSION,
65
- :"seam-lts-version" => Seam::LTS_VERSION
66
- }
67
- end
89
+ def self.deep_merge(hash1, hash2)
90
+ result = hash1.dup
91
+ hash2.each do |key, value|
92
+ result[key] = if value.is_a?(Hash) && result[key].is_a?(Hash)
93
+ deep_merge(result[key], value)
94
+ else
95
+ value
96
+ end
97
+ end
98
+ result
99
+ end
68
100
 
69
- def user_agent
70
- "seam-ruby/#{Seam::VERSION}"
101
+ private_class_method :deep_merge
71
102
  end
72
103
  end
73
104
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seam
4
+ module Clients
5
+ class AccessCodes
6
+ def initialize(client:, defaults:)
7
+ @client = client
8
+ @defaults = defaults
9
+ end
10
+
11
+ def simulate
12
+ @simulate ||= Seam::Clients::AccessCodesSimulate.new(client: @client, defaults: @defaults)
13
+ end
14
+
15
+ def unmanaged
16
+ @unmanaged ||= Seam::Clients::AccessCodesUnmanaged.new(client: @client, defaults: @defaults)
17
+ end
18
+
19
+ def create(device_id:, allow_external_modification: nil, attempt_for_offline_device: nil, code: nil, common_code_key: nil, ends_at: nil, is_external_modification_allowed: nil, is_offline_access_code: nil, is_one_time_use: nil, max_time_rounding: nil, name: nil, prefer_native_scheduling: nil, preferred_code_length: nil, starts_at: nil, sync: nil, use_backup_access_code_pool: nil, use_offline_access_code: nil)
20
+ res = @client.post("/access_codes/create", {device_id: device_id, allow_external_modification: allow_external_modification, attempt_for_offline_device: attempt_for_offline_device, code: code, common_code_key: common_code_key, ends_at: ends_at, is_external_modification_allowed: is_external_modification_allowed, is_offline_access_code: is_offline_access_code, is_one_time_use: is_one_time_use, max_time_rounding: max_time_rounding, name: name, prefer_native_scheduling: prefer_native_scheduling, preferred_code_length: preferred_code_length, starts_at: starts_at, sync: sync, use_backup_access_code_pool: use_backup_access_code_pool, use_offline_access_code: use_offline_access_code}.compact)
21
+
22
+ Seam::Resources::AccessCode.load_from_response(res.body["access_code"])
23
+ end
24
+
25
+ def create_multiple(device_ids:, allow_external_modification: nil, attempt_for_offline_device: nil, behavior_when_code_cannot_be_shared: nil, code: nil, ends_at: nil, is_external_modification_allowed: nil, is_offline_access_code: nil, is_one_time_use: nil, max_time_rounding: nil, name: nil, prefer_native_scheduling: nil, preferred_code_length: nil, starts_at: nil, use_backup_access_code_pool: nil, use_offline_access_code: nil)
26
+ res = @client.post("/access_codes/create_multiple", {device_ids: device_ids, allow_external_modification: allow_external_modification, attempt_for_offline_device: attempt_for_offline_device, behavior_when_code_cannot_be_shared: behavior_when_code_cannot_be_shared, code: code, ends_at: ends_at, is_external_modification_allowed: is_external_modification_allowed, is_offline_access_code: is_offline_access_code, is_one_time_use: is_one_time_use, max_time_rounding: max_time_rounding, name: name, prefer_native_scheduling: prefer_native_scheduling, preferred_code_length: preferred_code_length, starts_at: starts_at, use_backup_access_code_pool: use_backup_access_code_pool, use_offline_access_code: use_offline_access_code}.compact)
27
+
28
+ Seam::Resources::AccessCode.load_from_response(res.body["access_codes"])
29
+ end
30
+
31
+ def delete(access_code_id:, device_id: nil, sync: nil)
32
+ @client.post("/access_codes/delete", {access_code_id: access_code_id, device_id: device_id, sync: sync}.compact)
33
+
34
+ nil
35
+ end
36
+
37
+ def generate_code(device_id:)
38
+ res = @client.post("/access_codes/generate_code", {device_id: device_id}.compact)
39
+
40
+ Seam::Resources::AccessCode.load_from_response(res.body["generated_code"])
41
+ end
42
+
43
+ def get(access_code_id: nil, code: nil, device_id: nil)
44
+ res = @client.post("/access_codes/get", {access_code_id: access_code_id, code: code, device_id: device_id}.compact)
45
+
46
+ Seam::Resources::AccessCode.load_from_response(res.body["access_code"])
47
+ end
48
+
49
+ def list(access_code_ids: nil, device_id: nil, user_identifier_key: nil)
50
+ res = @client.post("/access_codes/list", {access_code_ids: access_code_ids, device_id: device_id, user_identifier_key: user_identifier_key}.compact)
51
+
52
+ Seam::Resources::AccessCode.load_from_response(res.body["access_codes"])
53
+ end
54
+
55
+ def pull_backup_access_code(access_code_id:)
56
+ res = @client.post("/access_codes/pull_backup_access_code", {access_code_id: access_code_id}.compact)
57
+
58
+ Seam::Resources::AccessCode.load_from_response(res.body["backup_access_code"])
59
+ end
60
+
61
+ def update(access_code_id:, allow_external_modification: nil, attempt_for_offline_device: nil, code: nil, device_id: nil, ends_at: nil, is_external_modification_allowed: nil, is_managed: nil, is_offline_access_code: nil, is_one_time_use: nil, max_time_rounding: nil, name: nil, prefer_native_scheduling: nil, preferred_code_length: nil, starts_at: nil, sync: nil, type: nil, use_backup_access_code_pool: nil, use_offline_access_code: nil)
62
+ @client.post("/access_codes/update", {access_code_id: access_code_id, allow_external_modification: allow_external_modification, attempt_for_offline_device: attempt_for_offline_device, code: code, device_id: device_id, ends_at: ends_at, is_external_modification_allowed: is_external_modification_allowed, is_managed: is_managed, is_offline_access_code: is_offline_access_code, is_one_time_use: is_one_time_use, max_time_rounding: max_time_rounding, name: name, prefer_native_scheduling: prefer_native_scheduling, preferred_code_length: preferred_code_length, starts_at: starts_at, sync: sync, type: type, use_backup_access_code_pool: use_backup_access_code_pool, use_offline_access_code: use_offline_access_code}.compact)
63
+
64
+ nil
65
+ end
66
+
67
+ def update_multiple(common_code_key:, allow_external_modification: nil, code: nil, ends_at: nil, is_external_modification_allowed: nil, name: nil, prefer_native_scheduling: nil, starts_at: nil)
68
+ @client.post("/access_codes/update_multiple", {common_code_key: common_code_key, allow_external_modification: allow_external_modification, code: code, ends_at: ends_at, is_external_modification_allowed: is_external_modification_allowed, name: name, prefer_native_scheduling: prefer_native_scheduling, starts_at: starts_at}.compact)
69
+
70
+ nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seam
4
+ module Clients
5
+ class AccessCodesSimulate
6
+ def initialize(client:, defaults:)
7
+ @client = client
8
+ @defaults = defaults
9
+ end
10
+
11
+ def create_unmanaged_access_code(code:, device_id:, name:)
12
+ res = @client.post("/access_codes/simulate/create_unmanaged_access_code", {code: code, device_id: device_id, name: name}.compact)
13
+
14
+ Seam::Resources::UnmanagedAccessCode.load_from_response(res.body["access_code"])
15
+ end
16
+ end
17
+ end
18
+ end