erp_integration 0.50.0 → 0.53.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|