alula-ruby 2.29.0 → 2.30.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: 1bc4313af51d05bbfa6b052d37686a460790479c7c6088ee572a1a5a29b74447
4
- data.tar.gz: f44486c2d746b6480a629fd4df9a419e7d4ab087d4b240f35a14f4e142071126
3
+ metadata.gz: e01ef49bc046ae1795d32869b79a7e4b92996c0d35e3cee3cfd2de8df534dcd2
4
+ data.tar.gz: 62d790ddc15244480f6ab1e08d5b77738a92aaeb94a6d286c2a82c9229694267
5
5
  SHA512:
6
- metadata.gz: 345ca2b0194f93b3e452c3511c7dcfca88bbc105865f5081e246c7d1f71dc61ea5ced3fdd66c17a345fde329664a51501c563ad0be18c32f4cf706ef5d31a4df
7
- data.tar.gz: 633a12588520c9a78a3eb6bc949a5639fae5dd485e097239b1590fe570de48b2619d7e51c3666ec036d9f4f6b5564f63245f3c550860194c90ca69bbea1a13a8
6
+ metadata.gz: 107146e1b827e0f7d8418ca3339d114c27f18750658e73af54e25b6190e65b0857983c93dd8862105b7ffdd2f039a0f1c142db6e2ae948c8b882d5912c561e2c
7
+ data.tar.gz: f095933709d5855fca510bfe3700409e6f1e97e2ab116dc1f9e88f2e845c94ec45ed2f41457536a03b4ab4038f23f5f9ad055b07127d6ebb7e8b49ffa626fb17
data/VERSION.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  | Version | Date | Description |
4
4
  | ------- | ------------| --------------------------------------------------------------------------- |
5
+ | v2.30.0 | 2025-12-16 | Adds M2MCentralStation and PermittedCentralStation |
5
6
  | v2.29.0 | 2025-12-01 | Adds User Impersonation Requests and new Meta |
6
7
  | v2.28.0 | 2025-11-19 | Add product_sku Device property |
7
8
  | v2.27.1 | 2025-11-14 | Add ONVIF feature support for CONNECT-FLX-DUAL and CONNECT-FLX-DUAL-Z devices |
data/lib/alula/client.rb CHANGED
@@ -18,8 +18,37 @@ module Alula
18
18
  end
19
19
 
20
20
  def request(http_method, resource_path, filters = {}, opts = {})
21
+ if resource_path.match(%r{^/m2m/})
22
+ request_m2m(http_method, resource_path, filters, opts)
23
+ else
24
+ request_alula(http_method, resource_path, filters, opts)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def request_m2m(http_method, resource_path, filters = {}, opts = {})
31
+ validate_request!(http_method, resource_path)
32
+ request_opts = build_options_m2m(http_method, resource_path, filters, opts)
33
+ api_url = config.api_url
34
+ request_resource_path = resource_path
35
+
36
+ # TODO: Handle network failures
37
+ response = make_request(http_method, api_url + request_resource_path, request_opts)
38
+
39
+ begin
40
+ resp = M2MResponse.from_httparty_response(response)
41
+ rescue JSON::ParserError
42
+ # TODO: Should be able to send better info up with this
43
+ raise 'Unable to decode JSON'
44
+ end
45
+
46
+ resp
47
+ end
48
+
49
+ def request_alula(http_method, resource_path, filters = {}, opts = {})
21
50
  validate_request!(http_method, resource_path) unless resource_path.match(%r{^/public/v1})
22
- request_opts = build_options(http_method, resource_path, filters, opts)
51
+ request_opts = build_options_alula(http_method, resource_path, filters, opts)
23
52
  api_url = video_request?(resource_path) ? config.video_api_url : config.api_url
24
53
  request_resource_path = video_request?(resource_path) ? resource_path.gsub(%r{^/video}, '') : resource_path
25
54
 
@@ -36,8 +65,6 @@ module Alula
36
65
  resp
37
66
  end
38
67
 
39
- private
40
-
41
68
  def validate_request!(http_method, resource_path)
42
69
  config.ensure_api_key_set
43
70
  config.ensure_api_url_set
@@ -64,7 +91,27 @@ module Alula
64
91
  resource_path.match(%r{^/video})
65
92
  end
66
93
 
67
- def build_options(http_method, resource_path, filters = {}, opts = {})
94
+ def build_options_m2m(http_method, resource_path, filters = {}, opts = {})
95
+ api_key = Alula::Client.config.impersonator_api_key || Alula::Client.config.api_key
96
+ request_opts = {
97
+ headers: {
98
+ 'Authorization': "Bearer #{api_key}",
99
+ 'User-Agent': "#{Alula::Client.config.user_agent || 'No Agent Set'}/alula-ruby v#{Alula::VERSION}"
100
+ }
101
+ }.merge(opts)
102
+
103
+ request_opts[:headers]['Content-Type'] = 'application/json'
104
+ request_opts[:body] = opts[:multipart] ? filters : JSON.generate(filters)
105
+
106
+ if Alula::Client.config.debug
107
+ request_opts[:debug_output] = Alula.logger
108
+ logger Alula.logger, :info
109
+ end
110
+
111
+ request_opts
112
+ end
113
+
114
+ def build_options_alula(http_method, resource_path, filters = {}, opts = {})
68
115
  api_key = Alula::Client.config.impersonator_api_key || Alula::Client.config.api_key
69
116
  request_opts = {
70
117
  headers: {
data/lib/alula/errors.rb CHANGED
@@ -231,4 +231,32 @@ module Alula
231
231
  @errors = errors
232
232
  end
233
233
  end
234
+
235
+ class M2MError < AlulaError
236
+ attr_reader :error_code
237
+
238
+ def self.for_response(response)
239
+ if response.data['Success'] == false
240
+ self.m2m_error_for_response(response)
241
+ else
242
+ super response
243
+ end
244
+ end
245
+
246
+ private
247
+
248
+ def self.m2m_error_for_response(response)
249
+ error_code = response.data['ErrorCode']
250
+ error = response.data['ErrorString']
251
+ message = self.build_message(error, error_code)
252
+ BadRequestError.new(message)
253
+ end
254
+
255
+ def self.build_message(error, code)
256
+ return error unless error.to_s.strip.empty?
257
+ return "M2M request failed with code #{code}" if code
258
+
259
+ 'M2M request failed'
260
+ end
261
+ end
234
262
  end
@@ -0,0 +1,22 @@
1
+ require_relative 'alula_response'
2
+
3
+ module Alula
4
+ class M2MResponse < AlulaResponse
5
+ attr_accessor :data, :http_body, :http_headers, :http_status, :raw, :rate_limit
6
+
7
+ def ok?
8
+ super && self.data['Success'] == true
9
+ end
10
+
11
+ def self.from_httparty_response(response)
12
+ resp = M2MResponse.new
13
+ resp.data = response.parsed_response
14
+ resp.http_body = response.body
15
+ resp.http_headers = response.headers
16
+ resp.rate_limit = Alula::RateLimit.new(response.headers)
17
+ resp.http_status = response.code
18
+ resp.raw = response
19
+ resp
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Alula
2
+ class M2MRestResource < ApiResource
3
+ def self.resource_url(id = nil)
4
+ if self == M2MRestResource
5
+ raise NotImplementedError, "Cannot call resource_url on a M2MRestResource. Try using M2MCentralStation instead."
6
+ end
7
+ base_path = "/m2m/bll/v3/external"
8
+ return base_path if id.nil?
9
+
10
+ [base_path, id].join('/').gsub(%r{/+}, '/').sub(%r{/$}, '')
11
+ end
12
+
13
+ def resource_url(id = nil)
14
+ self.class.resource_url(id)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Please forget about abstractions if working with M2M API. Endpoints are
4
+ # inconsistent and do not follow REST principles.
5
+ module Alula
6
+ class M2MCentralStation < Alula::M2MRestResource
7
+ extend Alula::ResourceAttributes
8
+
9
+ DEFAULT_PAGE_NUMBER = 1
10
+
11
+ field :app_name,
12
+ type: :string,
13
+ sortable: false,
14
+ filterable: true,
15
+ creatable_by: [],
16
+ patchable_by: []
17
+
18
+ field :primary_proxy,
19
+ type: :string,
20
+ sortable: false,
21
+ filterable: false,
22
+ creatable_by: [],
23
+ patchable_by: []
24
+
25
+ field :backup_proxy,
26
+ type: :string,
27
+ sortable: false,
28
+ filterable: false,
29
+ creatable_by: [],
30
+ patchable_by: []
31
+
32
+ class << self
33
+ def list(filters = {}, opts = {})
34
+ built_filters = {}
35
+ pagination = filters.delete(:page)
36
+ if pagination
37
+ built_filters['Page'] = pagination[:number]
38
+ built_filters['PageSize'] = pagination[:size]
39
+ end
40
+ response = Alula::Client.request(:post, resource_url('GetAvailableCentralStations'), built_filters, opts)
41
+ if response.ok?
42
+ list = construct_list_from_response(self, response.data.fetch('ResponseBody', {}), pagination)
43
+ list.rate_limit = response.rate_limit
44
+ list
45
+ else
46
+ error_class = M2MError.for_response(response)
47
+ raise error_class
48
+ end
49
+ end
50
+
51
+ def retrieve(id)
52
+ built_filters = {}
53
+ built_filters['AppName'] = id
54
+ response = Alula::Client.request(:post, self.resource_url('GetCentralStationProxies'), built_filters, {})
55
+ if response.ok?
56
+ item = build_model(self, response.data.fetch('ResponseBody', {}))
57
+ item.rate_limit = response.rate_limit
58
+ item
59
+ else
60
+ error_class = M2MError.for_response(response)
61
+ raise error_class
62
+ end
63
+ end
64
+
65
+ def method_missing(method, *args, &block)
66
+ query = QueryInterface.new(self)
67
+ return query.public_send(method, *args, &block) if query.respond_to?(method)
68
+
69
+ super
70
+ end
71
+
72
+ def respond_to_missing?(method, include_private = false)
73
+ QueryInterface.new(self).respond_to?(method, include_private) || super
74
+ end
75
+
76
+ private
77
+
78
+ def construct_list_from_response(klass, response_body, pagination)
79
+ list = ListObject.new(klass)
80
+ Array(response_body.fetch('AvailableCentralStations', [])).each do |item|
81
+ next unless item.is_a?(Hash)
82
+
83
+ list << build_model(klass, item)
84
+ end
85
+
86
+ list.set_meta Meta.new(
87
+ 'page' => {
88
+ 'total' => response_body['TotalCount'] || list.length,
89
+ 'number' => pagination[:number],
90
+ 'size' => pagination[:size]
91
+ }
92
+ )
93
+ list
94
+ end
95
+
96
+ def build_model(klass, attributes)
97
+ sanitized_attributes = attributes.each_with_object({}) do |(key, value), memo|
98
+ memo[Util.camelize(Util.underscore(key.to_s))] = value
99
+ end
100
+
101
+ klass.new(sanitized_attributes['appName'], sanitized_attributes)
102
+ end
103
+
104
+ def extract_page_value(pagination, key)
105
+ return nil unless pagination.is_a?(Hash)
106
+
107
+ pagination[key] || pagination[key.to_s]
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,55 @@
1
+ module Alula
2
+ class PermittedCentralStation < Alula::RestResource
3
+ extend Alula::ResourceAttributes
4
+ extend Alula::RelationshipAttributes
5
+ extend Alula::ApiOperations::Request
6
+ extend Alula::ApiOperations::List
7
+ extend Alula::ApiOperations::Save
8
+ extend Alula::ApiOperations::Delete
9
+
10
+ resource_path 'dealers/permittedCentralStations'
11
+ type 'dealers-permittedCentralStations'
12
+
13
+
14
+ relationship :dealer, type: 'dealers', cardinality: 'To-one'
15
+
16
+ # Resource Fields
17
+ # Not all params are used at the moment. See Alula::ResourceAttributes for details
18
+ # on how params are parsed,
19
+ field :id,
20
+ type: :string,
21
+ sortable: false,
22
+ filterable: false,
23
+ creatable_by: [:system],
24
+ patchable_by: []
25
+
26
+ field :dealer_id,
27
+ type: :string,
28
+ sortable: false,
29
+ filterable: false,
30
+ creatable_by: [:system],
31
+ patchable_by: []
32
+
33
+ field :remote_central_station_id,
34
+ type: :string,
35
+ sortable: false,
36
+ filterable: true,
37
+ creatable_by: [:system],
38
+ patchable_by: []
39
+
40
+ field :created_by,
41
+ type: :string,
42
+ sortable: false,
43
+ filterable: false,
44
+ creatable_by: [],
45
+ patchable_by: []
46
+
47
+ field :created_at,
48
+ type: :date,
49
+ sortable: false,
50
+ filterable: false,
51
+ creatable_by: [],
52
+ patchable_by: []
53
+
54
+ end
55
+ end
data/lib/alula/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alula
4
- VERSION = '2.29.0'
4
+ VERSION = '2.30.0'
5
5
  end
data/lib/alula.rb CHANGED
@@ -11,6 +11,8 @@ require_relative 'alula/api_operations/request'
11
11
  require_relative 'alula/api_operations/save'
12
12
  require_relative 'alula/api_operations/delete'
13
13
  require_relative 'alula/api_resource'
14
+ require_relative 'alula/m2m_rest_resource'
15
+ require_relative 'alula/m2m_response'
14
16
 
15
17
  require_relative 'alula/dcp/error_handler'
16
18
  require_relative 'alula/dcp/list_object'
@@ -147,9 +149,11 @@ require_relative 'alula/resources/feature_price'
147
149
  require_relative 'alula/resources/feature_bysubject'
148
150
  require_relative 'alula/resources/nfc_tag'
149
151
  require_relative 'alula/resources/sim_card'
152
+ require_relative 'alula/resources/permitted_central_station'
150
153
  require_relative 'alula/resources/dcp/config/synchronize'
151
154
  require_relative 'alula/resources/video/base_resource'
152
155
  require_relative 'alula/resources/video/device'
156
+ require_relative 'alula/resources/m2m/m2m_central_station'
153
157
 
154
158
  require_relative 'alula/procedures/device_cellular_history_proc'
155
159
  require_relative 'alula/procedures/device_rateplan_get_proc'
@@ -237,6 +241,8 @@ module Alula
237
241
  Alula::ReceiverBind,
238
242
  Alula::Revision,
239
243
  Alula::Self,
244
+ Alula::M2MCentralStation,
245
+ Alula::PermittedCentralStation,
240
246
  Alula::Station,
241
247
  Alula::User,
242
248
  Alula::UserAddress,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alula-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.29.0
4
+ version: 2.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Titus Johnson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-12-15 00:00:00.000000000 Z
11
+ date: 2025-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -225,6 +225,8 @@ files:
225
225
  - lib/alula/helpers/device_helpers/program_id_helper.rb
226
226
  - lib/alula/helpers/jwt_helper.rb
227
227
  - lib/alula/list_object.rb
228
+ - lib/alula/m2m_response.rb
229
+ - lib/alula/m2m_rest_resource.rb
228
230
  - lib/alula/meta.rb
229
231
  - lib/alula/monkey_patches.rb
230
232
  - lib/alula/oauth.rb
@@ -341,8 +343,10 @@ files:
341
343
  - lib/alula/resources/feature_planvideo.rb
342
344
  - lib/alula/resources/feature_price.rb
343
345
  - lib/alula/resources/helix_user.rb
346
+ - lib/alula/resources/m2m/m2m_central_station.rb
344
347
  - lib/alula/resources/nfc_tag.rb
345
348
  - lib/alula/resources/oauth_client.rb
349
+ - lib/alula/resources/permitted_central_station.rb
346
350
  - lib/alula/resources/receiver.rb
347
351
  - lib/alula/resources/receiver_bind.rb
348
352
  - lib/alula/resources/receiver_connection.rb