fakturoid 0.5.0 → 1.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -10
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +15 -0
  5. data/README.md +540 -145
  6. data/Rakefile +3 -1
  7. data/fakturoid.gemspec +36 -25
  8. data/lib/fakturoid/api/account.rb +13 -0
  9. data/lib/fakturoid/api/bank_account.rb +13 -0
  10. data/lib/fakturoid/api/base.rb +18 -0
  11. data/lib/fakturoid/api/event.rb +23 -0
  12. data/lib/fakturoid/api/expense.rb +55 -0
  13. data/lib/fakturoid/api/expense_payment.rb +20 -0
  14. data/lib/fakturoid/api/generator.rb +36 -0
  15. data/lib/fakturoid/api/inbox_file.rb +34 -0
  16. data/lib/fakturoid/api/inventory_item.rb +66 -0
  17. data/lib/fakturoid/api/inventory_move.rb +40 -0
  18. data/lib/fakturoid/api/invoice.rb +62 -0
  19. data/lib/fakturoid/api/invoice_message.rb +14 -0
  20. data/lib/fakturoid/api/invoice_payment.rb +26 -0
  21. data/lib/fakturoid/api/number_format.rb +13 -0
  22. data/lib/fakturoid/api/recurring_generator.rb +36 -0
  23. data/lib/fakturoid/api/subject.rb +42 -0
  24. data/lib/fakturoid/api/todo.rb +20 -0
  25. data/lib/fakturoid/api/user.rb +17 -0
  26. data/lib/fakturoid/api/webhook.rb +34 -0
  27. data/lib/fakturoid/api.rb +89 -9
  28. data/lib/fakturoid/client.rb +46 -12
  29. data/lib/fakturoid/config.rb +69 -12
  30. data/lib/fakturoid/oauth/access_token_service.rb +46 -0
  31. data/lib/fakturoid/oauth/credentials.rb +63 -0
  32. data/lib/fakturoid/oauth/flow/authorization_code.rb +53 -0
  33. data/lib/fakturoid/oauth/flow/base.rb +42 -0
  34. data/lib/fakturoid/oauth/flow/client_credentials.rb +27 -0
  35. data/lib/fakturoid/oauth/flow.rb +5 -0
  36. data/lib/fakturoid/oauth/request/api.rb +11 -0
  37. data/lib/fakturoid/oauth/request/base.rb +60 -0
  38. data/lib/fakturoid/oauth/request/oauth.rb +24 -0
  39. data/lib/fakturoid/oauth/request.rb +5 -0
  40. data/lib/fakturoid/oauth/token_response.rb +56 -0
  41. data/lib/fakturoid/oauth.rb +39 -0
  42. data/lib/fakturoid/response.rb +8 -20
  43. data/lib/fakturoid/utils.rb +27 -0
  44. data/lib/fakturoid/version.rb +1 -1
  45. data/lib/fakturoid.rb +22 -22
  46. metadata +48 -53
  47. data/.github/workflows/rubocop.yml +0 -20
  48. data/.github/workflows/tests.yml +0 -27
  49. data/.gitignore +0 -7
  50. data/Gemfile +0 -6
  51. data/lib/fakturoid/api/arguments.rb +0 -21
  52. data/lib/fakturoid/api/http_methods.rb +0 -23
  53. data/lib/fakturoid/client/account.rb +0 -11
  54. data/lib/fakturoid/client/bank_account.rb +0 -11
  55. data/lib/fakturoid/client/event.rb +0 -19
  56. data/lib/fakturoid/client/expense.rb +0 -49
  57. data/lib/fakturoid/client/generator.rb +0 -44
  58. data/lib/fakturoid/client/inventory_items.rb +0 -59
  59. data/lib/fakturoid/client/inventory_moves.rb +0 -36
  60. data/lib/fakturoid/client/invoice.rb +0 -73
  61. data/lib/fakturoid/client/number_format.rb +0 -11
  62. data/lib/fakturoid/client/subject.rb +0 -41
  63. data/lib/fakturoid/client/todo.rb +0 -18
  64. data/lib/fakturoid/client/user.rb +0 -20
  65. data/lib/fakturoid/connection.rb +0 -30
  66. data/lib/fakturoid/request.rb +0 -31
  67. data/test/api_test.rb +0 -24
  68. data/test/config_test.rb +0 -40
  69. data/test/fixtures/blocked_account.json +0 -8
  70. data/test/fixtures/invoice.json +0 -81
  71. data/test/fixtures/invoice.pdf +0 -0
  72. data/test/fixtures/invoice_error.json +0 -7
  73. data/test/fixtures/subjects.json +0 -52
  74. data/test/request_test.rb +0 -20
  75. data/test/response_test.rb +0 -189
  76. data/test/test_helper.rb +0 -19
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ module Api
5
+ class Webhook
6
+ include Base
7
+
8
+ def all(params = {})
9
+ request_params = Utils.permit_params(params, :page)
10
+
11
+ perform_request(HTTP_GET, "webhooks.json", request_params: request_params)
12
+ end
13
+
14
+ def find(id)
15
+ Utils.validate_numerical_id(id)
16
+ perform_request(HTTP_GET, "webhooks/#{id}.json")
17
+ end
18
+
19
+ def create(payload = {})
20
+ perform_request(HTTP_POST, "webhooks.json", payload: payload)
21
+ end
22
+
23
+ def update(id, payload = {})
24
+ Utils.validate_numerical_id(id)
25
+ perform_request(HTTP_PATCH, "webhooks/#{id}.json", payload: payload)
26
+ end
27
+
28
+ def delete(id)
29
+ Utils.validate_numerical_id(id)
30
+ perform_request(HTTP_DELETE, "webhooks/#{id}.json")
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/fakturoid/api.rb CHANGED
@@ -1,19 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fakturoid/api/arguments"
4
- require "fakturoid/api/http_methods"
3
+ require_relative "api/base"
4
+
5
+ # Sorted alphabetically
6
+ require_relative "api/account"
7
+ require_relative "api/bank_account"
8
+ require_relative "api/event"
9
+ require_relative "api/expense"
10
+ require_relative "api/expense_payment"
11
+ require_relative "api/generator"
12
+ require_relative "api/inbox_file"
13
+ require_relative "api/inventory_item"
14
+ require_relative "api/inventory_move"
15
+ require_relative "api/invoice"
16
+ require_relative "api/invoice_message"
17
+ require_relative "api/invoice_payment"
18
+ require_relative "api/number_format"
19
+ require_relative "api/recurring_generator"
20
+ require_relative "api/subject"
21
+ require_relative "api/todo"
22
+ require_relative "api/user"
23
+ require_relative "api/webhook"
5
24
 
6
25
  module Fakturoid
7
- class Api
8
- extend Arguments
9
- extend HttpMethods
26
+ module Api
27
+ def account
28
+ @account ||= Account.new(self)
29
+ end
30
+
31
+ def bank_accounts
32
+ @bank_accounts ||= BankAccount.new(self)
33
+ end
34
+
35
+ def events
36
+ @events ||= Event.new(self)
37
+ end
38
+
39
+ def expenses
40
+ @expenses ||= Expense.new(self)
41
+ end
42
+
43
+ def expense_payments
44
+ @expense_payments ||= ExpensePayment.new(self)
45
+ end
46
+
47
+ def generators
48
+ @generators ||= Generator.new(self)
49
+ end
50
+
51
+ def inbox_files
52
+ @inbox_files ||= InboxFile.new(self)
53
+ end
54
+
55
+ def inventory_items
56
+ @inventory_items ||= InventoryItem.new(self)
57
+ end
58
+
59
+ def inventory_moves
60
+ @inventory_moves ||= InventoryMove.new(self)
61
+ end
62
+
63
+ def invoices
64
+ @invoices ||= Invoice.new(self)
65
+ end
66
+
67
+ def invoice_messages
68
+ @invoice_messages ||= InvoiceMessage.new(self)
69
+ end
70
+
71
+ def invoice_payments
72
+ @invoice_payments ||= InvoicePayment.new(self)
73
+ end
74
+
75
+ def number_formats
76
+ @number_formats ||= NumberFormat.new(self)
77
+ end
78
+
79
+ def recurring_generators
80
+ @recurring_generators ||= RecurringGenerator.new(self)
81
+ end
82
+
83
+ def subjects
84
+ @subjects ||= Subject.new(self)
85
+ end
86
+
87
+ def todos
88
+ @todos ||= Todo.new(self)
89
+ end
10
90
 
11
- def self.configure(&block)
12
- @config ||= Fakturoid::Config.new(&block)
91
+ def users
92
+ @users ||= User.new(self)
13
93
  end
14
94
 
15
- def self.config
16
- @config
95
+ def webhooks
96
+ @webhooks ||= Webhook.new(self)
17
97
  end
18
98
  end
19
99
  end
@@ -1,14 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fakturoid/client/account"
4
- require "fakturoid/client/bank_account"
5
- require "fakturoid/client/number_format"
6
- require "fakturoid/client/user"
7
- require "fakturoid/client/subject"
8
- require "fakturoid/client/invoice"
9
- require "fakturoid/client/inventory_items"
10
- require "fakturoid/client/inventory_moves"
11
- require "fakturoid/client/expense"
12
- require "fakturoid/client/generator"
13
- require "fakturoid/client/event"
14
- require "fakturoid/client/todo"
3
+ module Fakturoid
4
+ class Client
5
+ extend Forwardable
6
+ include Api
7
+
8
+ attr_reader :config
9
+
10
+ # Authorization methods
11
+ def_delegators :@oauth, :authorization_uri, :authorize, :revoke_access, :perform_request
12
+
13
+ def self.configure(&block)
14
+ @config ||= Fakturoid::Config.new(&block) # rubocop:disable Naming/MemoizedInstanceVariableName
15
+ end
16
+
17
+ def self.config
18
+ @config
19
+ end
20
+
21
+ def initialize(config = {})
22
+ raise ConfigurationError, "Configuration is missing" if self.class.config.nil?
23
+
24
+ @config = self.class.config.duplicate(config)
25
+ @oauth = Oauth.new(self)
26
+ end
27
+
28
+ def account=(account)
29
+ config.account = account
30
+ end
31
+
32
+ def credentials
33
+ config.credentials
34
+ end
35
+
36
+ def credentials=(values)
37
+ config.credentials = values
38
+ end
39
+
40
+ def credentials_updated_callback(&block)
41
+ config.credentials_updated_callback = block
42
+ end
43
+
44
+ def call_credentials_updated_callback
45
+ config.credentials_updated_callback&.call(config.credentials)
46
+ end
47
+ end
48
+ end
@@ -2,39 +2,96 @@
2
2
 
3
3
  module Fakturoid
4
4
  class Config
5
- attr_accessor :email, :api_key, :account
5
+ attr_accessor :email, :account, :client_id, :client_secret, :oauth_flow, :redirect_uri, :credentials_updated_callback
6
6
  attr_writer :user_agent
7
7
 
8
- ENDPOINT = "https://app.fakturoid.cz/api/v2"
8
+ SUPPORTED_FLOWS = %w[authorization_code client_credentials].freeze
9
+ API_ENDPOINT = "https://app.fakturoid.cz/api/v3"
10
+ # API_ENDPOINT = "http://app.fakturoid.localhost/api/v3" # For development purposes
11
+ OAUTH_ENDPOINT = "#{API_ENDPOINT}/oauth".freeze
9
12
 
10
- def initialize(&_block)
13
+ def initialize
11
14
  yield self
15
+
16
+ validate_configuration
17
+ end
18
+
19
+ def credentials
20
+ @credentials ||= Oauth::Credentials.new
21
+ end
22
+
23
+ def credentials=(values)
24
+ @credentials = values.is_a?(Hash) ? Oauth::Credentials.new(values) : values
12
25
  end
13
26
 
14
27
  def user_agent
15
- if !defined?(@user_agent) || @user_agent.nil? || @user_agent.empty?
28
+ if Utils.empty?(@user_agent)
16
29
  "Fakturoid ruby gem (#{email})"
17
30
  else
18
31
  @user_agent
19
32
  end
20
33
  end
21
34
 
22
- def endpoint
23
- "#{ENDPOINT}/accounts/#{account}"
35
+ def api_endpoint
36
+ raise ConfigurationError, "Account slug is required" if Utils.empty?(account)
37
+
38
+ "#{API_ENDPOINT}/accounts/#{account}"
39
+ end
40
+
41
+ def api_endpoint_without_account
42
+ API_ENDPOINT
43
+ end
44
+
45
+ def oauth_endpoint
46
+ OAUTH_ENDPOINT
24
47
  end
25
48
 
26
- def endpoint_without_account
27
- ENDPOINT
49
+ def authorization_uri(state: nil)
50
+ params = {
51
+ client_id: client_id,
52
+ redirect_uri: redirect_uri,
53
+ response_type: "code"
54
+ }
55
+ params[:state] = state unless Utils.empty?(state)
56
+
57
+ connection = Faraday::Connection.new(oauth_endpoint)
58
+ connection.build_url(nil, params)
28
59
  end
29
60
 
30
- def faraday_v1?
31
- major_faraday_version == "1"
61
+ def access_token_auth_header
62
+ "#{credentials.token_type} #{credentials.access_token}"
63
+ end
64
+
65
+ def authorization_code_flow?
66
+ oauth_flow == "authorization_code"
67
+ end
68
+
69
+ def client_credentials_flow?
70
+ oauth_flow == "client_credentials"
71
+ end
72
+
73
+ # We can create multiple instances of the client, make sure we isolate the config for each
74
+ # as it contains credentials which must not be shared.
75
+ def duplicate(new_config)
76
+ self.class.new do |config|
77
+ config.email = email
78
+ config.account = new_config[:account] || account
79
+ config.user_agent = user_agent
80
+ config.client_id = client_id
81
+ config.client_secret = client_secret
82
+ config.oauth_flow = oauth_flow # 'client_credentials', 'authorization_code'
83
+ # only authorization_code
84
+ config.redirect_uri = redirect_uri
85
+ end
32
86
  end
33
87
 
34
88
  private
35
89
 
36
- def major_faraday_version
37
- @major_faraday_version ||= Faraday::VERSION.split(".").first
90
+ def validate_configuration
91
+ raise ConfigurationError, "Missing or unsupported OAuth flow, supported flows are - `authorization_code`, `client_credentials`" unless SUPPORTED_FLOWS.include?(oauth_flow)
92
+ raise ConfigurationError, "`email` or `user` agent is required" if Utils.empty?(email) && Utils.empty?(user_agent)
93
+ raise ConfigurationError, "Client credentials are required" if Utils.empty?(client_id) || Utils.empty?(client_secret)
94
+ raise ConfigurationError, "`redirect_uri` is required for Authorization Code Flow" if authorization_code_flow? && Utils.empty?(redirect_uri)
38
95
  end
39
96
  end
40
97
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ class AccessTokenService
6
+ attr_reader :oauth, :client
7
+
8
+ def initialize(oauth)
9
+ @oauth = oauth
10
+ @client = oauth.client
11
+ end
12
+
13
+ def perform_request(method, path, params)
14
+ check_access_token
15
+ fetch_access_token if client.config.credentials.access_token_expired?
16
+
17
+ retried = false
18
+
19
+ begin
20
+ Request::Api.new(method, path, client).call(params)
21
+ rescue AuthenticationError
22
+ raise if retried
23
+ retried = true
24
+ fetch_access_token
25
+
26
+ retry
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def check_access_token
33
+ raise ArgumentError, "OAuth access was not authorized by user" unless oauth.authorized?
34
+ return unless Utils.empty?(client.config.credentials.access_token)
35
+
36
+ fetch_access_token
37
+ end
38
+
39
+ def fetch_access_token
40
+ oauth.fetch_access_token.tap do
41
+ client.call_credentials_updated_callback
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ class Credentials
6
+ EXPIRY_BUFFER_IN_SECONDS = 10
7
+ MAX_EXPIRY_IN_SECONDS = 2 * 3600 # 2 hours
8
+
9
+ attr_accessor :access_token, :refresh_token, :token_type
10
+ attr_reader :expires_at
11
+
12
+ def initialize(values = {})
13
+ update(values)
14
+ end
15
+
16
+ def update(values)
17
+ values = values.transform_keys(&:to_sym)
18
+
19
+ self.access_token = values[:access_token]
20
+ self.refresh_token = values[:refresh_token] unless Utils.empty?(values[:refresh_token])
21
+ self.expires_at = values[:expires_at] || values[:expires_in]
22
+ self.token_type ||= values[:token_type]
23
+ end
24
+
25
+ def expires_at=(value)
26
+ @expires_at = parse_expires_at(value)
27
+ end
28
+
29
+ def expires_in=(value)
30
+ self.expires_at = value
31
+ end
32
+
33
+ def access_token_expired?
34
+ Time.now > (expires_at - EXPIRY_BUFFER_IN_SECONDS)
35
+ end
36
+
37
+ def as_json
38
+ {
39
+ access_token: access_token,
40
+ refresh_token: refresh_token,
41
+ expires_at: expires_at.to_datetime, # `DateTime` serializes into is8601, `Time` doesn't, so it can be saved as JSON safely.
42
+ token_type: token_type
43
+ }
44
+ end
45
+
46
+ private
47
+
48
+ def parse_expires_at(value)
49
+ case value
50
+ when DateTime
51
+ value.to_time
52
+ when String
53
+ Time.parse(value)
54
+ when Integer # `value` in seconds
55
+ raise ArgumentError, "`expires_at` cannot be unix timestamp (was #{value.inspect})" if value > MAX_EXPIRY_IN_SECONDS
56
+ Time.now + value
57
+ else
58
+ value
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Flow
6
+ class AuthorizationCode
7
+ include Base
8
+
9
+ GRANT_TYPE = "authorization_code"
10
+
11
+ def authorization_uri(state: nil)
12
+ client.config.authorization_uri(state: state)
13
+ end
14
+
15
+ def authorize(code:)
16
+ payload = {
17
+ grant_type: GRANT_TYPE,
18
+ redirect_uri: client.config.redirect_uri,
19
+ code: code
20
+ }
21
+
22
+ response = perform_request(HTTP_POST, "token.json", payload: payload)
23
+ client.config.credentials.update(response.body)
24
+ client.call_credentials_updated_callback
25
+ response
26
+ end
27
+
28
+ def fetch_access_token
29
+ payload = {
30
+ grant_type: "refresh_token",
31
+ refresh_token: client.config.credentials.refresh_token
32
+ }
33
+
34
+ response = perform_request(HTTP_POST, "token.json", payload: payload)
35
+ client.config.credentials.update(response.body)
36
+ response
37
+ end
38
+
39
+ def revoke_access
40
+ payload = {
41
+ token: client.config.credentials.refresh_token
42
+ }
43
+
44
+ perform_request(HTTP_POST, "revoke.json", payload: payload)
45
+ end
46
+
47
+ def authorized?
48
+ !Utils.empty?(client.config.credentials.refresh_token)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Flow
6
+ module Base
7
+ attr_reader :client
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def authorization_uri(state: nil)
14
+ raise NotImplementedError, "Authorization path is not supported"
15
+ end
16
+
17
+ def authorize(code:)
18
+ raise NotImplementedError, "Authorize is not supported"
19
+ end
20
+
21
+ def fetch_access_token
22
+ raise NotImplementedError, "Fetch access token is not supported"
23
+ end
24
+
25
+ def revoke_access
26
+ raise NotImplementedError, "Revoke access is not supported"
27
+ end
28
+
29
+ def authorized?
30
+ raise NotImplementedError, "Authorized is not supported"
31
+ end
32
+
33
+ protected
34
+
35
+ def perform_request(method, path, params)
36
+ raw_response = Request::Oauth.new(method, path, client).call(params)
37
+ TokenResponse.new(raw_response)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Flow
6
+ class ClientCredentials
7
+ include Base
8
+
9
+ GRANT_TYPE = "client_credentials"
10
+
11
+ def fetch_access_token
12
+ payload = {
13
+ grant_type: GRANT_TYPE
14
+ }
15
+
16
+ response = perform_request(HTTP_POST, "token.json", payload: payload)
17
+ client.config.credentials.update(response.body)
18
+ response
19
+ end
20
+
21
+ def authorized?
22
+ true
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "flow/base"
4
+ require_relative "flow/authorization_code"
5
+ require_relative "flow/client_credentials"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Request
6
+ class Api
7
+ include Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Request
6
+ module Base
7
+ attr_reader :method, :path, :client
8
+
9
+ HTTP_METHODS = [:get, :post, :patch, :delete].freeze
10
+ REQUEST_TIMEOUT = 10
11
+
12
+ def initialize(method, path, client)
13
+ @method = method
14
+ @path = path
15
+ @client = client
16
+ end
17
+
18
+ def call(params = {})
19
+ raise ArgumentError, "Unknown http method: #{method}" unless HTTP_METHODS.include?(method.to_sym)
20
+
21
+ request_params = params[:request_params] || {}
22
+
23
+ http_connection = connection(params)
24
+
25
+ http_connection.send(method) do |req|
26
+ req.url path, request_params
27
+ req.body = MultiJson.dump(params[:payload]) if params.key?(:payload)
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def default_options(options = {})
34
+ {
35
+ headers: {
36
+ content_type: "application/json",
37
+ accept: "application/json",
38
+ user_agent: client.config.user_agent
39
+ },
40
+ url: options[:url] || endpoint,
41
+ request: {
42
+ timeout: REQUEST_TIMEOUT
43
+ }
44
+ }
45
+ end
46
+
47
+ def endpoint
48
+ client.config.api_endpoint
49
+ end
50
+
51
+ def connection(options = {})
52
+ Faraday.new default_options(options) do |conn|
53
+ conn.headers[:authorization] = client.config.access_token_auth_header
54
+ conn.adapter Faraday.default_adapter
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fakturoid
4
+ class Oauth
5
+ module Request
6
+ class Oauth
7
+ include Base
8
+
9
+ protected
10
+
11
+ def connection(options = {})
12
+ Faraday.new default_options(options) do |conn|
13
+ conn.set_basic_auth(client.config.client_id, client.config.client_secret)
14
+ conn.adapter Faraday.default_adapter
15
+ end
16
+ end
17
+
18
+ def endpoint
19
+ client.config.oauth_endpoint
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request/base"
4
+ require_relative "request/api"
5
+ require_relative "request/oauth"