exact-target-client 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 614ec559735b61b1b116fdef1adf03bea2fb0bd0
4
+ data.tar.gz: 548a43df0c79f8fe7c1785f86447b8a897370315
5
+ SHA512:
6
+ metadata.gz: 3fba540477dc8e5b5ec01017c6919b5a65d2300ce465fa8600b11811d8b32cc4c534b59187671d32c904195aa040ea9dd9b7f6874bbd35b650bbadfb74bddca8
7
+ data.tar.gz: 9562533fbd5e005c7788b1e90d9c9ca0602b496c8c68ae31eda6341229f1826dfc096abdff03582850dcf03e1c09a874b49f96be3902acda9fe281667a984d0c
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Yotpo Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Exact Target Ruby API
2
+ [![Build Status](https://travis-ci.org/YotpoLtd/exact-target-ruby-api.svg?branch=master)](https://travis-ci.org/YotpoLtd/exact-target-ruby-api)
3
+
4
+ ## Usage
5
+
6
+
7
+ ## License
8
+
9
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
10
+
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ RSpec::Core::RakeTask.new(:spec)
2
+
3
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require "pry"
10
+ # Pry.start
11
+
12
+ require "irb"
13
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'exact_target_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'exact-target-client'
8
+ spec.version = ExactTargetClient::VERSION
9
+ spec.authors = ['Ariel Cabib', 'Yaron Dinur']
10
+ spec.email = ['acabib@yotpo.com', 'ydinur@yotpo.com']
11
+
12
+ spec.summary = 'Ruby client for interacting with ExactTarget (Salesforce Marketing Cloud) APIs'
13
+ spec.description = 'Ruby client for interacting with ExactTarget (Salesforce Marketing Cloud) APIs'
14
+ spec.homepage = 'https://github.com/YotpoLtd/exact-target-ruby-api'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.12'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
25
+ spec.add_runtime_dependency 'json'
26
+ spec.add_runtime_dependency 'savon', '2.3.3'
27
+ spec.add_runtime_dependency 'typhoeus'
28
+ spec.add_runtime_dependency 'oj'
29
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module ExactTargetClient
3
+ class Conf
4
+
5
+ DEFAULT_TOKEN_ENDPOINT = 'https://auth.exacttargetapis.com/v1/requestToken'
6
+ DEFAULT_API_ENDPOINT = 'https://www.exacttargetapis.com'
7
+
8
+ class << self
9
+
10
+ attr_accessor :wsdl, :token_endpoint, :api_endpoint, :client_id, :client_secret
11
+
12
+ def configure
13
+ @token_endpoint = DEFAULT_TOKEN_ENDPOINT
14
+ @api_endpoint = DEFAULT_API_ENDPOINT
15
+ yield self
16
+ true
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,109 @@
1
+ require 'exact_target_client/exact_target_rest_client'
2
+ require 'exact_target_client/exact_target_soap_client'
3
+
4
+ module ExactTargetClient
5
+ class ExactTargetAPI
6
+
7
+ attr_accessor :oauth_token, :refresh_token
8
+ class TimeOut < Exception;
9
+ end
10
+ class TokenExpired < Exception;
11
+ end
12
+ class ClientException < Exception;
13
+ end
14
+
15
+ def initialize
16
+ yield self if block_given?
17
+ raise ArgumentError, 'block not given' unless block_given?
18
+ init_clients
19
+ end
20
+
21
+ def refresh_oauth_token
22
+ results = @rest_client.get_oauth_token(Conf.client_id, Conf.client_secret, refresh_token)
23
+ if results
24
+ @oauth_token = results['accessToken']
25
+ @refresh_token = results['refreshToken']
26
+ refresh_clients(results['accessToken'])
27
+ results
28
+ end
29
+ end
30
+
31
+ def get_emails(ids = nil)
32
+ properties = %w(ID Name HTMLBody)
33
+ if ids.present?
34
+ filter = {property: 'ID', value: ids}
35
+ end
36
+ response = soap_client.retrieve('Email', properties, filter)
37
+ check_response(response)
38
+ end
39
+
40
+ def create_email(email_name, subject, html_template)
41
+ response = soap_client.create('Email',
42
+ {'Name' => email_name,
43
+ 'Subject' => subject,
44
+ 'HTMLBody' => html_template}
45
+ )
46
+ check_response(response)
47
+ end
48
+
49
+ def update_email(email_id, name, html_template, subject = nil)
50
+ properties = {'ID' => email_id, 'Name' => name, 'HTMLBody' => html_template}
51
+ if subject.present?
52
+ properties['Subject'] = subject
53
+ end
54
+ response = soap_client.update('Email', properties)
55
+ check_response(response)
56
+ end
57
+
58
+ def delete_email(email_id)
59
+ response = soap_client.delete('Email', {'ID' => email_id})
60
+ check_response(response)
61
+ end
62
+
63
+ private
64
+ attr_accessor :soap_client, :rest_client
65
+
66
+ def init_clients
67
+ @soap_client = ExactTargetClient::ExactTargetSoapClient.new do |c|
68
+ c.oauth_token = oauth_token
69
+ c.wsdl = Conf.wsdl % {:instance => oauth_token[0]} # WSDL instance is determined by first char of token
70
+ end
71
+ @rest_client = ExactTargetClient::ExactTargetRestClient.new do |c|
72
+ c.oauth_token = oauth_token
73
+ end
74
+ end
75
+
76
+ def refresh_clients(token)
77
+ @soap_client.set_oauth_token(token)
78
+ @rest_client.set_oauth_token(token)
79
+ end
80
+
81
+ def check_response(response)
82
+ if response.success?
83
+ response.results
84
+ else
85
+ raise ClientException.new(response.message)
86
+ end
87
+ end
88
+
89
+ def create_data_extension(properties)
90
+ response = soap_client.create('DataExtension', properties)
91
+ check_response(response)
92
+ end
93
+
94
+ def upsert_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, object_hash)
95
+ rest_client.upsert_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, object_hash)
96
+ end
97
+
98
+ def increment_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, column, step = 1)
99
+ rest_client.increment_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, column, step)
100
+ end
101
+
102
+ def get_subscribers_by_email(email, properties)
103
+ response = soap_client.retrieve('Subscriber', properties, {property: 'EmailAddress', value: email})
104
+ check_response(response)
105
+ end
106
+ end
107
+
108
+ end
109
+
@@ -0,0 +1,65 @@
1
+ module ExactTargetClient
2
+ class ExactTargetRestClient
3
+ attr_accessor :oauth_token
4
+
5
+ def initialize
6
+ yield self if block_given?
7
+ end
8
+
9
+ def get_oauth_token(client_id, client_secret, refresh_token = nil)
10
+ request_url = EXACT_TARGET_CONF['ENDPOINTS']['REQUEST_TOKEN']
11
+ params = {
12
+ clientId: client_id,
13
+ clientSecret: client_secret,
14
+ accessType: 'offline'
15
+ }
16
+ if refresh_token.present?
17
+ params[:refreshToken] = refresh_token
18
+ end
19
+ request(:post, request_url, params, false)
20
+ end
21
+
22
+ def set_oauth_token(token)
23
+ @oauth_token = token
24
+ end
25
+
26
+ def upsert_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, object_hash)
27
+ request_url = "#{EXACT_TARGET_CONF['ENDPOINTS']['API']}/hub/v1/dataevents/key:#{data_extension_customer_key}/rows/#{primary_key_name}:#{primary_key_value}"
28
+ request('PUT', request_url, {values: object_hash})
29
+ end
30
+
31
+ def increment_data_extension_row(data_extension_customer_key, primary_key_name, primary_key_value, column, step = 1)
32
+ request_url = "#{EXACT_TARGET_CONF['ENDPOINTS']['API']}/hub/v1/dataevents/key:#{data_extension_customer_key}/rows/#{primary_key_name}:#{primary_key_value}/column/#{column}/increment?step=#{step}"
33
+ request('PUT', request_url)
34
+ end
35
+
36
+
37
+ private
38
+ def request(type, url, params = nil, add_token = true)
39
+ body = params.to_json if params
40
+ headers = { 'Content-Type' => 'application/json' }
41
+ headers['Authorization'] = "Bearer #{@oauth_token}" if add_token
42
+ request = Typhoeus::Request.new(
43
+ url,
44
+ method: type,
45
+ body: body,
46
+ headers: headers
47
+ )
48
+ request.on_complete do |response|
49
+ if response.success?
50
+ return Oj.load(response.body)
51
+ elsif response.timed_out?
52
+ raise ExactTargetClient::ExactTargetAPI::TimeOut
53
+ else
54
+ response = JSON.parse(response.response_body)
55
+ if (response['message'] == 'Unauthorized' || response['message'] == 'Not Authorized') && url == EXACT_TARGET_CONF['ENDPOINTS']['REQUEST_TOKEN']
56
+ raise ExactTargetClient::ExactTargetAPI::TokenExpired
57
+ else
58
+ raise ExactTargetClient::ExactTargetAPI::ClientException.new("REST API Error #{response['errorcode'].to_s}: #{response['message']}")
59
+ end
60
+ end
61
+ end
62
+ request.run
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,148 @@
1
+ module ExactTargetClient
2
+ class SoapResponse
3
+ attr_reader :code, :message, :results, :request_id, :body, :result_key
4
+
5
+ def initialize(response, result_key, client)
6
+ @results = []
7
+ @result_key = result_key
8
+ @client = client
9
+ parse_response response
10
+ rescue => e
11
+ raise ExactTargetClient::ExactTargetAPI::ClientException.new("SOAP Client Error: #{e.message}")
12
+ end
13
+
14
+ def success?
15
+ @success ||= false
16
+ end
17
+
18
+ private
19
+
20
+ def get_more
21
+ parse_results @client.soap_client.call(:retrieve, :message => {'ContinueRequest' => request_id})
22
+ end
23
+
24
+ def parse_response(response)
25
+ @code = response.http.code
26
+ parse_body response
27
+ end
28
+
29
+ def parse_body(response)
30
+ @request_id = response.body[result_key][:request_id]
31
+ @success = response.body[result_key][:overall_status] == 'OK'
32
+ @results = result_key == :retrieve_response_msg ? parse_results(response) : response.body[result_key][:results]
33
+ @message = @results.present? ? response.body[result_key][:results][:status_message] : response.body[result_key][:overall_status]
34
+ rescue
35
+ @message = response.http.body
36
+ @body = response.http.body unless @body
37
+ end
38
+
39
+ def parse_results(response)
40
+ res = response.body[result_key][:results] || []
41
+ if response.body[result_key][:overall_status] == 'MoreDataAvailable'
42
+ res += get_more
43
+ end
44
+ Array.wrap(res)
45
+ rescue
46
+ []
47
+ end
48
+ end
49
+
50
+ class ExactTargetSoapClient
51
+ attr_accessor :wsdl, :oauth_token
52
+
53
+ RESPONSE_RESULT_KEYS = {
54
+ create: :create_response,
55
+ update: :update_response,
56
+ delete: :delete_response,
57
+ retrieve: :retrieve_response_msg
58
+ }
59
+
60
+ def initialize
61
+ yield self if block_given?
62
+ end
63
+
64
+ def header
65
+ raise ExactTargetClient::ExactTargetAPI::ClientException.new('OAuth token must be provided to SOAP client!') unless oauth_token
66
+ {
67
+ 'fueloauth' => oauth_token,
68
+ '@xmlns' => 'http://exacttarget.com'
69
+ }
70
+ end
71
+
72
+ def wsdl
73
+ @wsdl ||= 'https://webservice.exacttarget.com/etframework.wsdl'
74
+ end
75
+
76
+ def soap_client
77
+ @soap_client = Savon.client(
78
+ soap_header: header,
79
+ wsdl: wsdl,
80
+ log: false,
81
+ open_timeout: 120,
82
+ read_timeout: 120
83
+ )
84
+ end
85
+
86
+ def set_oauth_token(token)
87
+ @oauth_token = token
88
+ soap_client.globals[:soap_header] = header
89
+ end
90
+
91
+ def retrieve(object_type, properties, filter = nil)
92
+ raise ExactTargetClient::ExactTargetAPI::ClientException.new('Object properties must be specified') unless properties.present?
93
+ payload = {'ObjectType' => object_type, 'Properties' => properties}
94
+ if filter.present?
95
+ values = Array.wrap(filter[:value])
96
+ payload['Filter'] = {
97
+ '@xsi:type' => 'tns:SimpleFilterPart',
98
+ 'Property' => filter[:property],
99
+ 'SimpleOperator' => values.one? ? 'equals' : 'IN',
100
+ 'Value' => values
101
+ }
102
+ end
103
+ message = {'RetrieveRequest' => payload}
104
+ soap_request :retrieve, message
105
+ end
106
+
107
+ def create(object_type, properties)
108
+ soap_action :create, object_type, properties
109
+ end
110
+
111
+ def update(object_type, properties)
112
+ soap_action :update, object_type, properties
113
+ end
114
+
115
+ def delete(object_type, properties)
116
+ soap_action :delete, object_type, properties
117
+ end
118
+
119
+ private
120
+
121
+ def soap_action(action, object_type, properties)
122
+ properties['@xsi:type'] = "tns:#{object_type}"
123
+ message = {
124
+ 'Objects' => properties
125
+ }
126
+ soap_request action, message
127
+ end
128
+
129
+ def soap_request(action, message)
130
+ responseObject = SoapResponse
131
+ begin
132
+ tries ||= 3
133
+ response = soap_client.call(action, :message => message)
134
+ # TODO - handle other error types
135
+ rescue Savon::SOAPFault => error
136
+ message = error.to_hash[:fault][:faultstring]
137
+ if message == 'Token Expired'
138
+ raise ExactTargetClient::ExactTargetAPI::TokenExpired
139
+ elsif message == 'Login Failed'
140
+ retry unless (tries -= 1).zero?
141
+ else
142
+ raise ExactTargetClient::ExactTargetAPI::ClientException.new("SOAP Client Error: #{message}")
143
+ end
144
+ end
145
+ responseObject.new response, RESPONSE_RESULT_KEYS[action], self
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,3 @@
1
+ module ExactTargetClient
2
+ VERSION = '0.0.2'
3
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exact-target-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ariel Cabib
8
+ - Yaron Dinur
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2016-08-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.12'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.12'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: json
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: savon
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '='
75
+ - !ruby/object:Gem::Version
76
+ version: 2.3.3
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '='
82
+ - !ruby/object:Gem::Version
83
+ version: 2.3.3
84
+ - !ruby/object:Gem::Dependency
85
+ name: typhoeus
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: oj
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ description: Ruby client for interacting with ExactTarget (Salesforce Marketing Cloud)
113
+ APIs
114
+ email:
115
+ - acabib@yotpo.com
116
+ - ydinur@yotpo.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".gitignore"
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/console
127
+ - bin/setup
128
+ - exact-target-client.gemspec
129
+ - lib/exact_target_client/conf.rb
130
+ - lib/exact_target_client/exact_target_api.rb
131
+ - lib/exact_target_client/exact_target_rest_client.rb
132
+ - lib/exact_target_client/exact_target_soap_client.rb
133
+ - lib/exact_target_client/version.rb
134
+ homepage: https://github.com/YotpoLtd/exact-target-ruby-api
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.4.8
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Ruby client for interacting with ExactTarget (Salesforce Marketing Cloud)
158
+ APIs
159
+ test_files: []