active_call-doc_fox 0.1.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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +70 -0
  4. data/CHANGELOG.md +5 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +355 -0
  7. data/Rakefile +12 -0
  8. data/lib/active_call-doc_fox.rb +3 -0
  9. data/lib/doc_fox/access_token/facade.rb +12 -0
  10. data/lib/doc_fox/access_token/get_service.rb +62 -0
  11. data/lib/doc_fox/authentication/facade.rb +12 -0
  12. data/lib/doc_fox/authentication/get_service.rb +41 -0
  13. data/lib/doc_fox/base_service.rb +79 -0
  14. data/lib/doc_fox/concerns/enumerable.rb +65 -0
  15. data/lib/doc_fox/error.rb +67 -0
  16. data/lib/doc_fox/kyc_application/approve_service.rb +53 -0
  17. data/lib/doc_fox/kyc_application/archive_service.rb +43 -0
  18. data/lib/doc_fox/kyc_application/create_service.rb +61 -0
  19. data/lib/doc_fox/kyc_application/delete_service.rb +29 -0
  20. data/lib/doc_fox/kyc_application/facade.rb +22 -0
  21. data/lib/doc_fox/kyc_application/get_service.rb +52 -0
  22. data/lib/doc_fox/kyc_application/list_service.rb +46 -0
  23. data/lib/doc_fox/kyc_application/transfer_service.rb +56 -0
  24. data/lib/doc_fox/kyc_application/unapprove_service.rb +56 -0
  25. data/lib/doc_fox/kyc_application/unarchive_service.rb +43 -0
  26. data/lib/doc_fox/kyc_entity_template/facade.rb +17 -0
  27. data/lib/doc_fox/kyc_entity_template/get_service.rb +43 -0
  28. data/lib/doc_fox/kyc_entity_template/list_service.rb +28 -0
  29. data/lib/doc_fox/user/facade.rb +16 -0
  30. data/lib/doc_fox/user/get_service.rb +43 -0
  31. data/lib/doc_fox/user/list_service.rb +28 -0
  32. data/lib/doc_fox/version.rb +5 -0
  33. data/lib/doc_fox.rb +16 -0
  34. data/sig/doc_fox.rbs +4 -0
  35. metadata +109 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 06a4844a8cafc0cf44d2064815b76b3e7c595624f8e6df170d5951ab30fc8712
4
+ data.tar.gz: 72a3cc818e2df925808917b3e84c89e102ccbdf965a350d9895243202ae6b5e1
5
+ SHA512:
6
+ metadata.gz: 6d40925136bfbd0fa746d5396bcb07b8bcd145fd401d5560e19f45c1d44bf9379a5002a65151cbd99ba31f9407c61f1f0c015b23a4a31cfb552c3cb369cbe308
7
+ data.tar.gz: 3e6bb5d3a082dd2c8c587541364d437a50fc3784a90219a0c28b31dd1d7535aa76884286a3b8e52a8b57409a23581c8b6a4ba64a8f399afda9b1ffaa5163d844
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,70 @@
1
+ plugins:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.1
8
+ NewCops: enable
9
+ Exclude:
10
+ - doc_fox.gemspec
11
+ - bin/*
12
+ - 'vendor/**/*'
13
+ - 'gem/**/*'
14
+
15
+ Layout/ArgumentAlignment:
16
+ EnforcedStyle: with_fixed_indentation
17
+
18
+ Layout/HashAlignment:
19
+ EnforcedHashRocketStyle: table
20
+ EnforcedColonStyle: table
21
+
22
+ Lint/MissingSuper:
23
+ Enabled: false
24
+
25
+ Metrics/AbcSize:
26
+ Enabled: false
27
+
28
+ Metrics/BlockLength:
29
+ Exclude:
30
+ - spec/*/**.rb
31
+
32
+ Metrics/ClassLength:
33
+ Enabled: false
34
+
35
+ Metrics/CyclomaticComplexity:
36
+ Exclude:
37
+ - lib/doc_fox/concerns/enumerable.rb
38
+
39
+ Metrics/MethodLength:
40
+ Enabled: false
41
+
42
+ Naming/FileName:
43
+ Exclude:
44
+ - lib/active_call-doc_fox.rb
45
+
46
+ Naming/MemoizedInstanceVariableName:
47
+ EnforcedStyleForLeadingUnderscores: required
48
+
49
+ RSpec/ExampleLength:
50
+ Enabled: false
51
+
52
+ RSpec/MultipleExpectations:
53
+ Enabled: false
54
+
55
+ Style/ClassAndModuleChildren:
56
+ EnforcedStyle: compact
57
+ Exclude:
58
+ - lib/doc_fox.rb
59
+
60
+ Style/Documentation:
61
+ Enabled: false
62
+
63
+ Style/StringLiterals:
64
+ EnforcedStyle: single_quotes
65
+
66
+ Style/StringLiteralsInInterpolation:
67
+ EnforcedStyle: single_quotes
68
+
69
+ Style/SymbolArray:
70
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-03-23
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Kobus Joubert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # Active Call - nCino KYC DocFox
2
+
3
+ DocFox exposes the [nCino KYC DocFox API](https://www.docfoxapp.com/api/v2/documentation) endpoints through service objects.
4
+
5
+ - [Installation](#installation)
6
+ - [Configuration](#configuration)
7
+ - [Usage](#usage)
8
+ - [Using call](#using-call)
9
+ - [Using call!](#using-call!)
10
+ - [When to use call or call!](#using-call-or-call!)
11
+ - [Using lists](#using-lists)
12
+ - [KYC Applications](#kyc-applications)
13
+ - [KYC Entity Templates](#kyc-entity-templates)
14
+ - [Users](#users)
15
+ - [Development](#development)
16
+ - [Contributing](#contributing)
17
+ - [License](#license)
18
+
19
+ ## Installation
20
+
21
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
22
+
23
+ Install the gem and add to the application's Gemfile by executing:
24
+
25
+ ```bash
26
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
27
+ ```
28
+
29
+ If bundler is not being used to manage dependencies, install the gem by executing:
30
+
31
+ ```bash
32
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ Configure your API credentials.
38
+
39
+ In a Rails application, the standard practice is to place this code in a file named `doc_fox.rb` within the `config/initializers` directory.
40
+
41
+ ```ruby
42
+ require 'active_call-doc_fox'
43
+
44
+ DocFox::BaseService.configure do |config|
45
+ config.api_key = ''
46
+ config.secret = ''
47
+
48
+ # Optional configuration.
49
+ config.cache = Rails.cache # Default: ActiveSupport::Cache::MemoryStore.new
50
+ config.logger = Rails.logger # Default: Logger.new($stdout)
51
+ config.logger_level = :debug # Default: :info
52
+ config.log_headers = true # Default: false
53
+ config.log_bodies = true # Default: false
54
+ end
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### <a name='using-call'></a>Using `call`
60
+
61
+ Each service object returned will undergo validation before the `call` method is invoked to access API endpoints.
62
+
63
+ After **successful** validation.
64
+
65
+ ```ruby
66
+ service.success? # => true
67
+ service.errors # => #<ActiveModel::Errors []>
68
+ ```
69
+
70
+ After **failed** validation.
71
+
72
+ ```ruby
73
+ service.success? # => false
74
+ service.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=id, type=blank, options={}>]>
75
+ service.errors.full_messages # => ["Id can't be blank"]
76
+ ```
77
+
78
+ After a **successful** `call` invocation, the `response` attribute will contain a `Faraday::Response` object.
79
+
80
+ ```ruby
81
+ service.success? # => true
82
+ service.response # => #<Faraday::Response ...>
83
+ service.response.success? # => true
84
+ service.response.status # => 200
85
+ service.response.body # => {"data"=>{"id"=>"aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", "type"=>"kyc_application", "attributes"=>{}}, ...}
86
+ ```
87
+
88
+ At this point you will also have a `facade` object which will hold all the attributes for the specific resource.
89
+
90
+ ```ruby
91
+ service.facade # => #<DocFox::KycApplication::Facade @kyc_entity_type_name="South African Citizen" ...>
92
+ service.facade.kyc_entity_type_name # => 'South African Citizen'
93
+ ```
94
+
95
+ For convenience, facade attributes can be accessed directly on the service object.
96
+
97
+ ```ruby
98
+ service.kyc_entity_type_name # => 'South African Citizen'
99
+ ```
100
+
101
+ After a **failed** `call` invocation, the `response` attribute will still contain a `Faraday::Response` object.
102
+
103
+ ```ruby
104
+ service.success? # => false
105
+ service.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=not_found, options={}>]>
106
+ service.errors.full_messages # => ["Not Found"]
107
+ service.response # => #<Faraday::Response ...>
108
+ service.response.success? # => false
109
+ service.response.status # => 404
110
+ service.response.body # => {"errors"=>[{"title"=>"not_found", "status"=>"404", "detail"=>"The resource you requested could not be found"}]}
111
+ ```
112
+
113
+ ### <a name='using-call!'></a>Using `call!`
114
+
115
+ Each service object returned will undergo validation before the `call!` method is invoked to access API endpoints.
116
+
117
+ After **successful** validation.
118
+
119
+ ```ruby
120
+ service.success? # => true
121
+ ```
122
+
123
+ After **failed** validation, a `DocFox::ValidationError` exception will be raised with an `errors` attribute which
124
+ will contain an `ActiveModel::Errors` object.
125
+
126
+ ```ruby
127
+ rescue DocFox::ValidationError => exception
128
+ exception.message # => ''
129
+ exception.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=id, type=blank, options={}>]>
130
+ exception.errors.full_messages # => ["Id can't be blank"]
131
+ ```
132
+
133
+ After a **successful** `call!` invocation, the `response` attribute will contain a `Faraday::Response` object.
134
+
135
+ ```ruby
136
+ service.success? # => true
137
+ service.response # => #<Faraday::Response ...>
138
+ service.response.success? # => true
139
+ service.response.status # => 200
140
+ service.response.body # => {"data"=>{"id"=>"aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", "type"=>"kyc_application", "attributes"=>{}}, ...}
141
+ ```
142
+
143
+ At this point you will also have a `facade` object which will hold all the attributes for the specific resource.
144
+
145
+ ```ruby
146
+ service.facade # => #<DocFox::KycApplication::Facade @kyc_entity_type_name="South African Citizen" ...>
147
+ service.facade.kyc_entity_type_name # => 'South African Citizen'
148
+ ```
149
+
150
+ For convenience, facade attributes can be accessed directly on the service object.
151
+
152
+ ```ruby
153
+ service.kyc_entity_type_name # => 'South African Citizen'
154
+ ```
155
+
156
+ After a **failed** `call!` invocation, a `DocFox::RequestError` will be raised with a `response` attribute which will contain a `Faraday::Response` object.
157
+
158
+ ```ruby
159
+ rescue DocFox::RequestError => exception
160
+ exception.message # => 'Not Found'
161
+ exception.errors # => #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=not_found, options={}>]>
162
+ exception.errors.full_messages # => ["Not Found"]
163
+ exception.response # => #<Faraday::Response ...>
164
+ exception.response.status # => 404
165
+ exception.response.body # => {"errors"=>[{"title"=>"not_found", "status"=>"404", "detail"=>"The resource you requested could not be found"}]}
166
+ ```
167
+
168
+ ### <a name='using-call-or-call!'></a>When to use `call` or `call!`
169
+
170
+ An example of where to use `call` would be in a **controller** doing an inline synchronous request.
171
+
172
+ ```ruby
173
+ class SomeController < ApplicationController
174
+ def update
175
+ @service = DocFox::KycApplication::UpdateService.call(**params)
176
+
177
+ if @service.success?
178
+ redirect_to [@service], notice: 'Success', status: :see_other
179
+ else
180
+ flash.now[:alert] = @service.errors.full_messages.to_sentence
181
+ render :edit, status: :unprocessable_entity
182
+ end
183
+ end
184
+ end
185
+ ```
186
+
187
+ An example of where to use `call!` would be in a **job** doing an asynchronous request.
188
+
189
+ You can use the exceptions to determine which retry strategy to use and which to discard.
190
+
191
+ ```ruby
192
+ class SomeJob < ApplicationJob
193
+ discard_on DocFox::NotFoundError
194
+
195
+ retry_on DocFox::RequestTimeoutError, wait: 5.minutes, attempts: :unlimited
196
+ retry_on DocFox::TooManyRequestsError, wait: :polynomially_longer, attempts: 10
197
+
198
+ def perform
199
+ DocFox::KycApplication::UpdateService.call!(**params)
200
+ end
201
+ end
202
+ ```
203
+
204
+ ### Using lists
205
+
206
+ If you don't provide the `per_page` argument, multiple API requests will be made untill all records have been returned. You could be rate limited, so use wisely.
207
+
208
+ ### KYC Applications
209
+
210
+ #### List KYC applications
211
+
212
+ ```ruby
213
+ service = DocFox::KycApplication::ListService.call(page: 1, per_page: 10).each do |facade|
214
+ facade.id
215
+ end
216
+ ```
217
+
218
+ Filter by names, numbers contact information or external refs.
219
+
220
+ ```ruby
221
+ DocFox::KycApplication::ListService.call(search_term: 'eric.cartman@example.com').map { _1 }
222
+ ```
223
+
224
+ #### Get a KYC application
225
+
226
+ ```ruby
227
+ service = DocFox::KycApplication::GetService.call(id: '')
228
+ service.id
229
+ service.approved_at
230
+ service.archived
231
+ service.created_at
232
+ service.status
233
+ service.relationships.dig('profile', 'data', 'id')
234
+ service.relationships.dig('data_requirements', 'links', 'related')
235
+ ...
236
+ ```
237
+
238
+ Include related resources.
239
+
240
+ ```ruby
241
+ service = DocFox::KycApplication::GetService.call(id: '', params: { include: 'managed_by,profile.names' })
242
+ service.included
243
+ ```
244
+
245
+ #### Create a KYC application
246
+
247
+ ```ruby
248
+ service = DocFox::KycApplication::CreateService.call(
249
+ data: {
250
+ type: 'kyc_application',
251
+ attributes: {
252
+ kyc_entity_template_id: 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb',
253
+ names: {
254
+ first_names: 'Eric Theodore',
255
+ last_names: 'Cartman'
256
+ },
257
+ # ...more fields following the schema definitions.
258
+ }
259
+ }
260
+ )
261
+ ```
262
+
263
+ #### Approve a KYC application
264
+
265
+ ```ruby
266
+ service = DocFox::KycApplication::ApproveService.call(id: '')
267
+ ```
268
+
269
+ #### Unapprove a KYC application
270
+
271
+ ```ruby
272
+ service = DocFox::KycApplication::UnapproveService.call(id: '', reason: '')
273
+ ```
274
+
275
+ #### Transfer a KYC application
276
+
277
+ ```ruby
278
+ service = DocFox::KycApplication::TransferService.call(id: '', transfer_to_user_id: '')
279
+ ```
280
+
281
+ #### Archive a KYC application
282
+
283
+ ```ruby
284
+ service = DocFox::KycApplication::ArchiveService.call(id: '', reason: '')
285
+ ```
286
+
287
+ #### Unarchive a KYC application
288
+
289
+ ```ruby
290
+ service = DocFox::KycApplication::UnarchiveService.call(id: '', reason: '')
291
+ ```
292
+
293
+ #### Delete a KYC application
294
+
295
+ ```ruby
296
+ service = DocFox::KycApplication::DeleteService.call(id: '')
297
+ ```
298
+
299
+ ### KYC Entity Templates
300
+
301
+ #### List KYC entity templates
302
+
303
+ ```ruby
304
+ service = DocFox::KycEntityTemplate::ListService.call(page: 1, per_page: 10).each do |facade|
305
+ facade.id
306
+ end
307
+ ```
308
+
309
+ #### Get a KYC entity template
310
+
311
+ ```ruby
312
+ service = DocFox::KycEntityTemplate::GetService.call(id: '')
313
+ service.id
314
+ service.kyc_entity_type_name
315
+ service.kyc_entity_type_category
316
+ service.created_at
317
+ service.profile_schema
318
+ ...
319
+ ```
320
+
321
+ ### Users
322
+
323
+ #### List users
324
+
325
+ ```ruby
326
+ service = DocFox::User::ListService.call(page: 1, per_page: 10).each do |facade|
327
+ facade.id
328
+ end
329
+ ```
330
+
331
+ #### Get a user
332
+
333
+ ```ruby
334
+ service = DocFox::User::GetService.call(id: '')
335
+ service.id
336
+ service.email
337
+ service.first_names
338
+ service.last_names
339
+ service.deactivated
340
+ ...
341
+ ```
342
+
343
+ ## Development
344
+
345
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
346
+
347
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
348
+
349
+ ## Contributing
350
+
351
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kobusjoubert/doc_fox.
352
+
353
+ ## License
354
+
355
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'doc_fox'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocFox::AccessToken::Facade
4
+ attr_reader :token, :expires_at
5
+
6
+ def initialize(hash)
7
+ attributes = hash.dig('data', 'attributes')
8
+
9
+ @token = attributes['token']
10
+ @expires_at = attributes['expires_at']
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocFox::AccessToken::GetService < DocFox::BaseService
4
+ attr_reader :authentication_service
5
+
6
+ validate on: :request do
7
+ errors.merge!(authentication_service.errors) unless authentication_service.success?
8
+ end
9
+
10
+ before_call :authenticate
11
+
12
+ after_call :set_facade
13
+
14
+ delegate_missing_to :@facade
15
+
16
+ # Get access token.
17
+ #
18
+ # ==== Examples
19
+ #
20
+ # service = DocFox::AccessToken::GetService.call
21
+ # service.token # => '1000.xxxx.yyyy'
22
+ # service.expires_at # => '2025-01-01T06:00:00.000+02:00'
23
+ #
24
+ # GET /api/v2/tokens/new
25
+ def call
26
+ connection.get('tokens/new')
27
+ end
28
+
29
+ private
30
+
31
+ def connection
32
+ @_connection ||= Faraday.new do |conn|
33
+ conn.adapter Faraday.default_adapter
34
+ conn.url_prefix = base_url
35
+ conn.headers['X-Client-Api-Key'] = api_key
36
+ conn.headers['X-Client-Signature'] = signature
37
+ conn.request :json
38
+ conn.request :retry
39
+ conn.response :json
40
+ conn.response :logger, logger, **logger_options do |logger|
41
+ logger.filter(/(X-Client-Api-Key:).*"(.+)."/i, '\1 [FILTERED]')
42
+ logger.filter(/(X-Client-Signature:).*"(.+)."/i, '\1 [FILTERED]')
43
+ logger.filter(/"token":"([^"]+)"/i, '"token":"[FILTERED]"')
44
+ end
45
+ end
46
+ end
47
+
48
+ def authenticate
49
+ @authentication_service = DocFox::Authentication::GetService.call
50
+ end
51
+
52
+ def signature
53
+ # Turns 'HMAC-SHA256' -> 'SHA256'.
54
+ digest_algorithm = authentication_service.digest_algorithm.split('-').last
55
+
56
+ OpenSSL::HMAC.hexdigest(digest_algorithm, secret, authentication_service.nonce)
57
+ end
58
+
59
+ def set_facade
60
+ @facade = DocFox::AccessToken::Facade.new(response.body)
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocFox::Authentication::Facade
4
+ attr_reader :nonce, :digest_algorithm
5
+
6
+ def initialize(hash)
7
+ attributes = hash.dig('data', 'attributes')
8
+
9
+ @nonce = attributes['nonce']
10
+ @digest_algorithm = attributes['digest_algorithm']
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocFox::Authentication::GetService < DocFox::BaseService
4
+ after_call :set_facade
5
+
6
+ delegate_missing_to :@facade
7
+
8
+ # Get access token.
9
+ #
10
+ # ==== Examples
11
+ #
12
+ # service = DocFox::Authentication::GetService.call
13
+ # service.nonce # => ''
14
+ # service.digest_algorithm # => ''
15
+ #
16
+ # GET /api/v2/authentications/new
17
+ def call
18
+ connection.get('authentications/new')
19
+ end
20
+
21
+ private
22
+
23
+ def connection
24
+ @_connection ||= Faraday.new do |conn|
25
+ conn.adapter Faraday.default_adapter
26
+ conn.url_prefix = base_url
27
+ conn.headers['X-Client-Api-Key'] = api_key
28
+ conn.request :json
29
+ conn.request :retry
30
+ conn.response :json
31
+ conn.response :logger, logger, **logger_options do |logger|
32
+ logger.filter(/(X-Client-Api-Key:).*"(.+)."/i, '\1 [FILTERED]')
33
+ logger.filter(/"nonce":"([^"]+)"/i, '"nonce":"[FILTERED]"')
34
+ end
35
+ end
36
+ end
37
+
38
+ def set_facade
39
+ @facade = DocFox::Authentication::Facade.new(response.body)
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocFox::BaseService < ActiveCall::Base
4
+ include ActiveCall::Api
5
+
6
+ self.abstract_class = true
7
+
8
+ CACHE_KEY = { access_token: 'doc_fox/base_service/access_token' }.freeze
9
+
10
+ config_accessor :base_url, default: 'https://www.docfoxapp.com/api/v2', instance_writer: false
11
+ config_accessor :cache, default: ActiveSupport::Cache::MemoryStore.new, instance_writer: false
12
+ config_accessor :logger, default: Logger.new($stdout), instance_writer: false
13
+ config_accessor :log_level, default: :info, instance_writer: false
14
+ config_accessor :log_headers, default: false, instance_writer: false
15
+ config_accessor :log_bodies, default: false, instance_writer: false
16
+ config_accessor :api_key, :secret, instance_writer: false
17
+
18
+ attr_reader :facade
19
+
20
+ class << self
21
+ def exception_mapping
22
+ {
23
+ validation_error: DocFox::ValidationError,
24
+ request_error: DocFox::RequestError,
25
+ client_error: DocFox::ClientError,
26
+ server_error: DocFox::ServerError,
27
+ bad_request: DocFox::BadRequestError,
28
+ unauthorized: DocFox::UnauthorizedError,
29
+ forbidden: DocFox::ForbiddenError,
30
+ not_found: DocFox::NotFoundError,
31
+ not_acceptable: DocFox::NotAcceptableError,
32
+ proxy_authentication_required: DocFox::ProxyAuthenticationRequiredError,
33
+ request_timeout: DocFox::RequestTimeoutError,
34
+ conflict: DocFox::ConflictError,
35
+ gone: DocFox::GoneError,
36
+ unprocessable_entity: DocFox::UnprocessableEntityError,
37
+ too_many_requests: DocFox::TooManyRequestsError,
38
+ internal_server_error: DocFox::InternalServerError,
39
+ not_implemented: DocFox::NotImplementedError,
40
+ bad_gateway: DocFox::BadGatewayError,
41
+ service_unavailable: DocFox::ServiceUnavailableError,
42
+ gateway_timeout: DocFox::GatewayTimeoutError
43
+ }.freeze
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def connection
50
+ @_connection ||= Faraday.new do |conn|
51
+ conn.adapter Faraday.default_adapter
52
+ conn.url_prefix = base_url
53
+ conn.request :authorization, 'Bearer', -> { access_token }
54
+ conn.request :json
55
+ conn.request :retry
56
+ conn.response :json
57
+ conn.response :logger, logger, **logger_options do |logger|
58
+ logger.filter(/(Authorization:).*"(.+)."/i, '\1 [FILTERED]')
59
+ end
60
+ end
61
+ end
62
+
63
+ def logger_options
64
+ {
65
+ headers: log_headers,
66
+ log_level: log_level,
67
+ bodies: log_bodies,
68
+ formatter: Faraday::Logging::ColorFormatter, prefix: { request: 'DocFox', response: 'DocFox' }
69
+ }
70
+ end
71
+
72
+ def access_token
73
+ access_token = cache.read(CACHE_KEY[:access_token])
74
+ return access_token if access_token.present?
75
+
76
+ service = DocFox::AccessToken::GetService.call
77
+ cache.fetch(CACHE_KEY[:access_token], expires_at: Time.parse(service.expires_at) - 10) { service.token }
78
+ end
79
+ end