erp_integration 0.50.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 +14 -5
- data/lib/erp_integration/fulfil/api_resource.rb +32 -26
- data/lib/erp_integration/fulfil/client.rb +13 -11
- data/lib/erp_integration/fulfil/resources/party_address.rb +13 -0
- data/lib/erp_integration/middleware/api_keys_rotation.rb +33 -0
- data/lib/erp_integration/middleware/logger.rb +51 -0
- data/lib/erp_integration/party_address.rb +7 -0
- data/lib/erp_integration/version.rb +1 -1
- data/lib/erp_integration.rb +4 -0
- metadata +8 -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.
|
@@ -152,6 +152,11 @@ module ErpIntegration
|
|
152
152
|
# @return [Symbol] The configured adapter for the tracking number.
|
153
153
|
attr_writer :sales_line_option_adapter
|
154
154
|
|
155
|
+
# Allows configuring an adapter for the `PartyAddress` resource. When
|
156
|
+
# none is configured, it will default to Fulfil.
|
157
|
+
# @return [Symbol] The configured adapter for the tracking number.
|
158
|
+
attr_writer :party_address_adapter
|
159
|
+
|
155
160
|
# Allows configuring an adapter for the `ProductOption` resource. When
|
156
161
|
# none is configured, it will default to Fulfil.
|
157
162
|
# @return [Symbol] The configured adapter for the tracking number.
|
@@ -287,6 +292,10 @@ module ErpIntegration
|
|
287
292
|
@sales_line_option_adapter || :fulfil
|
288
293
|
end
|
289
294
|
|
295
|
+
def party_address_adapter
|
296
|
+
@party_address_adapter || :fulfil
|
297
|
+
end
|
298
|
+
|
290
299
|
def product_option_adapter
|
291
300
|
@product_option_adapter || :fulfil
|
292
301
|
end
|
@@ -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'
|
@@ -45,6 +48,7 @@ module ErpIntegration
|
|
45
48
|
autoload :Resource, 'erp_integration/resource'
|
46
49
|
autoload :SalesLineOption, 'erp_integration/sales_line_option'
|
47
50
|
autoload :SalesOrder, 'erp_integration/sales_order'
|
51
|
+
autoload :PartyAddress, 'erp_integration/party_address'
|
48
52
|
autoload :SalesOrderLine, 'erp_integration/sales_order_line'
|
49
53
|
autoload :SalesReturnReason, 'erp_integration/sales_return_reason'
|
50
54
|
autoload :StockBinTransfer, 'erp_integration/stock_bin_transfer'
|
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
|
@@ -293,6 +294,7 @@ files:
|
|
293
294
|
- lib/erp_integration/fulfil/resources/gift_card.rb
|
294
295
|
- lib/erp_integration/fulfil/resources/internal_shipment.rb
|
295
296
|
- lib/erp_integration/fulfil/resources/location.rb
|
297
|
+
- lib/erp_integration/fulfil/resources/party_address.rb
|
296
298
|
- lib/erp_integration/fulfil/resources/product.rb
|
297
299
|
- lib/erp_integration/fulfil/resources/product_category.rb
|
298
300
|
- lib/erp_integration/fulfil/resources/product_option.rb
|
@@ -315,7 +317,10 @@ files:
|
|
315
317
|
- lib/erp_integration/gift_card.rb
|
316
318
|
- lib/erp_integration/internal_shipment.rb
|
317
319
|
- lib/erp_integration/location.rb
|
320
|
+
- lib/erp_integration/middleware/api_keys_rotation.rb
|
318
321
|
- lib/erp_integration/middleware/error_handling.rb
|
322
|
+
- lib/erp_integration/middleware/logger.rb
|
323
|
+
- lib/erp_integration/party_address.rb
|
319
324
|
- lib/erp_integration/product.rb
|
320
325
|
- lib/erp_integration/product_category.rb
|
321
326
|
- lib/erp_integration/product_option.rb
|
@@ -363,7 +368,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
363
368
|
- !ruby/object:Gem::Version
|
364
369
|
version: '0'
|
365
370
|
requirements: []
|
366
|
-
rubygems_version: 3.
|
371
|
+
rubygems_version: 3.2.22
|
367
372
|
signing_key:
|
368
373
|
specification_version: 4
|
369
374
|
summary: Connects Mejuri with third-party ERP vendors
|