rdstation-ruby-client 1.2.1 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/rdsm-ruby-client-issue-template.md +49 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +167 -0
  5. data/README.md +243 -43
  6. data/lib/rdstation-ruby-client.rb +6 -0
  7. data/lib/rdstation.rb +19 -0
  8. data/lib/rdstation/api_response.rb +3 -3
  9. data/lib/rdstation/authentication.rb +32 -3
  10. data/lib/rdstation/authorization.rb +24 -0
  11. data/lib/rdstation/builder/field.rb +70 -0
  12. data/lib/rdstation/client.rb +17 -74
  13. data/lib/rdstation/contacts.rb +21 -16
  14. data/lib/rdstation/error.rb +23 -15
  15. data/lib/rdstation/error/format.rb +21 -3
  16. data/lib/rdstation/error/formatter.rb +53 -7
  17. data/lib/rdstation/error_handler.rb +31 -26
  18. data/lib/rdstation/error_handler/bad_request.rb +30 -0
  19. data/lib/rdstation/error_handler/unauthorized.rb +17 -9
  20. data/lib/rdstation/events.rb +7 -19
  21. data/lib/rdstation/fields.rb +31 -7
  22. data/lib/rdstation/retryable_request.rb +35 -0
  23. data/lib/rdstation/version.rb +1 -1
  24. data/lib/rdstation/webhooks.rb +25 -17
  25. data/rdstation-ruby-client.gemspec +4 -1
  26. data/spec/lib/rdstation-ruby-client_spec.rb +1 -1
  27. data/spec/lib/rdstation/api_response_spec.rb +34 -0
  28. data/spec/lib/rdstation/authentication_spec.rb +164 -0
  29. data/spec/lib/rdstation/authorization_spec.rb +24 -0
  30. data/spec/lib/rdstation/builder/field_spec.rb +69 -0
  31. data/spec/lib/rdstation/client_spec.rb +37 -0
  32. data/spec/lib/rdstation/contacts_spec.rb +54 -41
  33. data/spec/lib/rdstation/error/format_spec.rb +46 -0
  34. data/spec/lib/rdstation/error/formatter_spec.rb +83 -0
  35. data/spec/lib/rdstation/error_handler/unauthorized_spec.rb +0 -29
  36. data/spec/lib/rdstation/error_handler_spec.rb +162 -26
  37. data/spec/lib/rdstation/events_spec.rb +20 -9
  38. data/spec/lib/rdstation/fields_spec.rb +10 -3
  39. data/spec/lib/rdstation/retryable_request_spec.rb +142 -0
  40. data/spec/lib/rdstation/webhooks_spec.rb +41 -13
  41. data/spec/lib/rdstation_spec.rb +18 -0
  42. metadata +41 -12
  43. data/lib/rdstation/error_handler/default.rb +0 -15
  44. data/lib/rdstation/error_handler/resource_not_found.rb +0 -24
  45. data/spec/lib/rdstation/error_handler/default_spec.rb +0 -14
  46. data/spec/lib/rdstation/error_handler/resource_not_found_spec.rb +0 -54
@@ -4,7 +4,10 @@ require 'httparty'
4
4
  require 'rdstation/api_response'
5
5
 
6
6
  # API requests
7
+ require 'rdstation'
8
+ require 'rdstation/retryable_request'
7
9
  require 'rdstation/authentication'
10
+ require 'rdstation/authorization'
8
11
  require 'rdstation/client'
9
12
  require 'rdstation/contacts'
10
13
  require 'rdstation/events'
@@ -14,3 +17,6 @@ require 'rdstation/webhooks'
14
17
  # Error handling
15
18
  require 'rdstation/error'
16
19
  require 'rdstation/error_handler'
20
+
21
+ # Builders
22
+ require 'rdstation/builder/field'
@@ -0,0 +1,19 @@
1
+ module RDStation
2
+ class << self
3
+ attr_accessor :configuration
4
+
5
+ def configure
6
+ self.configuration ||= Configuration.new
7
+ yield(configuration)
8
+ end
9
+ end
10
+
11
+ class Configuration
12
+ attr_accessor :client_id, :client_secret
13
+ attr_reader :access_token_refresh_callback
14
+
15
+ def on_access_token_refresh(&block)
16
+ @access_token_refresh_callback = block
17
+ end
18
+ end
19
+ end
@@ -1,9 +1,9 @@
1
1
  module RDStation
2
2
  module ApiResponse
3
3
  def self.build(response)
4
- response_body = JSON.parse(response.body)
5
- return response_body unless response_body['errors']
6
- RDStation::ErrorHandler.new(response).raise_errors
4
+ return JSON.parse(response.body) if response.code.between?(200, 299)
5
+
6
+ RDStation::ErrorHandler.new(response).raise_error
7
7
  end
8
8
  end
9
9
  end
@@ -5,10 +5,12 @@ module RDStation
5
5
 
6
6
  AUTH_TOKEN_URL = 'https://api.rd.services/auth/token'.freeze
7
7
  DEFAULT_HEADERS = { 'Content-Type' => 'application/json' }.freeze
8
+ REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze
8
9
 
9
- def initialize(client_id, client_secret)
10
- @client_id = client_id
11
- @client_secret = client_secret
10
+ def initialize(client_id = nil, client_secret = nil)
11
+ warn_deprecation if client_id || client_secret
12
+ @client_id = client_id || RDStation.configuration&.client_id
13
+ @client_secret = client_secret || RDStation.configuration&.client_secret
12
14
  end
13
15
 
14
16
  #
@@ -47,8 +49,31 @@ module RDStation
47
49
  ApiResponse.build(response)
48
50
  end
49
51
 
52
+ def self.revoke(access_token:)
53
+ response = self.post(
54
+ REVOKE_URL,
55
+ body: revoke_body(access_token),
56
+ headers: revoke_headers(access_token)
57
+ )
58
+ ApiResponse.build(response)
59
+ end
60
+
50
61
  private
51
62
 
63
+ def self.revoke_body(access_token)
64
+ URI.encode_www_form({
65
+ token: access_token,
66
+ token_type_hint: 'access_token'
67
+ })
68
+ end
69
+
70
+ def self.revoke_headers(access_token)
71
+ {
72
+ "Authorization" => "Bearer #{access_token}",
73
+ "Content-Type" => "application/x-www-form-urlencoded"
74
+ }
75
+ end
76
+
52
77
  def post_to_auth_endpoint(params)
53
78
  default_body = { client_id: @client_id, client_secret: @client_secret }
54
79
  body = default_body.merge(params)
@@ -59,5 +84,9 @@ module RDStation
59
84
  headers: DEFAULT_HEADERS
60
85
  )
61
86
  end
87
+
88
+ def warn_deprecation
89
+ warn "DEPRECATION WARNING: Providing client_id and client_secret directly to RDStation::Authentication.new is deprecated and will be removed in future versions. Use RDStation.configure instead."
90
+ end
62
91
  end
63
92
  end
@@ -0,0 +1,24 @@
1
+ module RDStation
2
+ class Authorization
3
+ attr_reader :refresh_token
4
+ attr_accessor :access_token, :access_token_expires_in
5
+ def initialize(access_token:, refresh_token: nil, access_token_expires_in: nil)
6
+ @access_token = access_token
7
+ @refresh_token = refresh_token
8
+ @access_token_expires_in = access_token_expires_in
9
+ validate_access_token access_token
10
+ end
11
+
12
+ def headers
13
+ { "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" }
14
+ end
15
+
16
+ private
17
+
18
+ def validate_access_token(access_token)
19
+ access_token_msg = ':access_token is required'
20
+ raise ArgumentError, access_token_msg unless access_token
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDStation
4
+ class Builder
5
+ # More info: https://developers.rdstation.com/pt-BR/reference/fields#methodPostDetails
6
+ class Field
7
+ DATA_TYPES = %w(STRING INTEGER BOOLEAN STRING[]).freeze
8
+ PRESENTATION_TYPES = %w[TEXT_INPUT TEXT_AREA URL_INPUT PHONE_INPUT
9
+ EMAIL_INPUT CHECK_BOX NUMBER_INPUT COMBO_BOX
10
+ RADIO_BUTTON MULTIPLE_CHOICE].freeze
11
+
12
+ REQUIRED_FIELDS = %w[api_identifier data_type presentation_type label name].freeze
13
+
14
+ def initialize(api_identifier)
15
+ raise 'api_identifier required' unless api_identifier
16
+ unless valid_identifier(api_identifier)
17
+ raise 'api_identifier is not in a valid format, need start with "cf_"'
18
+ end
19
+
20
+ @values = {}
21
+ @values['api_identifier'] = api_identifier
22
+ end
23
+
24
+ def data_type(data_type)
25
+ raise "Not valid data_type - #{DATA_TYPES}" unless DATA_TYPES.include? data_type
26
+
27
+ @values['data_type'] = data_type
28
+ end
29
+
30
+ def presentation_type(presentation_type)
31
+ unless PRESENTATION_TYPES.include? presentation_type
32
+ raise "Not valid presentation_type - #{PRESENTATION_TYPES}"
33
+ end
34
+
35
+ @values['presentation_type'] = presentation_type
36
+ end
37
+
38
+ def label(language, label)
39
+ @values['label'] = { language.to_s => label }
40
+ end
41
+
42
+ def name(language, name)
43
+ @values['name'] = { language.to_s => name }
44
+ end
45
+
46
+ def validation_rules(validation_rules)
47
+ @values['validation_rules'] = validation_rules
48
+ end
49
+
50
+ def valid_options(valid_options)
51
+ @values['valid_options'] = valid_options
52
+ end
53
+
54
+ def build
55
+ empty_fields = REQUIRED_FIELDS.select { |field| @values[field].nil? }
56
+ unless empty_fields.empty?
57
+ raise "Required fields are missing - #{empty_fields}"
58
+ end
59
+
60
+ @values
61
+ end
62
+
63
+ private
64
+
65
+ def valid_identifier(api_identifier)
66
+ api_identifier.start_with? 'cf_'
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,90 +1,33 @@
1
- # encoding: utf-8
2
1
  module RDStation
3
- #
4
- # Mais informações em http://ajuda.rdstation.com.br/hc/pt-br/articles/204526429-Guia-de-integra%C3%A7%C3%B5es-com-o-RD-Station
5
- #
6
2
  class Client
7
- include HTTParty
8
-
9
- def initialize(rdstation_token, auth_token, identifier="integração")
10
- warn "DEPRECATION WARNING: all methods in this class has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
11
- @identificador = identifier
12
- @rdstation_token = rdstation_token
13
- @auth_token = auth_token
14
- end
15
-
16
- #
17
- # A hash do Lead pode conter os seguintes parâmetros:
18
- # (obrigatório) :email
19
- # :identificador
20
- # :nome
21
- # :empresa
22
- # :cargo
23
- # :telefone
24
- # :celular
25
- # :website
26
- # :twitter
27
- # :c_utmz
28
- # :created_at
29
- # :tags
30
- #
31
- # Caso algum parâmetro não seja identificado como campo padrão ou como
32
- # campo personalizado, este parâmetro desconhecido será gravado nos
33
- # "Detalhes do Lead".
34
- #
35
- def create_lead(lead_hash)
36
- warn "DEPRECATION WARNING: create_lead has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
37
- lead_hash = rdstation_token_hash.merge(lead_hash)
38
- lead_hash = lead_hash.merge(identifier_hash) unless lead_hash.has_key?(:identificador)
39
- post_with_body("/conversions", {:body => lead_hash})
40
- end
41
- alias_method :update_lead_info, :create_lead
42
-
43
- #
44
- # param lead:
45
- # id ou email do Lead a ser alterado
46
- #
47
- # param lead_hash:
48
- # Hash contendo:
49
- # :lifecycle_stage
50
- # 0 - Lead; 1 - Lead Qualificado; 2 - Cliente
51
- # :opportunity
52
- # true ou false
53
- #
54
- def change_lead(lead, lead_hash)
55
- warn "DEPRECATION WARNING: change_lead has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
56
- lead_hash = auth_token_hash.merge({:lead => lead_hash})
57
- put_with_body("/leads/#{lead}", :body => lead_hash.to_json, :headers => {'Content-Type' => 'application/json'})
58
- end
59
-
60
- def change_lead_status(lead_hash)
61
- warn "DEPRECATION WARNING: change_lead_status has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
62
- post_with_body("/services/#{@auth_token}/generic", :body => lead_hash )
3
+ def initialize(access_token:, refresh_token: nil)
4
+ warn_deprecation unless refresh_token
5
+ @authorization = Authorization.new(
6
+ access_token: access_token,
7
+ refresh_token: refresh_token
8
+ )
63
9
  end
64
10
 
65
- private
66
- def base_url
67
- "https://www.rdstation.com.br/api/1.2"
11
+ def contacts
12
+ @contacts ||= RDStation::Contacts.new(authorization: @authorization)
68
13
  end
69
14
 
70
- def rdstation_token_hash
71
- { :token_rdstation => @rdstation_token }
15
+ def events
16
+ @events ||= RDStation::Events.new(authorization: @authorization)
72
17
  end
73
18
 
74
- def auth_token_hash
75
- { :auth_token => @auth_token }
19
+ def fields
20
+ @fields ||= RDStation::Fields.new(authorization: @authorization)
76
21
  end
77
22
 
78
- def identifier_hash
79
- { :identificador => @identificador }
23
+ def webhooks
24
+ @webhooks ||= RDStation::Webhooks.new(authorization: @authorization)
80
25
  end
81
26
 
82
- def post_with_body(path, opts)
83
- self.class.post("#{base_url}#{path}", opts)
84
- end
27
+ private
85
28
 
86
- def put_with_body(path, opts)
87
- self.class.put("#{base_url}#{path}", opts)
29
+ def warn_deprecation
30
+ warn "DEPRECATION WARNING: Specifying refresh_token in RDStation::Client.new(access_token: 'at', refresh_token: 'rt') is optional right now, but will be mandatory in future versions. "
88
31
  end
89
32
  end
90
33
  end
@@ -3,9 +3,10 @@ module RDStation
3
3
  # More info: https://developers.rdstation.com/pt-BR/reference/contacts
4
4
  class Contacts
5
5
  include HTTParty
6
+ include ::RDStation::RetryableRequest
6
7
 
7
- def initialize(auth_token)
8
- @auth_token = auth_token
8
+ def initialize(authorization:)
9
+ @authorization = authorization
9
10
  end
10
11
 
11
12
  #
@@ -13,13 +14,17 @@ module RDStation
13
14
  # The unique uuid associated to each RD Station Contact.
14
15
  #
15
16
  def by_uuid(uuid)
16
- response = self.class.get(base_url(uuid), headers: required_headers)
17
- ApiResponse.build(response)
17
+ retryable_request(@authorization) do |authorization|
18
+ response = self.class.get(base_url(uuid), headers: authorization.headers)
19
+ ApiResponse.build(response)
20
+ end
18
21
  end
19
22
 
20
23
  def by_email(email)
21
- response = self.class.get(base_url("email:#{email}"), headers: required_headers)
22
- ApiResponse.build(response)
24
+ retryable_request(@authorization) do |authorization|
25
+ response = self.class.get(base_url("email:#{email}"), headers: authorization.headers)
26
+ ApiResponse.build(response)
27
+ end
23
28
  end
24
29
 
25
30
  # The Contact hash may contain the following parameters:
@@ -34,8 +39,10 @@ module RDStation
34
39
  # :website
35
40
  # :tags
36
41
  def update(uuid, contact_hash)
37
- response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => required_headers)
38
- ApiResponse.build(response)
42
+ retryable_request(@authorization) do |authorization|
43
+ response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => authorization.headers)
44
+ ApiResponse.build(response)
45
+ end
39
46
  end
40
47
 
41
48
  #
@@ -47,19 +54,17 @@ module RDStation
47
54
  # Contact data
48
55
  #
49
56
  def upsert(identifier, identifier_value, contact_hash)
50
- path = "#{identifier}:#{identifier_value}"
51
- response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: required_headers)
52
- ApiResponse.build(response)
57
+ retryable_request(@authorization) do |authorization|
58
+ path = "#{identifier}:#{identifier_value}"
59
+ response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: authorization.headers)
60
+ ApiResponse.build(response)
61
+ end
53
62
  end
54
63
 
55
64
  private
56
65
 
57
- def base_url(path = "")
66
+ def base_url(path = '')
58
67
  "https://api.rd.services/platform/contacts/#{path}"
59
68
  end
60
-
61
- def required_headers
62
- { "Authorization" => "Bearer #{@auth_token}", "Content-Type" => "application/json" }
63
- end
64
69
  end
65
70
  end
@@ -1,5 +1,4 @@
1
1
  module RDStation
2
-
3
2
  class Error < StandardError
4
3
  attr_reader :details, :http_status, :headers, :body
5
4
 
@@ -8,23 +7,32 @@ module RDStation
8
7
  message = details['error_message']
9
8
  raise ArgumentError, 'The details hash must contain an error message' unless message
10
9
 
11
- # Those three arguments are kept only for compatibility reasons.
12
- # They aren't needed since we can get them directly in the details hash.
13
- # Consider removing them when update the major version.
14
- @http_status = details['http_status']
15
- @headers = details['headers']
16
- @body = details['body']
17
-
18
10
  super(message)
19
11
  end
20
12
 
21
- class ConflictingField < Error; end
22
- class Default < Error; end
23
- class ExpiredAccessToken < Error; end
24
- class ExpiredCodeGrant < Error; end
25
- class InvalidCredentials < Error; end
26
- class InvalidEventType < Error; end
27
- class ResourceNotFound < Error; end
13
+ class BadRequest < Error; end
28
14
  class Unauthorized < Error; end
15
+ class Forbidden < Error; end
16
+ class NotFound < Error; end
17
+ class MethodNotAllowed < Error; end
18
+ class NotAcceptable < Error; end
19
+ class Conflict < Error; end
20
+ class UnsupportedMediaType < Error; end
21
+ class UnprocessableEntity < Error; end
22
+ class InternalServerError < Error; end
23
+ class NotImplemented < Error; end
24
+ class BadGateway < Error; end
25
+ class ServiceUnavailable < Error; end
26
+ class ServerError < Error; end
27
+ class UnknownError < Error; end
28
+
29
+ # 400 - Bad Request
30
+ class ConflictingField < BadRequest; end
31
+ class InvalidEventType < BadRequest; end
32
+
33
+ # 401 - Unauthorized
34
+ class ExpiredAccessToken < Unauthorized; end
35
+ class ExpiredCodeGrant < Unauthorized; end
36
+ class InvalidCredentials < Unauthorized; end
29
37
  end
30
38
  end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RDStation
2
4
  class Error
3
5
  class Format
4
- FLAT_HASH = 'FLAT_HASH'.freeze
5
- HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'.freeze
6
- ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'.freeze
6
+ FLAT_HASH = 'FLAT_HASH'
7
+ HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'
8
+ ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'
9
+ HASH_OF_MULTIPLE_TYPES = 'HASH_OF_MULTIPLE_TYPES'
10
+ HASH_OF_HASHES = 'HASH_OF_HASHES'
7
11
 
8
12
  def initialize(errors)
9
13
  @errors = errors
@@ -12,6 +16,9 @@ module RDStation
12
16
  def format
13
17
  return FLAT_HASH if flat_hash?
14
18
  return HASH_OF_ARRAYS if hash_of_arrays?
19
+ return HASH_OF_HASHES if hash_of_hashes?
20
+ return HASH_OF_MULTIPLE_TYPES if hash_of_multiple_types?
21
+
15
22
  ARRAY_OF_HASHES
16
23
  end
17
24
 
@@ -19,12 +26,23 @@ module RDStation
19
26
 
20
27
  def flat_hash?
21
28
  return unless @errors.is_a?(Hash)
29
+
22
30
  @errors.key?('error_type')
23
31
  end
24
32
 
25
33
  def hash_of_arrays?
26
34
  @errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Array }
27
35
  end
36
+
37
+ def hash_of_hashes?
38
+ @errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Hash }
39
+ end
40
+
41
+ def hash_of_multiple_types?
42
+ @errors.is_a?(Hash) &&
43
+ @errors.values.any? { |error| error.is_a? Hash } &&
44
+ @errors.values.any? { |error| error.is_a? Array }
45
+ end
28
46
  end
29
47
  end
30
48
  end