alula-ruby 1.6.0 → 1.8.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: 8e8587f55af4a330e24dfdb5f5e6df0ca0cb581f16d38a05b132925b03df0152
4
- data.tar.gz: 5af4c678137eb61daf3d7416644a95beaffa85c41870294775b3e4d801a88ccb
3
+ metadata.gz: 4c9520f289b6a7b5175a8a32b3a1934d6f1ecae534f4054cfa043533d7dd6572
4
+ data.tar.gz: 11bf384b95594822f1a8f4c93c71f294409e78e12d63bbad4f700f3060dd222e
5
5
  SHA512:
6
- metadata.gz: 744017971308b19fc198941335cfe71b2cdc4bbf0c223d41fb7dbb9cc325cfe13f667f7f59fd7c9d8dbb6a2fd7c2e38d301566e24e7729239c4fd213083f71f7
7
- data.tar.gz: 2343e372fd84218e713abfaa2574e4ead77a180c27057cee0bdfc5fcd354184ce01b953041ebf87b503e743622dedf51cb9a357d33cd4a7ead1f0eb378d7bab1
6
+ metadata.gz: 68f0c65cda2ed0d8c83e7ac321057f986e39ccadb89df5c7c58a37b1490b46ad3158b776370f87dc8cc6c8f1e68a068f88c8f44ad4d8c5a6581344472ca84021
7
+ data.tar.gz: 9608f45a747a310a1f13ef6341f458ac37b6ed0ba4a5ad30e485532d7424875dd2991562f9b6402152191167fc0a8eccfc0d092b8a84cb3ece489af73d9e1581
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.6
1
+ 3.3.0
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.0.6-alpine
1
+ FROM ruby:3.3.0-alpine
2
2
 
3
3
  RUN gem install bundler
4
4
 
data/VERSION.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  | Version | Date | Description |
4
4
  | ------- | --------- | --------------------------------------------------------------------------- |
5
+ | v1.8.0 | 2024-06-19 | Use Video API endpoints instead of core API passthrough |
6
+ | v1.7.0 | 2024-06-13 | Add DELETE, PATCH/POST, unregister capabilities for cameras |
5
7
  | v1.6.0 | 2024-01-26 | Add user language attribute |
6
8
  | v1.5.0 | 2024-01-09 | Add more known errors that use a different format |
7
9
  | v1.4.0 | 2024-01-04 | Add more known errors that use a different format, bugfix for `NotImplementedError` raising |
@@ -73,6 +73,7 @@ services:
73
73
  IPDAPI_WS_WORK_VIGILANCE_CANCEL_BATCH_WINDOW_MSEC: ${IPDAPI_WS_WORK_VIGILANCE_CANCEL_BATCH_WINDOW_MSEC:-10000}
74
74
  IPDAPI_WS_WORK_VIGILANCE_CANCEL_TERMINATE_DELAY_MSEC: ${IPDAPI_WS_WORK_VIGILANCE_CANCEL_TERMINATE_DELAY_MSEC:-500}
75
75
  IPDAPI_WS_WORK_VIGILANCE_TERMINATE_DELAY_MSEC: ${IPDAPI_WS_WORK_VIGILANCE_TERMINATE_DELAY_MSEC:-500}
76
+ LOG_LEVEL: warn
76
77
  MARIADB_ADDRESS: mariadb
77
78
  MARIADB_PASS: ""
78
79
  MARIADB_USER: root
@@ -82,13 +83,14 @@ services:
82
83
  MONGODB_USER: ipd
83
84
  RMQ_EPHEMERAL_ADDRESS: amqp://rmq-ephemeral
84
85
  RMQ_PERSISTENT_ADDRESS: amqp://rmq-persistent
86
+ REDIS_ADDRESS: redis-ipdapi
85
87
  healthcheck:
86
- interval: 15s
87
- retries: 5
88
+ interval: 5s
89
+ retries: 10
88
90
  start_period: 30s
89
- test: curl -s http://localhost/public/v1/healthcheck/default || exit -1
90
- timeout: 15s
91
- image: 6z1wlx5zf1.execute-api.us-east-1.amazonaws.com/alula/ipdapi:latest-master
91
+ test: apk add curl && curl -s http://localhost/public/v1/healthcheck/default || exit -1
92
+ timeout: 10s
93
+ image: 613707345027.dkr.ecr.us-east-1.amazonaws.com/api/ipdapi:staging
92
94
  networks:
93
95
  mesh_net:
94
96
  profiles:
@@ -101,23 +103,24 @@ services:
101
103
  ports:
102
104
  - "8080:80"
103
105
  mariadb:
106
+ hostname: mariadb
107
+ image: mariadb:10.3
104
108
  command:
105
109
  - --wait_timeout=28800
106
110
  - --max_connections=2048
107
111
  environment:
108
- MYSQL_ALLOW_EMPTY_PASSWORD: "true"
109
- healthcheck:
110
- interval: 15s
111
- retries: 5
112
- test: [CMD, mysqladmin, ping, -h, localhost]
113
- timeout: 15s
114
- image: mariadb:10.3
115
- networks:
116
- mesh_net:
112
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
117
113
  volumes:
118
114
  - mariadb_data:/var/lib/mysql
119
115
  - ./utils/mariadb/init:/docker-entrypoint-initdb.d
120
116
  - ./utils/mariadb/conf:/etc/mysql/conf.d
117
+ healthcheck:
118
+ test: mysql -D mysql --silent --execute "SELECT 1;"
119
+ interval: 30s
120
+ timeout: 10s
121
+ retries: 5
122
+ networks:
123
+ mesh_net:
121
124
  mongodb:
122
125
  command: --replSet api0 --bind_ip_all
123
126
  environment:
@@ -154,6 +157,19 @@ services:
154
157
  image: rabbitmq:management
155
158
  networks:
156
159
  mesh_net:
160
+ redis-ipdapi:
161
+ container_name: redis-ipdapi
162
+ image: redis:6.0.9-alpine
163
+ healthcheck:
164
+ test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
165
+ interval: 1s
166
+ timeout: 5s
167
+ retries: 30
168
+ networks:
169
+ mesh_net:
170
+ ports:
171
+ - 6379:6379
172
+ restart: always
157
173
  version: "3.9"
158
174
  volumes:
159
175
  dcp-mongo:
@@ -4,4 +4,4 @@ echo "INIT $(date +%s)" > /var/run/alula/ipdapi-status
4
4
  cat scripts/npm | grep -v npm | bash
5
5
  node app/migrations.js --create --up
6
6
  echo "READY $(date +%s)" >> /var/run/alula/ipdapi-status
7
- npm start
7
+ exec node app/main.js
@@ -13,6 +13,7 @@ module Alula
13
13
  attributes: as_patchable_json
14
14
  }
15
15
  }
16
+ payload[:data].delete(:id) if video_request?(resource_url)
16
17
 
17
18
  response = Alula::Client.request(:patch, resource_url, payload, {})
18
19
 
@@ -79,6 +80,10 @@ module Alula
79
80
  raise Alula::UnknownError, "Unknown HTTP response code, aborting. Code: #{response.http_status}"
80
81
  end
81
82
  end
83
+
84
+ def video_request?(resource_path)
85
+ resource_path.match(%r{^/video})
86
+ end
82
87
  end
83
88
  end
84
89
  end
data/lib/alula/client.rb CHANGED
@@ -7,7 +7,7 @@ module Alula
7
7
  class << self
8
8
  DEFAULT_CUSTOM_OPTIONS = {
9
9
  omitRelationships: true
10
- }
10
+ }.freeze
11
11
 
12
12
  def config
13
13
  @_config ||= Alula::ClientConfiguration.new
@@ -18,13 +18,13 @@ module Alula
18
18
  end
19
19
 
20
20
  def request(http_method, resource_path, filters = {}, opts = {})
21
- unless resource_path.match(%r{^/public/v1})
22
- validate_request!(http_method, resource_path)
23
- end
24
-
21
+ validate_request!(http_method, resource_path) unless resource_path.match(%r{^/public/v1})
25
22
  request_opts = build_options(http_method, resource_path, filters, opts)
23
+ api_url = video_request?(resource_path) ? config.video_api_url : config.api_url
24
+ request_resource_path = video_request?(resource_path) ? resource_path.gsub(%r{^/video}, '') : resource_path
25
+
26
26
  # TODO: Handle network failures
27
- response = make_request(http_method, config.api_url + resource_path, request_opts)
27
+ response = make_request(http_method, api_url + request_resource_path, request_opts)
28
28
 
29
29
  begin
30
30
  resp = AlulaResponse.from_httparty_response(response)
@@ -41,7 +41,11 @@ module Alula
41
41
  def validate_request!(http_method, resource_path)
42
42
  config.ensure_api_key_set
43
43
  config.ensure_api_url_set
44
- config.ensure_customer_id_set if resource_path.match(%r{^/video})
44
+ if video_request?(resource_path)
45
+ config.ensure_video_api_url_set
46
+ config.ensure_video_api_key_set
47
+ config.ensure_customer_id_set
48
+ end
45
49
  ensure_method_allowable(http_method)
46
50
  config.ensure_role_set if %i[patch post put].include? http_method
47
51
  end
@@ -51,9 +55,13 @@ module Alula
51
55
  end
52
56
 
53
57
  def ensure_method_allowable(http_method)
54
- unless %i[get post put patch delete].include?(http_method)
55
- raise "Unable to send a request with #{http_method} http method"
56
- end
58
+ return if %i[get post put patch delete].include?(http_method)
59
+
60
+ raise "Unable to send a request with #{http_method} http method"
61
+ end
62
+
63
+ def video_request?(resource_path)
64
+ resource_path.match(%r{^/video})
57
65
  end
58
66
 
59
67
  def build_options(http_method, resource_path, filters = {}, opts = {})
@@ -63,6 +71,7 @@ module Alula
63
71
  'User-Agent': "#{Alula::Client.config.user_agent || 'No Agent Set'}/alula-ruby v#{Alula::VERSION}"
64
72
  }
65
73
  }.merge(opts)
74
+ handle_video_request(request_opts) if video_request?(resource_path) # Add token for all video requests
66
75
 
67
76
  request_opts[:headers]['Content-Type'] = 'application/json' unless opts[:multipart]
68
77
  case http_method
@@ -70,11 +79,7 @@ module Alula
70
79
  :post
71
80
  request_opts[:body] = opts[:multipart] ? filters : JSON.generate(filters)
72
81
  when :get
73
- if resource_path.match(%r{^/(rest|rpc)})
74
- request_opts = request_opts.merge build_rest_options(filters)
75
- elsif resource_path.match(%r{^/video})
76
- add_customer_id(request_opts)
77
- end
82
+ request_opts = request_opts.merge build_rest_options(filters) if resource_path.match(%r{^/(rest|rpc)})
78
83
  when :delete
79
84
  # Nothing special
80
85
  end
@@ -111,8 +116,17 @@ module Alula
111
116
  filters
112
117
  end
113
118
 
114
- def add_customer_id(request_opts)
115
- request_opts[:query] = { 'customerId' => config.customer_id }
119
+ def handle_video_request(request_opts)
120
+ if Alula::Client.config.internal
121
+ set_token(request_opts, { internal: true })
122
+ else
123
+ set_token(request_opts, { customerId: Alula::Client.config.customer_id })
124
+ end
125
+ end
126
+
127
+ def set_token(request_opts, payload)
128
+ jwt = TokenExchange.fetch_video_token(payload)
129
+ request_opts[:headers]['token'] = jwt
116
130
  end
117
131
  end
118
132
  end
@@ -1,7 +1,7 @@
1
1
  module Alula
2
2
  class ClientConfiguration
3
- attr_accessor :api_url, :debug, :user_agent
4
- REQUEST_ATTRIBUTES = %i[api_key customer_id]
3
+ attr_accessor :api_url, :video_api_url, :debug, :user_agent
4
+ REQUEST_ATTRIBUTES = %i[api_key video_api_key customer_id internal]
5
5
 
6
6
  # Using RequestStore so we're thread safe even with our non-thread-safe architecture :(
7
7
  REQUEST_ATTRIBUTES.each do |prop|
@@ -42,11 +42,25 @@ module Alula
42
42
  end
43
43
  end
44
44
 
45
+ def ensure_video_api_url_set
46
+ return unless video_api_url.nil?
47
+
48
+ raise Alula::NotConfiguredError,
49
+ 'did you forget to set the Alula::Client.config.video_api_url config option?'
50
+ end
51
+
52
+ def ensure_video_api_key_set
53
+ return if video_api_key
54
+
55
+ raise Alula::NotConfiguredError,
56
+ 'Set your video API access token before making requests to the video API'
57
+ end
58
+
45
59
  def ensure_customer_id_set
46
- unless customer_id
47
- raise Alula::NotConfiguredError,
48
- 'Set your customer_id before making requests to the video API'
49
- end
60
+ return if internal || customer_id
61
+
62
+ raise Alula::NotConfiguredError,
63
+ 'Set your customer_id before making requests to the video API'
50
64
  end
51
65
 
52
66
  def ensure_role_set
data/lib/alula/errors.rb CHANGED
@@ -87,6 +87,8 @@ module Alula
87
87
  NotFoundError.new(message)
88
88
  when 'Device Not Found'
89
89
  NotFoundError.new(message)
90
+ when 'Unable to unregister device'
91
+ NotFoundError.new(message)
90
92
  else
91
93
  Alula.logger.error response
92
94
  raise NotImplementedError, "Unable to derive error for #{response.data['error'].to_json}"
@@ -128,6 +130,8 @@ module Alula
128
130
  NotFoundError.new(message)
129
131
  when 'Device Not Found'
130
132
  NotFoundError.new(message)
133
+ when 'Unable to unregister device'
134
+ NotFoundError.new(message)
131
135
  else
132
136
  Alula.logger.error response
133
137
  raise NotImplementedError, "Unable to derive error for #{response.data['errors'].to_json}"
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require 'base64'
3
+ require 'openssl'
4
+
5
+ module Alula
6
+ # Helper class to build JWT tokens
7
+ # Used for internal video API calls or video API calls with a customer ID
8
+ module JwtHelper
9
+ ONE_HOUR = 60 * 60
10
+ class << self
11
+ def build_jwt_token(jwt_payload)
12
+ jwt_secret = Alula::Client.config.video_api_key
13
+
14
+ header = build_jwt_header
15
+
16
+ token_data = add_timestamp_to_payload(jwt_payload)
17
+
18
+ encoded_header = convert_to_base64(JSON.generate(header))
19
+ encoded_data = convert_to_base64(JSON.generate(token_data))
20
+
21
+ token = "#{encoded_header}.#{encoded_data}"
22
+ signature = sign_token(token, jwt_secret)
23
+
24
+ "#{token}.#{signature}"
25
+ end
26
+
27
+ def build_jwt_header
28
+ {
29
+ 'typ' => 'JWT',
30
+ 'alg' => 'HS256'
31
+ }
32
+ end
33
+
34
+ def add_timestamp_to_payload(payload)
35
+ current_timestamp = Time.now.to_i
36
+ payload.merge(
37
+ {
38
+ 'iat' => current_timestamp,
39
+ 'exp' => current_timestamp + ONE_HOUR # expiry time is 60 minutes from time of creation
40
+ }
41
+ )
42
+ end
43
+
44
+ def sign_token(token, secret)
45
+ signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, token)
46
+ convert_to_base64(signature)
47
+ end
48
+
49
+ def convert_to_base64(source)
50
+ # Encode in classical base64,
51
+ encoded_source = Base64.strict_encode64(source)
52
+
53
+ # Remove padding equal characters,
54
+ encoded_source = encoded_source.gsub('=', '')
55
+
56
+ # Replace characters according to base64url specifications,
57
+ encoded_source.tr('+/', '-_')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alula
4
+ class VideoUnregisterProc < Alula::RpcResource
5
+ class Response < Alula::RpcResponse
6
+ end
7
+
8
+ def self.call(device_id:)
9
+ payload = {
10
+ deviceId: device_id
11
+ }
12
+
13
+ request(
14
+ http_method: :post,
15
+ path: '/rpc/v1/video/unregister',
16
+ payload: payload,
17
+ handler: Response
18
+ )
19
+ end
20
+ end
21
+ end
@@ -1,17 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/jwt_helper'
4
+
1
5
  module Alula
6
+ # Helper class to generate OAuth access tokens (core API) and JWT tokens (VSP API)
2
7
  class TokenExchange
3
- def self.token_for_user(user_id)
4
- url = '/rest/v1/oauth/accesstokens'
5
- payload = { data: { attributes: { userId: user_id } } }
6
- opts = {}
7
-
8
- response = Alula::Client.request(:post, url, payload, opts)
9
-
10
- if response.ok?
11
- ImpersonatedToken.new(response.data['data']['attributes'])
12
- else
13
- error_class = AlulaError.for_response(response)
14
- raise error_class
8
+ class << self
9
+ def token_for_user(user_id)
10
+ url = '/rest/v1/oauth/accesstokens'
11
+ payload = { data: { attributes: { userId: user_id } } }
12
+ opts = {}
13
+
14
+ response = Alula::Client.request(:post, url, payload, opts)
15
+
16
+ if response.ok?
17
+ ImpersonatedToken.new(response.data['data']['attributes'])
18
+ else
19
+ error_class = AlulaError.for_response(response)
20
+ raise error_class
21
+ end
22
+ end
23
+
24
+ def fetch_video_token(payload)
25
+ cache_key = generate_cache_key(payload)
26
+ cached_token, expiry = retrieve_cached_token(cache_key)
27
+
28
+ if cached_token && Time.now.to_i < expiry
29
+ jwt_token = cached_token
30
+ else
31
+ jwt_token = build_jwt_token(payload)
32
+ expiry = Time.now.to_i + JwtHelper::ONE_HOUR # 1 hour expiry
33
+ store_token_in_cache(cache_key, jwt_token, expiry)
34
+ end
35
+
36
+ jwt_token
37
+ end
38
+
39
+ private
40
+
41
+ def build_jwt_token(payload)
42
+ JwtHelper.build_jwt_token(payload)
43
+ end
44
+
45
+ def store_token_in_cache(cache_key, jwt_token, expiry)
46
+ @token_cache ||= {}
47
+ @token_cache[cache_key] = [jwt_token, expiry]
48
+ end
49
+
50
+ def retrieve_cached_token(cache_key)
51
+ @token_cache ||= {}
52
+ @token_cache[cache_key]
53
+ end
54
+
55
+ def generate_cache_key(payload)
56
+ if payload[:internal]
57
+ 'jwt_token_internal'
58
+ elsif payload[:customerId]
59
+ "jwt_token_customer_#{payload[:customerId]}"
60
+ else
61
+ raise ArgumentError, 'Invalid payload'
62
+ end
15
63
  end
16
64
  end
17
65
 
@@ -32,6 +32,11 @@ module Alula
32
32
 
33
33
  api_name :video
34
34
 
35
+ # Instance method
36
+ def resource_url(id = self.id)
37
+ "/#{self.class.api_name}/v1/#{self.class.resource_name}/#{id}"
38
+ end
39
+
35
40
  def construct_from(json_object)
36
41
  @raw_data = json_object.dup
37
42
  @values = json_object
@@ -2,15 +2,88 @@ module Alula
2
2
  module Video
3
3
  class Device < Alula::Video::BaseResource
4
4
  extend Alula::ApiOperations::Request
5
+ extend Alula::ApiOperations::Save
6
+ extend Alula::ApiOperations::Delete
5
7
 
6
- field :account_id, type: :string
7
- field :active, type: :boolean
8
- field :brand, type: :string
9
- field :hardware_id, type: :string
10
- field :manufacturer, type: :string
11
- field :name, type: :string
12
- field :serial_number, type: :string
13
- field :verification_code, type: :string
8
+ field :account_id,
9
+ type: :string,
10
+ patchable_by: %i[system station dealer user],
11
+ creatable_by: %i[system station dealer user]
12
+
13
+ field :active,
14
+ type: :boolean,
15
+ patchable_by: %i[system station dealer user],
16
+ creatable_by: %i[system station dealer user]
17
+
18
+ field :brand,
19
+ type: :string,
20
+ patchable_by: %i[system station dealer user],
21
+ creatable_by: %i[system station dealer user]
22
+
23
+ field :hardware_id,
24
+ type: :string,
25
+ patchable_by: %i[system station dealer user],
26
+ creatable_by: %i[system station dealer user]
27
+
28
+ field :manufacturer,
29
+ type: :string,
30
+ patchable_by: %i[system station dealer user],
31
+ creatable_by: %i[system station dealer user]
32
+
33
+ field :name,
34
+ type: :string,
35
+ patchable_by: %i[system station dealer user],
36
+ creatable_by: %i[system station dealer user]
37
+
38
+ field :serial_number,
39
+ type: :string,
40
+ patchable_by: %i[system station dealer user],
41
+ creatable_by: %i[system station dealer user]
42
+
43
+ field :verification_code,
44
+ type: :string,
45
+ patchable_by: %i[system station dealer user],
46
+ creatable_by: %i[system station dealer user]
47
+
48
+ field :timezone,
49
+ type: :string,
50
+ patchable_by: %i[system station dealer user],
51
+ creatable_by: %i[system station dealer user]
52
+
53
+ field :timezone_id,
54
+ type: :string,
55
+ patchable_by: %i[system station dealer user],
56
+ creatable_by: %i[system station dealer user]
57
+
58
+ field :daylight_saving,
59
+ type: :number,
60
+ patchable_by: %i[system station dealer user],
61
+ creatable_by: %i[system station dealer user]
62
+
63
+ field :time_format,
64
+ type: :number,
65
+ patchable_by: %i[system station dealer user],
66
+ creatable_by: %i[system station dealer user]
67
+
68
+ field :customer_id,
69
+ type: :string,
70
+ patchable_by: %i[system station dealer user],
71
+ creatable_by: %i[system station dealer user]
72
+
73
+ field :online,
74
+ type: :object,
75
+ patchable_by: %i[system station dealer user],
76
+ creatable_by: %i[system station dealer user]
77
+
78
+ field :settings,
79
+ type: :object,
80
+ patchable_by: %i[system station dealer user],
81
+ creatable_by: %i[system station dealer user]
82
+
83
+ field :model,
84
+ type: :string,
85
+ patchable_by: %i[system station dealer user],
86
+ creatable_by: %i[system station dealer user]
14
87
 
15
88
  def is_jsw?
16
89
  manufacturer == 'jsw'
data/lib/alula/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alula
4
- VERSION = '1.6.0'
4
+ VERSION = '1.8.0'
5
5
  end
data/lib/alula.rb CHANGED
@@ -94,6 +94,7 @@ require_relative 'alula/procedures/user_plansvideo_price_get'
94
94
  require_relative 'alula/procedures/user_get_locked_proc'
95
95
  require_relative 'alula/procedures/user_unlock_proc'
96
96
  require_relative 'alula/procedures/user_password_reset_proc'
97
+ require_relative 'alula/procedures/video_unregister_proc'
97
98
 
98
99
  module Alula
99
100
  #
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: 1.6.0
4
+ version: 1.8.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: 2024-01-29 00:00:00.000000000 Z
11
+ date: 2024-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -210,6 +210,7 @@ files:
210
210
  - lib/alula/errors.rb
211
211
  - lib/alula/filter_builder.rb
212
212
  - lib/alula/helpers/device_attribute_translations.rb
213
+ - lib/alula/helpers/jwt_helper.rb
213
214
  - lib/alula/list_object.rb
214
215
  - lib/alula/meta.rb
215
216
  - lib/alula/monkey_patches.rb
@@ -240,6 +241,7 @@ files:
240
241
  - lib/alula/procedures/user_transfer_reject.rb
241
242
  - lib/alula/procedures/user_transfer_request.rb
242
243
  - lib/alula/procedures/user_unlock_proc.rb
244
+ - lib/alula/procedures/video_unregister_proc.rb
243
245
  - lib/alula/query_interface.rb
244
246
  - lib/alula/rate_limit.rb
245
247
  - lib/alula/relationship_attributes.rb