wix-hive-ruby 0.9.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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rubocop.yml +487 -0
  4. data/.yardopts +6 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +641 -0
  9. data/Rakefile +33 -0
  10. data/e2e/activities_api_spec.rb +334 -0
  11. data/e2e/contacts_api_spec.rb +364 -0
  12. data/e2e/e2e_helper.rb +35 -0
  13. data/e2e/insights_api_spec.rb +29 -0
  14. data/lib/hive/activities/contact/contact_create_activity.rb +109 -0
  15. data/lib/hive/activities/contact/contact_form_activity.rb +31 -0
  16. data/lib/hive/activities/conversion/conversion_complete_activity.rb +33 -0
  17. data/lib/hive/activities/e_commerce/purchase_activity.rb +150 -0
  18. data/lib/hive/activities/factory.rb +71 -0
  19. data/lib/hive/activities/hotels/hotels_cancel_activity.rb +52 -0
  20. data/lib/hive/activities/hotels/hotels_confirmation_activity.rb +133 -0
  21. data/lib/hive/activities/hotels/hotels_purchase_activity.rb +48 -0
  22. data/lib/hive/activities/hotels/hotels_purchase_failed_activity.rb +59 -0
  23. data/lib/hive/activities/messaging/send_activity.rb +75 -0
  24. data/lib/hive/activities/music/album_fan_activity.rb +36 -0
  25. data/lib/hive/activities/music/album_share_activity.rb +23 -0
  26. data/lib/hive/activities/music/track_lyrics_activity.rb +38 -0
  27. data/lib/hive/activities/music/track_play_activity.rb +31 -0
  28. data/lib/hive/activities/music/track_played_activity.rb +31 -0
  29. data/lib/hive/activities/music/track_share_activity.rb +32 -0
  30. data/lib/hive/activities/music/track_skipped_activity.rb +31 -0
  31. data/lib/hive/activities/scheduler/scheduler_appointment_activity.rb +73 -0
  32. data/lib/hive/activity.rb +60 -0
  33. data/lib/hive/activity_summary.rb +24 -0
  34. data/lib/hive/connect/request/wix_api_request.rb +92 -0
  35. data/lib/hive/connect/response/error.rb +88 -0
  36. data/lib/hive/connect/response/parse_json.rb +29 -0
  37. data/lib/hive/connect/response/raise_error.rb +17 -0
  38. data/lib/hive/connect/wix_client.rb +148 -0
  39. data/lib/hive/contact.rb +153 -0
  40. data/lib/hive/cursor.rb +48 -0
  41. data/lib/hive/errors.rb +5 -0
  42. data/lib/hive/extensions/hashie_hash.rb +16 -0
  43. data/lib/hive/extensions/hashie_validate_enum.rb +11 -0
  44. data/lib/hive/rest/activities.rb +55 -0
  45. data/lib/hive/rest/api.rb +13 -0
  46. data/lib/hive/rest/contacts.rb +114 -0
  47. data/lib/hive/rest/insights.rb +17 -0
  48. data/lib/hive/util.rb +20 -0
  49. data/lib/hive/version.rb +14 -0
  50. data/lib/wix-hive-ruby.rb +5 -0
  51. data/samples/quick_start.rb +49 -0
  52. data/spec/hive/activities/contact/contact_create_activity_spec.rb +25 -0
  53. data/spec/hive/activities/contact/contact_form_activity_spec.rb +9 -0
  54. data/spec/hive/activities/conversion/conversion_complete_activity_spec.rb +9 -0
  55. data/spec/hive/activities/e_commerce/purchase_activity_spec.rb +19 -0
  56. data/spec/hive/activities/factory_spec.rb +78 -0
  57. data/spec/hive/activities/hotels/hotels_cancel_activity_spec.rb +22 -0
  58. data/spec/hive/activities/hotels/hotels_confirmation_activity_spec.rb +34 -0
  59. data/spec/hive/activities/hotels/hotels_purchase_activity_spec.rb +22 -0
  60. data/spec/hive/activities/hotels/hotels_purchase_failed_activity_spec.rb +22 -0
  61. data/spec/hive/activities/messaging/send_activity_spec.rb +13 -0
  62. data/spec/hive/activities/scheduler/scheduler_appointment_activity_spec.rb +10 -0
  63. data/spec/hive/activity_spec.rb +18 -0
  64. data/spec/hive/connect/request/wix_api_request_spec.rb +54 -0
  65. data/spec/hive/connect/response/error_spec.rb +31 -0
  66. data/spec/hive/connect/response/parse_json_spec.rb +28 -0
  67. data/spec/hive/connect/response/raise_error_spec.rb +19 -0
  68. data/spec/hive/connect/wix_client_spec.rb +103 -0
  69. data/spec/hive/contact_spec.rb +148 -0
  70. data/spec/hive/cursor_spec.rb +75 -0
  71. data/spec/hive/hashie_hash_spec.rb +23 -0
  72. data/spec/hive/rest/activities_spec.rb +87 -0
  73. data/spec/hive/rest/contacts_spec.rb +225 -0
  74. data/spec/hive/rest/insights_spec.rb +17 -0
  75. data/spec/hive/util_spec.rb +36 -0
  76. data/spec/spec_helper.rb +59 -0
  77. data/wix-hive-ruby.gemspec +38 -0
  78. metadata +392 -0
@@ -0,0 +1,29 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Hive
5
+ module Response
6
+ class ParseJson < Faraday::Response::Middleware
7
+ WHITESPACE_REGEX = /\A^\s*$\z/
8
+
9
+ def on_complete(response)
10
+ response.body = parse(response.body) if respond_to?(:parse) && !unparsable_status_codes.include?(response.status)
11
+ end
12
+
13
+ def parse(body)
14
+ case body
15
+ when WHITESPACE_REGEX, nil
16
+ nil
17
+ else
18
+ JSON.parse(body, symbolize_names: true)
19
+ end
20
+ end
21
+
22
+ def unparsable_status_codes
23
+ [204, 301, 302, 304]
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ Faraday::Response.register_middleware parse_json: Hive::Response::ParseJson
@@ -0,0 +1,17 @@
1
+ require 'faraday'
2
+
3
+ module Hive
4
+ module Response
5
+ class RaiseError < Faraday::Response::Middleware
6
+ def on_complete(response)
7
+ status_code = response.status.to_i
8
+ klass = Hive::Response::Error.errors[status_code]
9
+ return unless klass
10
+ error = klass.from_response(response)
11
+ fail(error)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ Faraday::Response.register_middleware raise_error: Hive::Response::RaiseError
@@ -0,0 +1,148 @@
1
+ require 'hive/connect/request/wix_api_request'
2
+ require 'hive/rest/api'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'faraday/request/multipart'
6
+ require 'json'
7
+ require 'timeout'
8
+ require 'hive/connect/response/parse_json'
9
+ require 'hive/connect/response/raise_error'
10
+ require 'hive/connect/response/error'
11
+ require 'hive/version'
12
+ require 'logger'
13
+
14
+ module Hive
15
+ class Client
16
+ include Hive::REST::API
17
+
18
+ attr_accessor :secret_key, :app_id, :instance_id, :api_family, :api_version, :logger, :api_base
19
+ attr_writer :user_agent
20
+
21
+ def initialize(options = {})
22
+ # Defaults
23
+ @api_family = 'v1'
24
+ @api_version = '1.0.0'
25
+ @api_base = 'https://openapi.wix.com'
26
+
27
+ options.each do |key, value|
28
+ send(:"#{key}=", value)
29
+ end
30
+ yield(self) if block_given?
31
+
32
+ validate_configuration!
33
+ end
34
+
35
+ def headers=(options = {})
36
+ connection_options[:headers].merge!(options)
37
+ end
38
+
39
+ # :nocov:
40
+ def request_config=(options = {})
41
+ connection_options[:request].merge!(options)
42
+ end
43
+ # :nocov:
44
+
45
+ # @return [String]
46
+ def user_agent
47
+ @user_agent ||= "Hive Ruby Gem #{Hive::Version}"
48
+ end
49
+
50
+ def wix_request(request)
51
+ request(request.verb, request.path, request.options)
52
+ end
53
+
54
+ def request(method, path, options = {})
55
+ connection.send(method.to_sym) do |request|
56
+ request.url path, options.fetch(:params, {})
57
+ request.headers.update(options.fetch(:headers, {}))
58
+ body = options.fetch(:body, {})
59
+ request.body = body if body.length > 0
60
+ end
61
+ rescue Faraday::Error::TimeoutError, Timeout::Error => error
62
+ raise(Hive::Response::Error::RequestTimeout, error)
63
+ rescue Faraday::Error::ClientError, JSON::ParserError => error
64
+ raise(Hive::Response::Error, error)
65
+ end
66
+
67
+ class << self
68
+ def parse_instance_data(signed_instance, app_secret)
69
+ signature, encoded_json = signed_instance.split('.', 2)
70
+
71
+ fail Hive::SignatureError, 'invalid signed instance' if signature.nil? || encoded_json.nil?
72
+
73
+ encoded_json_hack = encoded_json + ('=' * (4 - encoded_json.length.modulo(4)))
74
+
75
+ json_str = Base64.urlsafe_decode64(encoded_json_hack)
76
+
77
+ hmac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, app_secret, encoded_json)
78
+
79
+ my_signature = Base64.urlsafe_encode64(hmac).gsub('=', '')
80
+
81
+ fail Hive::SignatureError, 'the signatures do not match' if signature != my_signature
82
+
83
+ Hashie::Mash.new(JSON.parse(json_str))
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def validate_configuration!
90
+ if secret_key.nil?
91
+ fail Hive::ConfigurationError, "Invalid secret key: #{secret_key}"
92
+ elsif app_id.nil?
93
+ fail Hive::ConfigurationError, "Invalid app id: #{app_id}"
94
+ elsif instance_id.nil?
95
+ fail Hive::ConfigurationError, "Invalid instance id: #{instance_id}"
96
+ end
97
+ end
98
+
99
+ def middleware
100
+ @middleware ||= Faraday::RackBuilder.new do |faraday|
101
+ # Checks for files in the payload, otherwise leaves everything untouched
102
+ faraday.request :multipart
103
+ # Encodes as "application/json" if not already encoded
104
+ faraday.request :json
105
+ # Encodes as "application/x-www-form-urlencoded" if not already encoded
106
+ faraday.request :url_encoded
107
+ # Handle error responses
108
+ faraday.response :raise_error
109
+ # Parse JSON response bodies
110
+ faraday.response :parse_json
111
+ # Log requests to the STDOUT
112
+ add_logger(faraday)
113
+ # Set default HTTP adapter
114
+ faraday.adapter Faraday.default_adapter
115
+ end
116
+ end
117
+
118
+ # :nocov:
119
+ def add_logger(faraday)
120
+ case @logger
121
+ when :file
122
+ faraday.use Faraday::Response::Logger, Logger.new('hive.log')
123
+ when :stdout
124
+ faraday.use Faraday::Response::Logger
125
+ end
126
+ end
127
+ # :nocov:
128
+
129
+ # rubocop:disable Style/MethodLength:
130
+ def connection_options
131
+ @connection_options ||= {
132
+ builder: middleware,
133
+ headers: {
134
+ accept: 'application/json',
135
+ user_agent: user_agent
136
+ },
137
+ request: {
138
+ open_timeout: 10,
139
+ timeout: 30
140
+ }
141
+ }
142
+ end
143
+
144
+ def connection
145
+ @connection ||= Faraday.new(@api_base, connection_options)
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,153 @@
1
+ require 'hashie'
2
+
3
+ module Hive
4
+ class Name < Hashie::Trash
5
+ include Hashie::Extensions::IgnoreUndeclared
6
+ property :prefix
7
+ property :first
8
+ property :middle
9
+ property :last
10
+ property :suffix
11
+ end
12
+
13
+ class Company < Hashie::Trash
14
+ include Hashie::Extensions::IgnoreUndeclared
15
+ property :role
16
+ property :name
17
+ end
18
+
19
+ class Email < Hashie::Trash
20
+ include Hashie::Extensions::IgnoreUndeclared
21
+ property :id
22
+ property :tag
23
+ property :email
24
+ property :emailStatus
25
+ property :unsubscribeLink
26
+ end
27
+
28
+ class Phone < Hashie::Trash
29
+ include Hashie::Extensions::IgnoreUndeclared
30
+ property :id
31
+ property :tag
32
+ property :phone
33
+ property :normalizedPhone
34
+ end
35
+
36
+ class Address < Hashie::Trash
37
+ include Hashie::Extensions::IgnoreUndeclared
38
+ property :id
39
+ property :tag
40
+ property :address
41
+ property :neighborhood
42
+ property :city
43
+ property :region
44
+ property :country
45
+ property :postalCode
46
+ end
47
+
48
+ class Url < Hashie::Trash
49
+ include Hashie::Extensions::IgnoreUndeclared
50
+ property :id
51
+ property :tag
52
+ property :url
53
+ end
54
+
55
+ class Date < Hashie::Trash
56
+ include Hashie::Extensions::IgnoreUndeclared
57
+ property :id
58
+ property :tag
59
+ property :date, with: lambda { |v| Time.parse(v) }
60
+ end
61
+
62
+ class Note < Hashie::Trash
63
+ include Hashie::Extensions::IgnoreUndeclared
64
+ property :id
65
+ property :modifiedAt
66
+ property :content
67
+ end
68
+
69
+ class Custom < Hashie::Trash
70
+ include Hashie::Extensions::IgnoreUndeclared
71
+ property :id
72
+ property :field
73
+ property :value
74
+ end
75
+
76
+ class Link < Hashie::Trash
77
+ include Hashie::Extensions::IgnoreUndeclared
78
+ property :href
79
+ property :rel
80
+ end
81
+
82
+ class Contact < Hashie::Trash
83
+ include Hashie::Extensions::IgnoreUndeclared
84
+ include Hashie::Extensions::Coercion
85
+
86
+ coerce_key :name, Name
87
+ coerce_key :company, Company
88
+ coerce_key :emails, Array[Email]
89
+ coerce_key :phones, Array[Phone]
90
+ coerce_key :addresses, Array[Address]
91
+ coerce_key :urls, Array[Url]
92
+ coerce_key :dates, Array[Date]
93
+ coerce_key :notes, Array[Note]
94
+ coerce_key :custom, Array[Custom]
95
+ coerce_key :links, Array[Link]
96
+ property :id
97
+ property :name, default: Hive::Name.new
98
+ property :picture
99
+ property :company, default: Hive::Company.new
100
+ property :emails, default: []
101
+ property :phones, default: []
102
+ property :addresses, default: []
103
+ property :urls, default: []
104
+ property :dates, default: []
105
+ property :notes, default: []
106
+ property :custom, default: []
107
+ property :createdAt
108
+ property :links
109
+ property :modifiedAt
110
+
111
+ remove_method :emails=, :phones=, :addresses=, :urls=, :dates=, :notes=, :custom=, :links=
112
+
113
+ def add_email(args)
114
+ emails << Email.new(args)
115
+ end
116
+
117
+ def add_phone(args)
118
+ phones << Phone.new(args)
119
+ end
120
+
121
+ def add_address(args)
122
+ addresses << Address.new(args)
123
+ end
124
+
125
+ def add_url(args)
126
+ urls << Url.new(args)
127
+ end
128
+
129
+ def add_date(args)
130
+ dates << Date.new(args)
131
+ end
132
+
133
+ def add_note(args)
134
+ notes << Note.new(args)
135
+ end
136
+
137
+ def add_custom(args)
138
+ custom << Custom.new(args)
139
+ end
140
+ end
141
+
142
+ class ContactSubscriber < Hashie::Trash
143
+ include Hashie::Extensions::IgnoreUndeclared
144
+ include Hashie::Extensions::Coercion
145
+
146
+ coerce_key :name, Name
147
+ coerce_key :emails, Array[Email]
148
+
149
+ property :id
150
+ property :name, default: Hive::Name.new
151
+ property :emails, default: []
152
+ end
153
+ end
@@ -0,0 +1,48 @@
1
+ require 'hashie'
2
+ require 'hive/contact'
3
+ require 'hive/activity'
4
+ require 'hive/errors'
5
+
6
+ module Hive
7
+ class Cursor < Hashie::Trash
8
+ include Hashie::Extensions::IgnoreUndeclared
9
+
10
+ def initialize(hash, klass, current_request)
11
+ @next_request = current_request.clone
12
+ @klass = klass
13
+ super(hash)
14
+ self.results = results.map { |item| klass.new(item) }
15
+ end
16
+
17
+ property :total
18
+ property :pageSize
19
+ property :previousCursor
20
+ property :nextCursor
21
+ property :results, default: []
22
+
23
+ def next?
24
+ !nextCursor.nil? && nextCursor != '0'
25
+ end
26
+
27
+ def previous?
28
+ !previousCursor.nil? && previousCursor != '0'
29
+ end
30
+
31
+ def next_page
32
+ fail Hive::CursorOperationError, 'Next page not available!' if nextCursor.nil?
33
+ cursored_request(nextCursor)
34
+ end
35
+
36
+ def previous_page
37
+ fail Hive::CursorOperationError, 'Previous page not available!' if previousCursor.nil?
38
+ cursored_request(previousCursor)
39
+ end
40
+
41
+ private
42
+
43
+ def cursored_request(cursor)
44
+ @next_request.params.merge!(cursor: cursor)
45
+ @next_request.perform_with_cursor(@klass)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module Hive
2
+ class CursorOperationError < StandardError; end
3
+ class ConfigurationError < StandardError; end
4
+ class SignatureError < StandardError; end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Hashie
2
+ class Hash < ::Hash
3
+ def flexibly_convert_to_hash(object, options = {})
4
+ if object.method(:to_hash).arity == 0
5
+ object.to_hash
6
+ else
7
+ object.to_hash(options)
8
+ end
9
+ rescue ArgumentError
10
+ # HACK: If the provided object overrides the method() definition (see Hive::Activities::Messaging::Recipient)
11
+ # we don't want the whole conversion to fail. So we assume execute the more likely to be present to_hash method.
12
+ # I have opened an issue here: https://github.com/intridea/hashie/issues/222
13
+ object.to_hash
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Hashie
2
+ module Validate
3
+ module_function
4
+ def enum(enum)
5
+ lambda do |v|
6
+ fail ArgumentError, "Invalid value #{v} ! Valid ones are: #{enum}" unless enum.include?(v)
7
+ v
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ require 'hive/activity'
2
+
3
+ module Hive
4
+ module REST
5
+ module Activities
6
+ include Hive::Util
7
+
8
+ def add_contact_activity(id, activity)
9
+ read_only?(activity.activityType)
10
+
11
+ perform_with_object(:post,
12
+ "v1/contacts/#{id}/activities",
13
+ Hive::ActivityResult,
14
+ body: activity.to_json)
15
+ end
16
+
17
+ def new_activity(session_token, activity)
18
+ read_only?(activity.activityType)
19
+
20
+ perform_with_object(:post,
21
+ 'v1/activities',
22
+ Hive::ActivityResult,
23
+ body: activity.to_json,
24
+ params: { userSessionToken: session_token })
25
+ end
26
+
27
+ def activity(id)
28
+ perform_with_object(:get, "v1/activities/#{id}", Hive::Activity)
29
+ end
30
+
31
+ def contact_activities(id, query_options = {})
32
+ activities(query_options, "v1/contacts/#{id}/activities")
33
+ end
34
+
35
+ def activities(query_options = {}, path = 'v1/activities')
36
+ transform_activities_query(query_options)
37
+ perform_with_cursor(:get, path, Hive::Activity, params: query_options)
38
+ end
39
+
40
+ private
41
+
42
+ READ_ONLY_ACTIVITIES = [Hive::Activities::CONTACTS_CREATE.type]
43
+
44
+ def read_only?(activity_type)
45
+ fail ArgumentError,
46
+ "Activity is read only! Please provide an activity other than: #{READ_ONLY_ACTIVITIES}" if READ_ONLY_ACTIVITIES.find { |type| type == activity_type }
47
+ end
48
+
49
+ def transform_activities_query(query)
50
+ activity_types = query[:activityTypes]
51
+ query[:activityTypes] = activity_types.join(',') if activity_types && activity_types.respond_to?(:join)
52
+ end
53
+ end
54
+ end
55
+ end