erp_integration 0.52.0 → 0.53.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 +4 -4
- data/.github/workflows/pull_requests.yml +1 -1
- data/README.md +32 -1
- data/lib/erp_integration/api_keys_pool.rb +31 -0
- data/lib/erp_integration/configuration.rb +5 -5
- data/lib/erp_integration/fulfil/api_resource.rb +32 -26
- data/lib/erp_integration/fulfil/client.rb +13 -11
- data/lib/erp_integration/middleware/api_keys_rotation.rb +33 -0
- data/lib/erp_integration/middleware/logger.rb +51 -0
- data/lib/erp_integration/version.rb +1 -1
- data/lib/erp_integration.rb +3 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7719f3eaa455812be74cbdb4448118217e8294c90e742bb9d4074fad423a10ab
|
4
|
+
data.tar.gz: 35e221932e9c229957951869daa324c7f40549bf1cdfe21d7c56dc3202741832
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9835ef2f7400c112999ac8ca0135d7f11be0c69129bd4403f5620042b06394e09a6ec62c225d9d82d41d66b7d6290a602edf525d48fe910e92ab48562456711
|
7
|
+
data.tar.gz: 4a39d845dda9398b971334392fb58539a3cc208896a19e608c2207ddc040331b9e2a27ffef6ff1bcdc1ab56df4ba4a5de1a6da85399d37ca53a3b795f762771f
|
data/README.md
CHANGED
@@ -25,11 +25,42 @@ To configure the gem, create an initializer and add the following lines:
|
|
25
25
|
```erb
|
26
26
|
# config/initializers/erp_integration.rb
|
27
27
|
ErpIntegration.configure do |config|
|
28
|
-
config.
|
28
|
+
config.fulfil_api_keys = '<your-api-key>'
|
29
29
|
config.fulfil_merchant_id = '<your-merchant-id>'
|
30
30
|
end
|
31
31
|
```
|
32
32
|
|
33
|
+
You can configure multiple API keys, to enable rotation mechanism (see ["API key rotation"](README.md:L42)).
|
34
|
+
```erb
|
35
|
+
# config/initializers/erp_integration.rb
|
36
|
+
ErpIntegration.configure do |config|
|
37
|
+
config.fulfil_api_keys = ['<your-api-key1>', '<your-api-key2>']
|
38
|
+
config.fulfil_merchant_id = '<your-merchant-id>'
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
### API keys rotation
|
43
|
+
|
44
|
+
To set up API key rotation, configure multiple keys.
|
45
|
+
Every time a Fulfil client receives a `404` or `429`, it will rotate an API key.
|
46
|
+
|
47
|
+
```erb
|
48
|
+
# config/initializers/erp_integration.rb
|
49
|
+
ErpIntegration.configure do |config|
|
50
|
+
config.fulfil_api_keys = ['<your-api-key1>', '<your-api-key2>']
|
51
|
+
...
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
You can also configure a separate group of API keys for each resource.
|
56
|
+
From this point, `ErpIntegration::SalesOrder` will use custom API keys, and the other resources will continue to use the configured API keys pool.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
ErpIntegration::SalesOrder.api_keys_pool = '<your-api-key>'
|
60
|
+
# or
|
61
|
+
ErpIntegration::SalesOrder.api_keys_pool = ['<your-api-key1>', '<your-api-key2>']
|
62
|
+
```
|
63
|
+
|
33
64
|
### Supported Query Methods
|
34
65
|
|
35
66
|
After configuring the gem, one can easily query all the available ERP resources from the connected third-parties. In all cases, the API will return a collection of resources.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
# The `ApiKeysPool` class is a simple
|
5
|
+
# class that holds a list of API keys
|
6
|
+
# and rotates between them.
|
7
|
+
class ApiKeysPool
|
8
|
+
attr_reader :api_keys
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@api_keys = []
|
12
|
+
@current_key_index = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allows setting a list of API keys for the pool.
|
16
|
+
def api_keys=(keys)
|
17
|
+
@api_keys = Array(keys)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the current API key.
|
21
|
+
# @return [String] The current API key.
|
22
|
+
def current_key
|
23
|
+
@api_keys[@current_key_index]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Rotates the API key to the next one in the list.
|
27
|
+
def rotate_key!
|
28
|
+
@current_key_index = (@current_key_index + 1) % @api_keys.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,14 +8,14 @@ module ErpIntegration
|
|
8
8
|
# ```ruby
|
9
9
|
# # config/initializers/erp_integration.rb
|
10
10
|
# ErpIntegration.configure do |config|
|
11
|
-
# config.
|
11
|
+
# config.fulfil_api_keys = "..."
|
12
12
|
# end
|
13
13
|
# ```
|
14
14
|
class Configuration
|
15
|
-
# The `
|
16
|
-
# requests to the Fulfil API endpoints.
|
17
|
-
# @return [
|
18
|
-
attr_accessor :
|
15
|
+
# The `fulfil_api_keys` sets a single or a list of API keys to use
|
16
|
+
# in the `FulfilClient` to authorize the requests to the Fulfil API endpoints.
|
17
|
+
# @return [Array<Atring>] The API keys for Fulfil.
|
18
|
+
attr_accessor :fulfil_api_keys
|
19
19
|
|
20
20
|
# The `fulfil_merchant_id` is used by the `FulfilClient` to connect to
|
21
21
|
# the right Fulfil API endpoints.
|
@@ -17,7 +17,7 @@ module ErpIntegration
|
|
17
17
|
include QueryMethods
|
18
18
|
|
19
19
|
attr_accessor :resource_klass
|
20
|
-
delegate :client, :model_name, to: 'self.class'
|
20
|
+
delegate :api_keys_pool=, :client, :model_name, to: 'self.class'
|
21
21
|
|
22
22
|
def initialize(resource_klass)
|
23
23
|
@resource_klass = resource_klass
|
@@ -27,12 +27,24 @@ module ErpIntegration
|
|
27
27
|
# @return [ErpIntegration::Fulfil::Client] The HTTP client for Fulfil.
|
28
28
|
def self.client
|
29
29
|
Client.new(
|
30
|
-
|
30
|
+
api_keys_pool: api_keys_pool,
|
31
31
|
merchant_id: config.fulfil_merchant_id,
|
32
32
|
logger: config.logger
|
33
33
|
)
|
34
34
|
end
|
35
35
|
|
36
|
+
# The `api_keys_pool` exposes the API keys pool to the class.
|
37
|
+
# @return [ErpIntegration::ApiKeysPool] The API keys pool object.
|
38
|
+
def self.api_keys_pool
|
39
|
+
@api_keys_pool ||= ApiKeysPool.new.tap { |pool| pool.api_keys = config.fulfil_api_keys }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Allows setting a new API keys pool for the `ApiResource`
|
43
|
+
# @return [ErpIntegration::ApiKeysPool] ApiKeysPool object with the new API keys.
|
44
|
+
def self.api_keys_pool=(fulfil_api_keys)
|
45
|
+
@api_keys_pool = ApiKeysPool.new.tap { |pool| pool.api_keys = fulfil_api_keys }
|
46
|
+
end
|
47
|
+
|
36
48
|
# The `config` exposes the gem's configuration to the `ApiResource`.
|
37
49
|
# @return [ErpIntegration::Configuration] The gem's configuration object.
|
38
50
|
def self.config
|
@@ -60,19 +72,16 @@ module ErpIntegration
|
|
60
72
|
# be executed and the results will be fetched.
|
61
73
|
# @return [Array] An enumerable collection object with all API results.
|
62
74
|
def all
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
offset: calculated_offset
|
74
|
-
)
|
75
|
-
).map { |item| resource_klass.new(item) }
|
75
|
+
client.put(
|
76
|
+
api_resource_path,
|
77
|
+
Query.new(
|
78
|
+
fields: selected_fields,
|
79
|
+
filters: where_clauses,
|
80
|
+
alternative_filters: or_clauses,
|
81
|
+
limit: limit_value,
|
82
|
+
offset: calculated_offset
|
83
|
+
)
|
84
|
+
).map { |item| resource_klass.new(item) }
|
76
85
|
end
|
77
86
|
|
78
87
|
# As with the `all` method, the `query methods` lazyly build a search/read
|
@@ -81,17 +90,14 @@ module ErpIntegration
|
|
81
90
|
# the result will be fetched
|
82
91
|
# @return [Integer] The count of records that match with the query in Fulfil
|
83
92
|
def count
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
alternative_filters: or_clauses
|
93
|
-
).to_h.except(:fields)
|
94
|
-
)
|
93
|
+
client.put(
|
94
|
+
"model/#{model_name}/search_count",
|
95
|
+
Query.new(
|
96
|
+
fields: nil,
|
97
|
+
filters: where_clauses,
|
98
|
+
alternative_filters: or_clauses
|
99
|
+
).to_h.except(:fields)
|
100
|
+
)
|
95
101
|
end
|
96
102
|
|
97
103
|
# The `each` method turns the `ApiResource` instance into an enumerable object.
|
@@ -3,11 +3,11 @@
|
|
3
3
|
module ErpIntegration
|
4
4
|
module Fulfil
|
5
5
|
class Client
|
6
|
-
attr_reader :
|
7
|
-
attr_writer :connection, :faraday_adapter
|
6
|
+
attr_reader :api_keys_pool, :merchant_id
|
7
|
+
attr_writer :connection, :faraday_adapter, :rotate_statuses
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(api_keys_pool:, merchant_id:, logger: nil)
|
10
|
+
@api_keys_pool = api_keys_pool
|
11
11
|
@merchant_id = merchant_id
|
12
12
|
@logger = logger
|
13
13
|
end
|
@@ -36,13 +36,12 @@ module ErpIntegration
|
|
36
36
|
faraday.response :follow_redirects
|
37
37
|
faraday.response :json # Decode response bodies as JSON
|
38
38
|
|
39
|
-
# Notice that logging headers will expose sensitive information
|
40
|
-
# like api-key. To avoid use filters:
|
41
|
-
# https://lostisland.github.io/faraday/middleware/logger
|
42
|
-
faraday.response :logger, @logger, { headers: false, bodies: true } if @logger
|
43
|
-
|
44
39
|
# Custom error handling for the error response
|
45
40
|
faraday.use ErpIntegration::Middleware::ErrorHandling
|
41
|
+
# Custom middleware for rotating API keys
|
42
|
+
faraday.use ErpIntegration::Middleware::ApiKeysRotation, api_keys_pool: api_keys_pool
|
43
|
+
# Custom middleware for logging requests and responses
|
44
|
+
faraday.use ErpIntegration::Middleware::Logger, @logger if @logger
|
46
45
|
|
47
46
|
# Adapter definition should be last in order to make the json parsers be loaded correctly
|
48
47
|
faraday.adapter faraday_adapter
|
@@ -67,11 +66,14 @@ module ErpIntegration
|
|
67
66
|
|
68
67
|
private
|
69
68
|
|
69
|
+
def api_key
|
70
|
+
api_keys_pool.current_key
|
71
|
+
end
|
72
|
+
|
70
73
|
def default_headers
|
71
74
|
{
|
72
75
|
'Accept': 'application/json',
|
73
|
-
'Content-Type': 'application/json'
|
74
|
-
'X-API-KEY': api_key
|
76
|
+
'Content-Type': 'application/json'
|
75
77
|
}
|
76
78
|
end
|
77
79
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
module Middleware
|
5
|
+
class ApiKeysRotation < Faraday::Middleware
|
6
|
+
HTTP_ROTATE_CODES = [404, 429].freeze
|
7
|
+
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(app, options = {})
|
11
|
+
super(app)
|
12
|
+
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
on_request(env)
|
18
|
+
|
19
|
+
@app.call(env).on_complete { |response| on_complete(response) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_request(env)
|
23
|
+
env[:request_headers]['X-API-KEY'] = options[:api_keys_pool].current_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_complete(env)
|
27
|
+
return unless HTTP_ROTATE_CODES.include?(env.status)
|
28
|
+
|
29
|
+
options[:api_keys_pool].rotate_key!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday/logging/formatter'
|
4
|
+
|
5
|
+
module ErpIntegration
|
6
|
+
module Middleware
|
7
|
+
class Logger < Faraday::Middleware
|
8
|
+
# Notice that logging headers will expose sensitive information
|
9
|
+
# like api-key.
|
10
|
+
DEFAULT_OPTIONS = { headers: false, bodies: true, errors: true }.freeze
|
11
|
+
|
12
|
+
def initialize(app, logger = nil, options = {})
|
13
|
+
super(app)
|
14
|
+
|
15
|
+
@logger = logger
|
16
|
+
formatter_klass = options.delete(:formatter) || Faraday::Logging::Formatter
|
17
|
+
@formatter = formatter_klass.new(logger: logger, options: DEFAULT_OPTIONS)
|
18
|
+
yield @formatter if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
api_key_fragment = sanitize_api_key(env[:request_headers]['X-API-KEY'])
|
23
|
+
|
24
|
+
if @logger.respond_to?(:tagged)
|
25
|
+
@logger.tagged("API key *#{api_key_fragment}") do
|
26
|
+
@formatter.request(env)
|
27
|
+
|
28
|
+
@app.call(env).on_complete { |response| on_complete(response) }
|
29
|
+
end
|
30
|
+
else
|
31
|
+
@logger.info("Requested ERP with API key *#{api_key_fragment}")
|
32
|
+
@formatter.request(env)
|
33
|
+
|
34
|
+
@app.call(env).on_complete { |response| on_complete(response) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_complete(env)
|
39
|
+
@formatter.response(env)
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_error(exc)
|
43
|
+
@formatter.exception(exc) if @formatter.respond_to?(:exception)
|
44
|
+
end
|
45
|
+
|
46
|
+
def sanitize_api_key(api_key)
|
47
|
+
api_key[-4..-1] if api_key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/erp_integration.rb
CHANGED
@@ -10,10 +10,13 @@ require 'json'
|
|
10
10
|
|
11
11
|
require_relative 'erp_integration/version'
|
12
12
|
require_relative 'erp_integration/errors'
|
13
|
+
require_relative 'erp_integration/api_keys_pool'
|
13
14
|
require_relative 'erp_integration/configuration'
|
14
15
|
|
15
16
|
# Middleware
|
17
|
+
require_relative 'erp_integration/middleware/api_keys_rotation'
|
16
18
|
require_relative 'erp_integration/middleware/error_handling'
|
19
|
+
require_relative 'erp_integration/middleware/logger'
|
17
20
|
|
18
21
|
# HTTP clients
|
19
22
|
require_relative 'erp_integration/fulfil/client'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erp_integration
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.53.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Vermaas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -259,6 +259,7 @@ files:
|
|
259
259
|
- bin/setup
|
260
260
|
- erp_integration.gemspec
|
261
261
|
- lib/erp_integration.rb
|
262
|
+
- lib/erp_integration/api_keys_pool.rb
|
262
263
|
- lib/erp_integration/bill_of_material.rb
|
263
264
|
- lib/erp_integration/bill_of_material_input.rb
|
264
265
|
- lib/erp_integration/bill_of_material_output.rb
|
@@ -316,7 +317,9 @@ files:
|
|
316
317
|
- lib/erp_integration/gift_card.rb
|
317
318
|
- lib/erp_integration/internal_shipment.rb
|
318
319
|
- lib/erp_integration/location.rb
|
320
|
+
- lib/erp_integration/middleware/api_keys_rotation.rb
|
319
321
|
- lib/erp_integration/middleware/error_handling.rb
|
322
|
+
- lib/erp_integration/middleware/logger.rb
|
320
323
|
- lib/erp_integration/party_address.rb
|
321
324
|
- lib/erp_integration/product.rb
|
322
325
|
- lib/erp_integration/product_category.rb
|
@@ -365,7 +368,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
365
368
|
- !ruby/object:Gem::Version
|
366
369
|
version: '0'
|
367
370
|
requirements: []
|
368
|
-
rubygems_version: 3.
|
371
|
+
rubygems_version: 3.2.22
|
369
372
|
signing_key:
|
370
373
|
specification_version: 4
|
371
374
|
summary: Connects Mejuri with third-party ERP vendors
|