marketingcloudsdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +92 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.md +13 -0
  7. data/README.md +130 -0
  8. data/Rakefile +1 -0
  9. data/lib/marketingcloudsdk.rb +74 -0
  10. data/lib/marketingcloudsdk/client.rb +283 -0
  11. data/lib/marketingcloudsdk/http_request.rb +113 -0
  12. data/lib/marketingcloudsdk/objects.rb +757 -0
  13. data/lib/marketingcloudsdk/rest.rb +122 -0
  14. data/lib/marketingcloudsdk/soap.rb +288 -0
  15. data/lib/marketingcloudsdk/targeting.rb +58 -0
  16. data/lib/marketingcloudsdk/utils.rb +47 -0
  17. data/lib/marketingcloudsdk/version.rb +39 -0
  18. data/lib/new.rb +1240 -0
  19. data/marketingcloudsdk.gemspec +30 -0
  20. data/samples/sample-AddSubscriberToList.rb +56 -0
  21. data/samples/sample-CreateAndStartDataExtensionImport.rb +29 -0
  22. data/samples/sample-CreateAndStartListImport.rb +27 -0
  23. data/samples/sample-CreateContentAreas.rb +48 -0
  24. data/samples/sample-CreateDataExtensions.rb +54 -0
  25. data/samples/sample-CreateProfileAttributes.rb +48 -0
  26. data/samples/sample-SendEmailToDataExtension.rb +23 -0
  27. data/samples/sample-SendEmailToList.rb +23 -0
  28. data/samples/sample-SendTriggeredSends.rb +30 -0
  29. data/samples/sample-bounceevent.rb +70 -0
  30. data/samples/sample-campaign.rb +211 -0
  31. data/samples/sample-clickevent.rb +71 -0
  32. data/samples/sample-contentarea.rb +122 -0
  33. data/samples/sample-dataextension.rb +209 -0
  34. data/samples/sample-directverb.rb +55 -0
  35. data/samples/sample-email.rb +122 -0
  36. data/samples/sample-email.senddefinition.rb +134 -0
  37. data/samples/sample-folder.rb +143 -0
  38. data/samples/sample-import.rb +104 -0
  39. data/samples/sample-list.rb +105 -0
  40. data/samples/sample-list.subscriber.rb +97 -0
  41. data/samples/sample-openevent.rb +70 -0
  42. data/samples/sample-profileattribute.rb +57 -0
  43. data/samples/sample-sentevent.rb +70 -0
  44. data/samples/sample-subscriber.rb +136 -0
  45. data/samples/sample-triggeredsend.rb +130 -0
  46. data/samples/sample-unsubevent.rb +72 -0
  47. data/samples/sample_helper.rb.template +8 -0
  48. data/spec/client_spec.rb +210 -0
  49. data/spec/helper_funcs_spec.rb +11 -0
  50. data/spec/http_request_spec.rb +36 -0
  51. data/spec/objects_helper_spec.rb +32 -0
  52. data/spec/objects_spec.rb +484 -0
  53. data/spec/rest_spec.rb +48 -0
  54. data/spec/soap_spec.rb +140 -0
  55. data/spec/spec_helper.rb +14 -0
  56. data/spec/targeting_spec.rb +39 -0
  57. metadata +260 -0
@@ -0,0 +1,122 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ module MarketingCloudSDK
38
+ module Rest
39
+
40
+ include MarketingCloudSDK::Targeting
41
+
42
+ def rest_client
43
+ self
44
+ end
45
+
46
+ def normalize_keys obj
47
+ if obj and obj.is_a? Hash
48
+ obj.keys.each do |k|
49
+ obj[(k.to_sym rescue k) || k] = obj.delete(k)
50
+ end
51
+ end
52
+ obj
53
+ end
54
+
55
+ def get_url_properties url, properties
56
+ url_property_names = url.scan(/(%{(.+?)})/).collect{|frmt, name| name}
57
+ url_properties = {}
58
+ properties.keys.each do |k|
59
+ if url_property_names.include? k
60
+ url_properties[k] = properties.delete(k)
61
+ end
62
+ end
63
+ url_properties
64
+ end
65
+
66
+ def complete_url url, url_properties
67
+ normalize_keys(url_properties)
68
+ url = url % url_properties if url_properties
69
+ url.end_with?('/') ? url.chop : url
70
+ rescue KeyError => ex
71
+ raise "#{ex.message} to complete #{url}"
72
+ end
73
+
74
+ def parse_properties url, properties
75
+ url_properties = get_url_properties url, properties
76
+ url = complete_url url, url_properties
77
+ [url, properties]
78
+ end
79
+
80
+ def rest_get url, properties={}
81
+ url, properties = parse_properties url, properties
82
+ rest_request :get, url, {'params' => properties}
83
+ end
84
+
85
+ def rest_delete url, properties={}
86
+ url, properties = parse_properties url, properties
87
+ rest_request :delete, url
88
+ end
89
+
90
+ def rest_patch url, properties={}
91
+ url, payload = parse_properties url, properties
92
+ rest_request :patch, url, {'data' => payload,
93
+ 'content_type' => 'application/json'}
94
+ end
95
+
96
+ def rest_post url, properties={}
97
+ url, payload = parse_properties url, properties
98
+ rest_request :post, url, {'data' => payload,
99
+ 'content_type' => 'application/json'}
100
+ end
101
+
102
+ private
103
+ def rest_request action, url, options={}
104
+ retried = false
105
+ begin
106
+ #Try to refresh the token and if we do then we need to regenerate the header as well.
107
+ self.refresh
108
+ (options['params'] ||= {}).merge! 'access_token' => access_token
109
+ rsp = rest_client.send(action, url, options)
110
+ raise 'Unauthorized' if rsp.message == 'Unauthorized'
111
+ rescue
112
+ raise if retried
113
+ self.refresh! # ask for forgiveness not, permission
114
+ retried = true
115
+ retry
116
+ end
117
+ rsp
118
+ rescue
119
+ rsp
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,288 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ require 'savon'
38
+ module MarketingCloudSDK
39
+
40
+ class SoapResponse < MarketingCloudSDK::Response
41
+
42
+ def continue
43
+ rsp = nil
44
+ if more?
45
+ rsp = unpack @client.soap_client.call(:retrieve, :message => {'ContinueRequest' => request_id})
46
+ else
47
+ puts 'No more data'
48
+ end
49
+
50
+ rsp
51
+ end
52
+
53
+ private
54
+ def unpack_body raw
55
+ @body = raw.body
56
+ @request_id = raw.body[raw.body.keys.first][:request_id]
57
+ unpack_msg raw
58
+ rescue
59
+ @message = raw.http.body
60
+ @body = raw.http.body unless @body
61
+ end
62
+
63
+ def unpack raw
64
+ @code = raw.http.code
65
+ unpack_body raw
66
+ @success = @message == 'OK'
67
+ @results += (unpack_rslts raw)
68
+ end
69
+
70
+ def unpack_msg raw
71
+ @message = raw.soap_fault? ? raw.body[:fault][:faultstring] : raw.body[raw.body.keys.first][:overall_status]
72
+ end
73
+
74
+ def unpack_rslts raw
75
+ @more = (raw.body[raw.body.keys.first][:overall_status] == 'MoreDataAvailable')
76
+ rslts = raw.body[raw.body.keys.first][:results] || []
77
+ rslts = [rslts] unless rslts.kind_of? Array
78
+ rslts
79
+ rescue
80
+ []
81
+ end
82
+ end
83
+
84
+ class DescribeResponse < SoapResponse
85
+ attr_reader :properties, :retrievable, :updatable, :required, :extended, :viewable, :editable
86
+ private
87
+
88
+ def unpack_rslts raw
89
+ @retrievable, @updatable, @required, @properties, @extended, @viewable, @editable = [], [], [], [], [], [], [], []
90
+ definition = raw.body[raw.body.keys.first][:object_definition]
91
+ _props = definition[:properties]
92
+ _props.each do |p|
93
+ @retrievable << p[:name] if p[:is_retrievable] and (p[:name] != 'DataRetentionPeriod')
94
+ @updatable << p[:name] if p[:is_updatable]
95
+ @required << p[:name] if p[:is_required]
96
+ @properties << p[:name]
97
+ end
98
+ # ugly, but a necessary evil
99
+ _exts = definition[:extended_properties].nil? ? {} : definition[:extended_properties] # if they have no extended properties nil is returned
100
+ _exts = _exts[:extended_property] || [] # if no properties nil and we need an array to iterate
101
+ _exts = [_exts] unless _exts.kind_of? Array # if they have only one extended property we need to wrap it in array to iterate
102
+ _exts.each do |p|
103
+ @viewable << p[:name] if p[:is_viewable]
104
+ @editable << p[:name] if p[:is_editable]
105
+ @extended << p[:name]
106
+ end
107
+ @success = true # overall_status is missing from definition response, so need to set here manually
108
+ _props + _exts
109
+ rescue
110
+ @message = "Unable to describe #{raw.locals[:message]['DescribeRequests']['ObjectDefinitionRequest']['ObjectType']}"
111
+ @success = false
112
+ []
113
+ end
114
+ end
115
+
116
+ module Soap
117
+ attr_accessor :wsdl, :debug#, :internal_token
118
+
119
+ include MarketingCloudSDK::Targeting
120
+
121
+ def header
122
+ raise 'Require legacy token for soap header' unless internal_token
123
+ {
124
+ 'oAuth' => {'oAuthToken' => internal_token},
125
+ :attributes! => { 'oAuth' => { 'xmlns' => 'http://exacttarget.com' }}
126
+ }
127
+ end
128
+
129
+ def debug
130
+ @debug ||= false
131
+ end
132
+
133
+ def wsdl
134
+ @wsdl ||= 'https://webservice.exacttarget.com/etframework.wsdl'
135
+ end
136
+
137
+ def soap_client
138
+ self.refresh
139
+ @soap_client = Savon.client(
140
+ soap_header: header,
141
+ wsdl: wsdl,
142
+ endpoint: endpoint,
143
+ wsse_auth: ["*", "*"],
144
+ raise_errors: false,
145
+ log: debug,
146
+ open_timeout:180,
147
+ read_timeout: 180
148
+ )
149
+ end
150
+
151
+ def soap_describe object_type
152
+ message = {
153
+ 'DescribeRequests' => {
154
+ 'ObjectDefinitionRequest' => {
155
+ 'ObjectType' => object_type
156
+ }
157
+ }
158
+ }
159
+ soap_request :describe, message
160
+ end
161
+
162
+ def soap_perform object_type, action, properties
163
+ message = {}
164
+ message['Action'] = action
165
+ message['Definitions'] = {'Definition' => properties}
166
+ message['Definitions'][:attributes!] = { 'Definition' => { 'xsi:type' => ('tns:' + object_type) }}
167
+
168
+ soap_request :perform, message
169
+ end
170
+
171
+
172
+ def soap_configure object_type, action, properties
173
+ message = {}
174
+ message['Action'] = action
175
+ message['Configurations'] = {}
176
+ if properties.is_a? Array then
177
+ message['Configurations']['Configuration'] = []
178
+ properties.each do |configItem|
179
+ message['Configurations']['Configuration'] << configItem
180
+ end
181
+ else
182
+ message['Configurations'] = {'Configuration' => properties}
183
+ end
184
+ message['Configurations'][:attributes!] = { 'Configuration' => { 'xsi:type' => ('tns:' + object_type) }}
185
+
186
+ soap_request :configure, message
187
+ end
188
+
189
+ def soap_get object_type, properties=nil, filter=nil
190
+ if properties.nil? or properties.empty?
191
+ rsp = soap_describe object_type
192
+ if rsp.success?
193
+ properties = rsp.retrievable
194
+ else
195
+ rsp.instance_variable_set(:@message, "Unable to get #{object_type}") # back door update
196
+ return rsp
197
+ end
198
+ elsif properties.kind_of? Hash
199
+ properties = properties.keys
200
+ elsif properties.kind_of? String
201
+ properties = [properties]
202
+ end
203
+
204
+ message = {'ObjectType' => object_type, 'Properties' => properties}
205
+
206
+ if filter and filter.kind_of? Hash
207
+ message['Filter'] = filter
208
+ message[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:SimpleFilterPart' } }
209
+
210
+ if filter.has_key?('LogicalOperator')
211
+ message[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:ComplexFilterPart' }}
212
+ message['Filter'][:attributes!] = {
213
+ 'LeftOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' },
214
+ 'RightOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }}
215
+ end
216
+ end
217
+ message = {'RetrieveRequest' => message}
218
+
219
+ soap_request :retrieve, message
220
+ end
221
+
222
+ def soap_post object_type, properties
223
+ soap_cud :create, object_type, properties
224
+ end
225
+
226
+ def soap_patch object_type, properties
227
+ soap_cud :update, object_type, properties
228
+ end
229
+
230
+ def soap_delete object_type, properties
231
+ soap_cud :delete, object_type, properties
232
+ end
233
+
234
+ def soap_put object_type, properties
235
+ soap_cud :update, object_type, properties, true
236
+ end
237
+
238
+ private
239
+
240
+ def soap_cud action, object_type, properties, upsert=nil
241
+ # get a list of attributes so we can seperate
242
+ # them from standard object properties
243
+ #type_attrs = soap_describe(object_type).editable
244
+
245
+ #
246
+ # properties = [properties] unless properties.kind_of? Array
247
+ # properties.each do |p|
248
+ # formated_attrs = []
249
+ # p.each do |k, v|
250
+ # if type_attrs.include? k
251
+ # p.delete k
252
+ # attrs = MarketingCloudSDK.format_name_value_pairs k => v
253
+ # formated_attrs.concat attrs
254
+ # end
255
+ # end
256
+ # (p['Attributes'] ||= []).concat formated_attrs unless formated_attrs.empty?
257
+ # end
258
+ #
259
+
260
+ message = {
261
+ 'Objects' => properties,
262
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + object_type) } }
263
+ }
264
+
265
+ if upsert
266
+ message['Options'] = {"SaveOptions" => {"SaveOption" => {"PropertyName"=> "*", "SaveAction" => "UpdateAdd"}}}
267
+ end
268
+
269
+ soap_request action, message
270
+ end
271
+
272
+ def soap_request action, message
273
+ response = action.eql?(:describe) ? DescribeResponse : SoapResponse
274
+ retried = false
275
+ begin
276
+ rsp = soap_client.call(action, :message => message)
277
+ rescue
278
+ raise if retried
279
+ retried = true
280
+ retry
281
+ end
282
+ response.new rsp, self
283
+ rescue
284
+ raise if rsp.nil?
285
+ response.new rsp, self
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,58 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ module MarketingCloudSDK::Targeting
38
+ attr_accessor :access_token
39
+ attr_reader :endpoint
40
+
41
+ include MarketingCloudSDK::HTTPRequest
42
+
43
+ def endpoint
44
+ unless @endpoint
45
+ determine_stack
46
+ end
47
+ @endpoint
48
+ end
49
+
50
+ protected
51
+ def determine_stack
52
+ options = {'params' => {'access_token' => self.access_token}}
53
+ response = get("https://www.exacttargetapis.com/platform/v1/endpoints/soap", options)
54
+ @endpoint = response['url']
55
+ rescue => e
56
+ raise 'Unable to determine stack using: ' + e.message
57
+ end
58
+ end