seam 2.0.0.prerelease.1 → 2.0.0rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +49 -53
  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 +65 -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 +47 -0
  10. data/lib/seam/http.rb +52 -0
  11. data/lib/seam/http_multi_workspace.rb +66 -0
  12. data/lib/seam/http_single_workspace.rb +46 -0
  13. data/lib/seam/options.rb +64 -0
  14. data/lib/seam/parse_options.rb +23 -0
  15. data/lib/seam/request.rb +84 -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 +23 -0
  89. data/lib/seam.rb +19 -68
  90. metadata +115 -71
  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/logger.rb +0 -12
  126. data/lib/seam/resources/access_code.rb +0 -12
  127. data/lib/seam/resources/acs_access_group.rb +0 -9
  128. data/lib/seam/resources/acs_credential.rb +0 -12
  129. data/lib/seam/resources/acs_credential_pool.rb +0 -9
  130. data/lib/seam/resources/acs_credential_provisioning_automation.rb +0 -9
  131. data/lib/seam/resources/acs_entrance.rb +0 -9
  132. data/lib/seam/resources/acs_system.rb +0 -9
  133. data/lib/seam/resources/acs_user.rb +0 -9
  134. data/lib/seam/resources/action_attempt.rb +0 -46
  135. data/lib/seam/resources/base_resource.rb +0 -58
  136. data/lib/seam/resources/client_session.rb +0 -9
  137. data/lib/seam/resources/climate_setting_schedule.rb +0 -11
  138. data/lib/seam/resources/connect_webview.rb +0 -9
  139. data/lib/seam/resources/connected_account.rb +0 -12
  140. data/lib/seam/resources/device.rb +0 -12
  141. data/lib/seam/resources/device_provider.rb +0 -7
  142. data/lib/seam/resources/enrollment_automation.rb +0 -9
  143. data/lib/seam/resources/event.rb +0 -9
  144. data/lib/seam/resources/network.rb +0 -9
  145. data/lib/seam/resources/noise_threshold.rb +0 -7
  146. data/lib/seam/resources/phone.rb +0 -12
  147. data/lib/seam/resources/resource_error.rb +0 -9
  148. data/lib/seam/resources/resource_errors_support.rb +0 -9
  149. data/lib/seam/resources/resource_warning.rb +0 -9
  150. data/lib/seam/resources/resource_warnings_support.rb +0 -9
  151. data/lib/seam/resources/service_health.rb +0 -7
  152. data/lib/seam/resources/unmanaged_access_code.rb +0 -12
  153. data/lib/seam/resources/unmanaged_device.rb +0 -12
  154. data/lib/seam/resources/user_identity.rb +0 -9
  155. data/lib/seam/resources/webhook.rb +0 -7
  156. data/lib/seam/resources/workspace.rb +0 -7
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require_relative "deep_hash_accessor"
5
+
6
+ module Seam
7
+ module Resources
8
+ class BaseResource
9
+ attr_accessor :data, :client
10
+
11
+ def initialize(data, client = nil)
12
+ @data = data
13
+ @client = client
14
+
15
+ @data.each do |key, value|
16
+ value = Seam::DeepHashAccessor.new(value) if value.is_a?(Hash)
17
+ instance_variable_set(:"@#{key}", value)
18
+ end
19
+ end
20
+
21
+ def update_from_response(data)
22
+ @data = data
23
+ @data.each do |key, value|
24
+ value = Seam::DeepHashAccessor.new(value) if value.is_a?(Hash)
25
+ instance_variable_set(:"@#{key}", value)
26
+ end
27
+ end
28
+
29
+ def self.load_from_response(data, client = nil)
30
+ if data.is_a?(Array)
31
+ data.map { |d| new(d, client) }
32
+ else
33
+ new(data, client)
34
+ end
35
+ end
36
+
37
+ def inspect
38
+ "<#{self.class.name}:#{"0x00%x" % (object_id << 1)}\n" + # rubocop:disable Style/StringConcatenation, Style/FormatString
39
+ instance_variables
40
+ .map { |k| k.to_s.sub("@", "") }
41
+ .filter { |k| k != "data" and k != "client" and respond_to? k }
42
+ .map { |k| " #{k}=#{send(k).inspect}" }
43
+ .join("\n") + ">"
44
+ end
45
+
46
+ def self.date_accessor(*attrs)
47
+ attrs.each do |attr|
48
+ define_method(attr) do
49
+ value = instance_variable_get(:"@#{attr}")
50
+
51
+ raise "No value for #{attr} set" if value.nil?
52
+
53
+ parse_datetime(value)
54
+ end
55
+ end
56
+ end
57
+
58
+ protected
59
+
60
+ def parse_datetime(value)
61
+ Time.parse(value)
62
+ end
63
+ end
64
+ end
65
+ 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,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../wait_for_action_attempt"
4
+
5
+ module Seam
6
+ module Helpers
7
+ module ActionAttempt
8
+ def self.decide_and_wait(action_attempt, client, wait_for_action_attempt)
9
+ if wait_for_action_attempt == true
10
+ return wait_until_finished(action_attempt, client)
11
+ elsif wait_for_action_attempt.is_a?(Hash)
12
+ return wait_until_finished(action_attempt, client, timeout: wait_for_action_attempt[:timeout],
13
+ polling_interval: wait_for_action_attempt[:polling_interval])
14
+ end
15
+
16
+ action_attempt
17
+ end
18
+
19
+ def self.wait_until_finished(action_attempt, client, timeout: nil, polling_interval: nil)
20
+ timeout = timeout.nil? ? 5.0 : timeout
21
+ polling_interval = polling_interval.nil? ? 0.5 : polling_interval
22
+
23
+ time_waiting = 0.0
24
+
25
+ while action_attempt.status == "pending"
26
+ sleep(polling_interval)
27
+ time_waiting += polling_interval
28
+
29
+ raise Seam::ActionAttemptTimeoutError.new(action_attempt, timeout) if time_waiting > timeout
30
+
31
+ action_attempt = update_action_attempt(action_attempt, client)
32
+ end
33
+
34
+ raise Seam::ActionAttemptFailedError.new(action_attempt) if action_attempt.status == "error"
35
+
36
+ action_attempt
37
+ end
38
+
39
+ def self.update_action_attempt(action_attempt, client)
40
+ response = client.get("/action_attempts/get", {action_attempt_id: action_attempt.action_attempt_id})
41
+
42
+ action_attempt.update_from_response(response.body["action_attempt"])
43
+ action_attempt
44
+ end
45
+ end
46
+ end
47
+ 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,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "parse_options"
5
+ require_relative "lts_version"
6
+ require_relative "version"
7
+ require_relative "auth"
8
+ require_relative "routes/resources/index"
9
+ require_relative "routes/clients/index"
10
+ require_relative "routes/routes"
11
+
12
+ module Seam
13
+ module Http
14
+ class MultiWorkspace
15
+ attr_reader :client, :defaults
16
+
17
+ def initialize(personal_access_token:, endpoint: nil, wait_for_action_attempt: true, faraday_options: {},
18
+ faraday_retry_options: {})
19
+ @wait_for_action_attempt = wait_for_action_attempt
20
+ @defaults = {"wait_for_action_attempt" => wait_for_action_attempt}
21
+ @endpoint = Http::Options.get_endpoint(endpoint)
22
+ @auth_headers = Http::Auth.get_auth_headers_for_multi_workspace_personal_access_token(personal_access_token)
23
+ @client = Http::Request.create_faraday_client(@endpoint, @auth_headers, faraday_options,
24
+ faraday_retry_options)
25
+ end
26
+
27
+ def self.lts_version
28
+ Seam::LTS_VERSION
29
+ end
30
+
31
+ def lts_version
32
+ Seam::LTS_VERSION
33
+ end
34
+
35
+ def workspaces
36
+ @workspaces ||= WorkspacesProxy.new(Seam::Clients::Workspaces.new(client: @client, defaults: @defaults))
37
+ end
38
+
39
+ def self.from_personal_access_token(personal_access_token, endpoint: nil, wait_for_action_attempt: true, faraday_options: {}, faraday_retry_options: {})
40
+ new(
41
+ personal_access_token: personal_access_token,
42
+ endpoint: endpoint,
43
+ wait_for_action_attempt: wait_for_action_attempt,
44
+ faraday_options: faraday_options,
45
+ faraday_retry_options: faraday_retry_options
46
+ )
47
+ end
48
+ end
49
+
50
+ class WorkspacesProxy
51
+ def initialize(workspaces)
52
+ @workspaces = workspaces
53
+ end
54
+
55
+ def list(**kwargs)
56
+ @workspaces.list(**kwargs)
57
+ end
58
+
59
+ def create(**kwargs)
60
+ @workspaces.create(**kwargs)
61
+ end
62
+ end
63
+
64
+ private_constant :WorkspacesProxy
65
+ end
66
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "parse_options"
5
+ require_relative "routes/resources/index"
6
+ require_relative "routes/clients/index"
7
+ require_relative "routes/routes"
8
+ require_relative "version"
9
+ require_relative "deep_hash_accessor"
10
+
11
+ module Seam
12
+ module Http
13
+ class SingleWorkspace
14
+ include Seam::Routes
15
+
16
+ attr_reader :client, :defaults
17
+
18
+ def initialize(client: nil, api_key: nil, personal_access_token: nil, workspace_id: nil, endpoint: nil,
19
+ wait_for_action_attempt: true, faraday_options: {}, faraday_retry_options: {})
20
+ options = Http::Options.parse_options(api_key: api_key, personal_access_token: personal_access_token,
21
+ workspace_id: workspace_id, endpoint: endpoint)
22
+ @endpoint = options[:endpoint]
23
+ @auth_headers = options[:auth_headers]
24
+ @defaults = Seam::DeepHashAccessor.new({"wait_for_action_attempt" => wait_for_action_attempt})
25
+ @client = client || Http::Request.create_faraday_client(@endpoint, @auth_headers, faraday_options,
26
+ faraday_retry_options)
27
+
28
+ initialize_routes(client: @client, defaults: @defaults)
29
+ end
30
+
31
+ def lts_version
32
+ Seam::LTS_VERSION
33
+ end
34
+
35
+ def self.from_api_key(api_key, endpoint: nil, wait_for_action_attempt: false, faraday_options: {}, faraday_retry_options: {})
36
+ new(api_key: api_key, endpoint: endpoint, wait_for_action_attempt: wait_for_action_attempt,
37
+ faraday_options: faraday_options, faraday_retry_options: faraday_retry_options)
38
+ end
39
+
40
+ def self.from_personal_access_token(personal_access_token, workspace_id, endpoint: nil, wait_for_action_attempt: false, faraday_options: {}, faraday_retry_options: {})
41
+ new(personal_access_token: personal_access_token, workspace_id: workspace_id, endpoint: endpoint,
42
+ wait_for_action_attempt: wait_for_action_attempt, faraday_options: faraday_options, faraday_retry_options: faraday_retry_options)
43
+ end
44
+ end
45
+ end
46
+ 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,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http"
3
+ require "faraday"
4
+ require "faraday/retry"
5
+ require_relative "lts_version"
6
+ require_relative "version"
4
7
 
5
8
  module Seam
6
- class Request
7
- attr_reader :base_uri, :api_key, :debug
8
-
9
- class Error < StandardError
10
- attr_reader :status, :response
9
+ module Http
10
+ module Request
11
+ def self.create_faraday_client(endpoint, auth_headers, faraday_options = {}, faraday_retry_options = {})
12
+ default_options = {
13
+ url: endpoint,
14
+ headers: auth_headers.merge(default_headers)
15
+ }
16
+
17
+ options = deep_merge(default_options, faraday_options)
18
+
19
+ default_faraday_retry_options = {
20
+ max: 2,
21
+ backoff_factor: 2
22
+ }
23
+
24
+ faraday_retry_options = default_faraday_retry_options.merge(faraday_retry_options)
25
+
26
+ Faraday.new(options) do |builder|
27
+ builder.request :json
28
+ builder.response :json
29
+ builder.use ResponseMiddleware
30
+ builder.request :retry, faraday_retry_options
31
+ end
32
+ end
11
33
 
12
- def initialize(message, status, response)
13
- super(message)
14
- @status = status
15
- @response = response
34
+ def self.default_headers
35
+ {
36
+ "User-Agent" => "seam-ruby/#{Seam::VERSION}",
37
+ "Content-Type" => "application/json",
38
+ :"seam-sdk-name" => "seamapi/ruby",
39
+ :"seam-sdk-version" => Seam::VERSION,
40
+ :"seam-lts-version" => Seam::LTS_VERSION
41
+ }
16
42
  end
17
- end
18
43
 
19
- def initialize(api_key:, base_uri:, debug: false)
20
- @api_key = api_key
21
- @base_uri = base_uri
22
- @debug = debug
23
- end
44
+ class ResponseMiddleware < Faraday::Response::RaiseError
45
+ def on_complete(env)
46
+ return if env.success?
24
47
 
25
- def perform(method, uri, config = {})
26
- Logger.info("Request: #{method} #{uri} #{config}") if debug
48
+ status_code = env.status
49
+ request_id = env.response_headers["seam-request-id"]
27
50
 
28
- config[:body] = config[:body].to_json if config[:body]
51
+ raise Http::UnauthorizedError.new(request_id) if status_code == 401
29
52
 
30
- response = HTTP.request(
31
- method,
32
- build_url(uri),
33
- {headers: headers}.merge(config)
34
- )
53
+ if seam_api_error_response?(env)
54
+ body = JSON.parse(env.body)
55
+ error = body["error"]
56
+ error_details = {
57
+ type: error["type"] || "unknown_error",
58
+ message: error["message"] || "Unknown error",
59
+ data: error["data"]
60
+ }
35
61
 
36
- return response.parse if response.status.success?
62
+ if error["type"] == "invalid_input"
63
+ error_details["validation_errors"] = error["validation_errors"]
64
+ raise Http::InvalidInputError.new(error_details, status_code, request_id)
65
+ end
37
66
 
38
- handle_error_response(response, method, uri)
39
- end
67
+ raise Http::ApiError.new(error_details, status_code, request_id)
68
+ end
40
69
 
41
- protected
70
+ super
71
+ end
42
72
 
43
- def handle_error_response(response, method, uri)
44
- msg = "Api Error #{response.status.code} #{method} #{uri}"
45
- code = response.status.code
73
+ def seam_api_error_response?(env)
74
+ return false unless env.response_headers
46
75
 
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
76
+ content_type = env.response_headers["Content-Type"]
77
+ return false unless content_type&.start_with?("application/json")
50
78
 
51
- raise Error.new(msg, code, response)
52
- end
79
+ begin
80
+ body = JSON.parse(env.body)
81
+ return false unless body.is_a?(Hash) && body["error"].is_a?(Hash)
53
82
 
54
- def build_url(uri)
55
- "#{base_uri}#{uri}"
56
- end
83
+ error = body["error"]
84
+ error["type"].is_a?(String) && error["message"].is_a?(String)
85
+ rescue JSON::ParserError
86
+ false
87
+ end
88
+ end
89
+ end
57
90
 
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
91
+ def self.deep_merge(hash1, hash2)
92
+ result = hash1.dup
93
+ hash2.each do |key, value|
94
+ result[key] = if value.is_a?(Hash) && result[key].is_a?(Hash)
95
+ deep_merge(result[key], value)
96
+ else
97
+ value
98
+ end
99
+ end
100
+ result
101
+ end
68
102
 
69
- def user_agent
70
- "seam-ruby/#{Seam::VERSION}"
103
+ private_class_method :deep_merge
71
104
  end
72
105
  end
73
106
  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