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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1164d8044b67e9e2b3a76d2fc3fe1cc2a293e0a930a5e3e8f2424ec48089b9a
4
- data.tar.gz: 11e5bccbf22d70be8496049fb731cd6fe782cba50a2283520eaeacd836b71631
3
+ metadata.gz: 7719f3eaa455812be74cbdb4448118217e8294c90e742bb9d4074fad423a10ab
4
+ data.tar.gz: 35e221932e9c229957951869daa324c7f40549bf1cdfe21d7c56dc3202741832
5
5
  SHA512:
6
- metadata.gz: 7e62a94f229b19e3ea1fa70f315e9e6e6295fa0d2ee309505787031ca69aaf6188449418abf72f9af2c2682debe083eca14eca7b5e259c5fc0c29788c0e84522
7
- data.tar.gz: f78b319f38240e564de0136021df8f3cd866cdecbd6b409d814b3111c0acf2d200e173f685f5e110d1cc0c6957b5eff49d05e9ffb0783ec059c1a789579f8dce
6
+ metadata.gz: e9835ef2f7400c112999ac8ca0135d7f11be0c69129bd4403f5620042b06394e09a6ec62c225d9d82d41d66b7d6290a602edf525d48fe910e92ab48562456711
7
+ data.tar.gz: 4a39d845dda9398b971334392fb58539a3cc208896a19e608c2207ddc040331b9e2a27ffef6ff1bcdc1ab56df4ba4a5de1a6da85399d37ca53a3b795f762771f
@@ -15,4 +15,4 @@ jobs:
15
15
  steps:
16
16
  - uses: seferov/pr-lint-action@master
17
17
  with:
18
- title-regex: '^\[RELEASE|NO-TICKET|DL|UDL|AD|DU|AR|IN|CON|DIS|AS|MT|SCR|RO|DEL|BE-\d*\](\ )'
18
+ title-regex: '^\[RELEASE|NO-TICKET|DL|UDL|AD|DU|AR|IN|CON|DIS|AS|MT|SCR|RO|DEL|BE|STORE-\d*\](\ )'
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.fulfil_api_key = '<your-api-key>'
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.fulfil_api_key = "..."
11
+ # config.fulfil_api_keys = "..."
12
12
  # end
13
13
  # ```
14
14
  class Configuration
15
- # The `fulfil_api_key` is used by the `FulfilClient` to authorize the
16
- # requests to the Fulfil API endpoints.
17
- # @return [String] The API key for Fulfil.
18
- attr_accessor :fulfil_api_key
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
- api_key: config.fulfil_api_key,
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
- return @results if defined?(@results)
64
-
65
- @results =
66
- client.put(
67
- api_resource_path,
68
- Query.new(
69
- fields: selected_fields,
70
- filters: where_clauses,
71
- alternative_filters: or_clauses,
72
- limit: limit_value,
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
- return @count if defined?(@count)
85
-
86
- @count =
87
- client.put(
88
- "model/#{model_name}/search_count",
89
- Query.new(
90
- fields: nil,
91
- filters: where_clauses,
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 :api_key, :merchant_id
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(api_key:, merchant_id:, logger: nil)
10
- @api_key = api_key
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../api_resource'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ module Resources
8
+ class PartyAddress < ApiResource
9
+ self.model_name = 'party.address'
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ class PartyAddress < Resource
5
+ attr_accessor :id, :phone, :email
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ErpIntegration
4
- VERSION = '0.50.0'
4
+ VERSION = '0.53.0'
5
5
  end
@@ -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.50.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-08-06 00:00:00.000000000 Z
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.4.10
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