bluekai 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +44 -0
  8. data/Rakefile +2 -0
  9. data/bluekai.gemspec +28 -0
  10. data/circle.yml +3 -0
  11. data/lib/bluekai.rb +7 -0
  12. data/lib/bluekai/client.rb +407 -0
  13. data/lib/bluekai/error.rb +4 -0
  14. data/lib/bluekai/version.rb +3 -0
  15. data/spec/fixtures/signatures/1952b4f61dbd2187b5589dfc390af2ee985ca6f43020081d420f5d9ca025532e +1 -0
  16. data/spec/fixtures/signatures/26d52d2e4c2d42ba54ec6b4de9e67d9b566aeb8224c28261d1d133b7fb3c3436 +1 -0
  17. data/spec/fixtures/signatures/2db01e685f73149994efc9824c10fa91e704fb7256d748c4305101513444f49f +1 -0
  18. data/spec/fixtures/signatures/391112d6698e9864c1d9b37f92ab7e6db690a7e84949378ee272b011f3b82fc0 +1 -0
  19. data/spec/fixtures/signatures/3c33aa44e5fbea304ffda0390ac2e413a02d187d5425f67c3edab34684b2b18f +1 -0
  20. data/spec/fixtures/signatures/691325444d07bf08ce72b95283f8a8fd74e19f6963263e5367772b7633cbeab0 +1 -0
  21. data/spec/fixtures/signatures/78f40f967bf9cb1ae030412e7c28267a8050926a538226c685fb2dec6f23b44a +1 -0
  22. data/spec/fixtures/signatures/847da153b01c0ea4e3beeeb00f5552c3dd773227ee5aeaf5c9ff9a620adae324 +1 -0
  23. data/spec/fixtures/signatures/a49b4d9ac5b30c91f95a907b887f9b8eaf5c7856b844c49d2b937c00d88af63d +1 -0
  24. data/spec/fixtures/signatures/a6d578d433ca6f7c84ce9da14c415b3d302bf1da2b87f7666c637419fb784f57 +1 -0
  25. data/spec/fixtures/signatures/d30077840edb2b580c1c9a4a6659989b8342cfdc439ceef0c984672849288243 +1 -0
  26. data/spec/fixtures/signatures/d3c90cbaf307901dff229713b703217d7460f3c50400aa981ac20088f58343ad +1 -0
  27. data/spec/fixtures/vcr_cassettes/Bluekai_Client/_ping/when_we_get_a_pong_from_bluekai/.yml +30 -0
  28. data/spec/fixtures/vcr_cassettes/Bluekai_Client/creates_a_category.yml +49 -0
  29. data/spec/fixtures/vcr_cassettes/Bluekai_Client/creates_a_rule.yml +57 -0
  30. data/spec/fixtures/vcr_cassettes/Bluekai_Client/lists_10_phint_rules.yml +194 -0
  31. data/spec/fixtures/vcr_cassettes/Bluekai_Client/lists_4_categories.yml +3569 -0
  32. data/spec/fixtures/vcr_cassettes/Bluekai_Client/lists_Bluekai_taxonomy_nodes.yml +161267 -0
  33. data/spec/fixtures/vcr_cassettes/Bluekai_Client/performs_a_ping.yml +36 -0
  34. data/spec/fixtures/vcr_cassettes/Bluekai_Client/reads_a_category_and_its_reach.yml +2459 -0
  35. data/spec/fixtures/vcr_cassettes/Bluekai_Client/reads_a_rule.yml +56 -0
  36. data/spec/fixtures/vcr_cassettes/Bluekai_Client/updates_a_category.yml +48 -0
  37. data/spec/fixtures/vcr_cassettes/Bluekai_Client/updates_a_rule.yml +57 -0
  38. data/spec/lib/bluekai/client_integration_spec.rb +153 -0
  39. data/spec/lib/bluekai/client_spec.rb +19 -0
  40. data/spec/spec_helper.rb +108 -0
  41. metadata +193 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 68f6e5115ea82ddcda1d0ad4780f27fd0b33d73e
4
+ data.tar.gz: acd51bbeef54aac2ce754571fbb6fe3cd592707a
5
+ SHA512:
6
+ metadata.gz: 8460428509ffdac8f0b0994f4be95fa89982e967d67d7342a5b9a796729e6672f2aa879e1b247798b65a8afabff27be2e3f757da0e4b8a97fd76127c44843b80
7
+ data.tar.gz: e35bbd450af7dc97de6489f679c8fbfa6415e483d967dcb322088d49db60f757a816aa42ccf98276588d64588fc8b4226ea1a46dcc32ec59d1a7c631ee8a6b72
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+
6
+ group :development, :test do
7
+ gem 'rubocop-ci', github: 'ad2games/rubocop-ci'
8
+ gem "codeclimate-test-reporter", group: :test, require: nil
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 ad2games GmbH
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Bluekai
2
+ [![Gem Version](http://img.shields.io/gem/v/bluekai.svg)](http://rubygems.org/gems/bluekai)
3
+ [![Circle CI](https://circleci.com/gh/ad2games/bluekai.png?style=shield&circle-token=323d6ce1376f1473cab4474d89f8fc7287446595)](https://circleci.com/gh/ad2games/bluekai)
4
+ [![Code Climate](https://codeclimate.com/repos/5523e6d8695680516e00144d/badges/40648ea53c768dd15de5/gpa.svg)](https://codeclimate.com/repos/5523e6d8695680516e00144d/feed)
5
+ [![Test Coverage](https://codeclimate.com/repos/5523e6d8695680516e00144d/badges/40648ea53c768dd15de5/coverage.svg)](https://codeclimate.com/repos/5523e6d8695680516e00144d/feed)
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'bluekai'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install bluekai
21
+
22
+ ## Usage
23
+
24
+ Before you start, please ensure the following variables are correctly set in your environment: `ENV['BLUEKAI_DOMAIN']`, `ENV['BLUEKAI_API_USER_KEY']`, `ENV['BLUEKAI_API_PRIVATE_KEY']` and `ENV['BLUEKAI_PARTNER_ID']`
25
+
26
+
27
+ List all self-classification categories
28
+ ```ruby
29
+ Bluekai::Client.new.category_list({})
30
+ ```
31
+ Read category parameters including estimated reach (estimated number of unique users
32
+ based on 30-day inventory) on desktop
33
+
34
+ ```ruby
35
+ Bluekai::Client.new.category_read({category_id: 421426, stats: 'true', device_type: 'desktop'})
36
+ ```
37
+
38
+ ## Contributing
39
+
40
+ 1. Fork it ( https://github.com/[my-github-username]/bluekai/fork )
41
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
42
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rubocop-ci'
data/bluekai.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bluekai/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bluekai"
8
+ spec.version = Bluekai::VERSION
9
+ spec.authors = ["ad2games GmbH"]
10
+ spec.email = ["developers@ad2games.com"]
11
+ spec.summary = "Simple client for the BlueKai API"
12
+ spec.description = "Simple client for the BlueKai API (services.bluekai.com)"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'httparty'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency 'vcr'
26
+ spec.add_development_dependency 'webmock'
27
+ spec.add_development_dependency 'rspec'
28
+ end
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ test:
2
+ pre:
3
+ - bundle exec rake rubocop
data/lib/bluekai.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'bluekai/version'
2
+ require 'bluekai/client'
3
+ require 'bluekai/error'
4
+
5
+ module Bluekai
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,407 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'httparty'
4
+ require 'cgi'
5
+
6
+ module Bluekai
7
+ # A simple BlueKai client
8
+ class Client
9
+ attr_reader :domain, :api_user_key
10
+
11
+ def initialize(opts = {})
12
+ @domain = opts.fetch(:domain, ENV['BLUEKAI_DOMAIN']) ||
13
+ fail(Error, 'BlueKai domain missing')
14
+ @api_user_key = opts.fetch(:api_user_key, ENV['BLUEKAI_API_USER_KEY']) ||
15
+ fail(Error, 'BlueKai API user key missing')
16
+ @api_private_key = opts.fetch(:api_private_key, ENV['BLUEKAI_API_PRIVATE_KEY']) ||
17
+ fail(Error, 'BlueKai API private key missing')
18
+ @partner_id = opts.fetch(:partner_id, ENV['BLUEKAI_PARTNER_ID']) ||
19
+ fail(Error, 'BlueKai PartnerID missing')
20
+ @opts = opts
21
+ end
22
+
23
+ def ping
24
+ request('GET', '/Services/WS/Ping', {}).to_i == 200 rescue false
25
+ end
26
+
27
+ # Public: Lists categories in the BlueKai taxonomy. API definition
28
+ # can be found here https://kb.bluekai.com/display/PD/Taxonomy+API
29
+ #
30
+ # parentId - integer
31
+ # fullPath - {0,1}
32
+ # bkSize - {0,1} Enter 1 to include the inventory of unique users in
33
+ # the BlueKai network for each category.
34
+ # intlDataCountryId - {-1..24} for country index see
35
+ # (https://kb.bluekai.com/display/PD/Taxonomy+API)
36
+ # device_code - {0 = Desktop + Mobile,1 = Desktop, 2 = Mobile}
37
+ # showBuyable - {0,1}
38
+ # showLeafStatus - {0,1}
39
+ # description - {0,1}
40
+ # vertical - {0,1}
41
+ # showReceivedAudienceCategories - {0,1}
42
+ # showCategoryPriceAtDate - {'YYYY-MM-DD'}
43
+ #
44
+ # Returns array of taxonomy nodes.
45
+ def taxonomy(query = {})
46
+ request('GET', '/Services/WS/Taxonomy', query)[:nodeList]
47
+ end
48
+
49
+ ####
50
+ #### Categories
51
+ ####
52
+ #### API definition can be found here
53
+ #### https://kb.bluekai.com/display/PD/Self-Classification+Category+API
54
+ ####
55
+
56
+ # Public: Lists self classification categories in private taxonomy
57
+ #
58
+ # name:string - Returns all self-classification categories based on the specified
59
+ # name (whole or partial). The name is case-insensitive.
60
+ #
61
+ # offset:integer - Specify the starting index from which to return
62
+ # the self-classification categories.
63
+ #
64
+ # size:integer - Specifies the maximum number of categories to be included in
65
+ # the response. This filter requires the offset filter to be specified.
66
+ #
67
+ # parent_id:integer - Returns all self-classification categories
68
+ # based on the ID of the specified parent category.
69
+ #
70
+ # sort_by:string - Enter 'name' or 'id' to sort the returned self-classification categories in
71
+ # alphabetical or numerical order (based on categoryId)
72
+ #
73
+ # sorting_order:string - Enter 'asc' or 'desc' to list the returned
74
+ # self-classification categories in ascending
75
+ # or descending order based on the category
76
+ # name or categoryId.
77
+ #
78
+ # Returns array of category hashes
79
+ def category_list(query = {})
80
+ query = { sort_by: 'name',
81
+ sorting_order: 'asc' }.merge(query)
82
+ request('GET', '/Services/WS/classificationCategories', query)[:categories]
83
+ end
84
+
85
+ # Public: returns the self-classification category
86
+ # specified by the category_id
87
+ #
88
+ # category_id:integer - MANDATORY The unique ID assigned to the
89
+ # self-classification category to be retrieved
90
+ #
91
+ # stats:string - {'True','False'} Returns the reach (estimated
92
+ # number of unique users based on 30-day
93
+ # inventory) for the self-classification category.
94
+ #
95
+ # device_type:string - reach for the self-classification category
96
+ # based on the specified device, which may either
97
+ # be 'all', 'desktop', or 'mobile'
98
+ #
99
+ # intl_code - returns the reach for the self-classification category
100
+ # based on the specified country. The default country
101
+ # is ALL. You may enter one of the following country
102
+ # codes: ALL, US, AU, CA, GB, GER, ESP, NL, MX, IT,
103
+ # FR, BR, AR, RU, NZ, JP, CL, CN.
104
+ #
105
+ # Returns: A hash of Bluekai private category data
106
+ def category_read(query)
107
+ fail 'no category_id found in hash' unless query.key?(:category_id)
108
+ category_id = query.delete(:category_id)
109
+ request('GET', "/Services/WS/classificationCategories/#{category_id}", query)
110
+ end
111
+
112
+ # Public: Creates a new self-classification category
113
+ #
114
+ # name:string - Name of the self-classification category
115
+ #
116
+ # parent_id:integer - Unique ID of the parent node for the
117
+ # self-classification category.
118
+ #
119
+ # description:string - Description of uUser attribute
120
+ # represented by this category.
121
+ #
122
+ # analytics_excluded:string - {'True','False'} Specify whether the
123
+ # self-classification category is to be excluded
124
+ # from Audience Analytics reports. This property
125
+ # is false by default.
126
+ #
127
+ # navigation_only:string - {'True','False'} Specify whether the
128
+ # self-classification category functions
129
+ # exclusively as a parent node that cannot be
130
+ # selected. This property is false by default.
131
+ #
132
+ # mutex_children:string - {'True','False'} Specify whether to limit
133
+ # the number of the category's child nodes
134
+ # that can be added to an audience segment to one.
135
+ # This property is false by default.
136
+ #
137
+ # notes:string - (Optional) Enter any notes to be associated with
138
+ # this self-classification category.
139
+ #
140
+ # Example body hash = {name: 'a new category',
141
+ # parent_id: '2342', description: 'an example category',
142
+ # analytics_excluded: 'false', navigation_only: 'false',
143
+ # mutex_children: 'false', notes: 'Just an API test' }
144
+ #
145
+ # Returns: hash of created category parameters including its category_id
146
+ def category_create(body)
147
+ request('POST', '/Services/WS/classificationCategories', {}, body)
148
+ end
149
+
150
+ # Public: Updates a given self-classification category
151
+ #
152
+ # category_id:integer - The unique ID assigned to the self-classification
153
+ # category to be updated
154
+ #
155
+ # name:string - Name of the self-classification category
156
+ #
157
+ # parent_id:integer - Unique ID of the parent node for the
158
+ # self-classification category.
159
+ #
160
+ # description:string - Description of uUser attribute
161
+ # represented by this category.
162
+ #
163
+ # analytics_excluded:string - {'True','False'} Specify whether the
164
+ # self-classification category is to be excluded
165
+ # from Audience Analytics reports. This property
166
+ # is false by default.
167
+ #
168
+ # navigation_only:string - {'True','False'} Specify whether the
169
+ # self-classification category functions
170
+ # exclusively as a parent node that cannot be
171
+ # selected. This property is false by default.
172
+ #
173
+ # mutex_children:string - {'True','False'} Specify whether to limit
174
+ # the number of the category's child nodes
175
+ # that can be added to an audience segment to one.
176
+ # This property is false by default.
177
+ #
178
+ # notes:string - (Optional) Enter any notes to be associated with
179
+ # this self-classification category.
180
+ #
181
+ # Example body hash = {category_id: 1234, name: 'a chaged category',
182
+ # parent_id: '2342', description: 'an example category',
183
+ # analytics_exclued: 'false', navigation_only: 'false',
184
+ # mutex_children: 'false', notes: 'Just an API test' }
185
+ #
186
+ # Returns: hash of updated category parameters
187
+ def category_update(category_id, body)
188
+ request('PUT', "/Services/WS/classificationCategories/#{category_id}", {}, body)
189
+ end
190
+
191
+ ####
192
+ #### Classification Rules
193
+ #### API definition can be found here
194
+ #### https://kb.bluekai.com/display/PD/Self-Classification+Rule+API
195
+ ####
196
+
197
+ # Public: List the self-classification rules in your private
198
+ # taxonomy
199
+ #
200
+ # sort_by:string - Enter 'status', 'id', 'created_at', 'updated_at',
201
+ # or 'type' to sort the returned self-classification
202
+ # rules by the specified option.
203
+ #
204
+ # sorting_order:string - Enter 'asc' or 'desc' to list the returned
205
+ # self-classification rules in ascending or descending
206
+ # order based on the specified sort option.
207
+ #
208
+ # ids:integer - Returns the self-classification rule matching the specified
209
+ # rule ID, or returns all the self-classification rules matching
210
+ # the specified list of rule IDs. Syntax for passing multiple rule IDs:
211
+ # ruleId1&id=ruleId2 Example: 123&id=125
212
+ #
213
+ # type:enum - Enter 'phint' or 'url' to return only the phint or
214
+ # URL-based self-classification rules.
215
+ #
216
+ # site_ids:string - Returns all the self-classification rules under the
217
+ # specified site ID or list of site IDs. Syntax for
218
+ # passing multiple site IDs: site_id_1&site_ids=site_id_2
219
+ # Example: 1234&site_ids=1235
220
+ #
221
+ # category_ids:string - Returns all the self-classification rules used
222
+ # to map the specified catgeory ID or list of category IDs.
223
+ # Syntax for passing multiple category
224
+ # IDs: category_id_1&category_ids=category_id_2
225
+ # Example: 1234&category_ids=1235
226
+ #
227
+ # offset:integer - Specify the starting index from which to return the
228
+ # self-classification rules.
229
+ #
230
+ # size:integer - Specify the maximum number of rules to be included in
231
+ # the response. This filter requires the offset filter
232
+ # to be specified.
233
+ #
234
+ # created_date_range:string - Returns all the self-classification rules
235
+ # created within the specified list of dates.
236
+ # Syntax: YYYY-MM-DD&created_date_range=YYYY-MM-DD
237
+ # Example: 2014-01-01&created_date_range=2014-31-01
238
+ #
239
+ # updated_date_range:string - Returns all the self-classification rules updated
240
+ # within the specified list of dates.
241
+ # Syntax: YYYY-MM-DD&updated_date_range=YYYY-MM-DD
242
+ # Example: 2014-01-01&updated_date_range=2014-31-01
243
+ #
244
+ # status:string - Enter 'Active' or 'Creating' to return the
245
+ # self-classification rules based on the specified status referrer
246
+ # boolean Returns all URL-based self-classification rules that
247
+ # classify the site URL (False) or the referrer URL (True) in
248
+ # the collected URL.
249
+ #
250
+ # exact:boolean - Returns all URL-based self-classification rules that classify
251
+ # an exact URL (True) or a top-level URL (False) in the collected URL.
252
+ # Returns: hash of Bluekai rules
253
+ def rule_list(query)
254
+ request('GET', '/Services/WS/classificationRules', query)[:rules]
255
+ end
256
+
257
+ # Public: Reads a self-classification rule
258
+ #
259
+ # rule_id:integer - The unique ID assigned to the
260
+ # self-classification rule to be retrieved
261
+ #
262
+ # Returns: hash of Blukkai rule parameters
263
+ def rule_read(rule_id)
264
+ request('GET', "/Services/WS/classificationRules/#{rule_id}", {})
265
+ end
266
+
267
+ # Public: Creates a new self-classification rule
268
+ #
269
+ # name:string - Enter a string specifying the name of the self-classification rule.
270
+ #
271
+ # type:string - {'phint', 'url'} Specify the type of classification rule.
272
+ #
273
+ # phints:[{phint}] - If you are creating a phint-based rule,
274
+ # enter a list of your phint definitions.
275
+ # Each phint requires the following properties:
276
+ # key - The phint key operator - The criteria
277
+ # used for determining how the phint value
278
+ # is applied ('is' or 'contains')
279
+ # value - The full or partial phint value,
280
+ # depending on the specified operator.
281
+ #
282
+ # urls:[string(s)] - Provide a list of your URL definitions
283
+ # if you are creating for URL-based rules.
284
+ #
285
+ # referrer:string - {'True','False'} If you are creating a
286
+ # URL-based rule, specify whether the URL to
287
+ # be classified is the site URL (False) or
288
+ # the referrer URL (True).
289
+ #
290
+ # exact:string - {'True','False'} If you are creating a
291
+ # URL-based rule, specify whether the URL collected
292
+ # from your site must match the URL in your
293
+ # rule (True) or match a top-level URL (False) so
294
+ # that you can target users visiting the child pages
295
+ # without specifying them.
296
+ #
297
+ # partner_id:integer - Enter the unique ID assigned to your BlueKai DMP seat.
298
+ #
299
+ # site_ids (optional):[interger(s)] - Enter a list of containers/site IDs to which
300
+ # the self-classification rule applies. If
301
+ # you do not include this parameter, the
302
+ # rule is applicable to ALL the
303
+ # container/site IDs in your seat.
304
+ #
305
+ # category_ids:[integer(s)] - a list of category IDs to which
306
+ # the self-classification rule applies.
307
+ #
308
+ # JSON example for Phint-based self-classification rule
309
+ # {
310
+ # "name": "Phint Example",
311
+ # "type": "phint",
312
+ # "phints": [
313
+ # {
314
+ # "key": "x",
315
+ # "value": "123",
316
+ # "operator": "is"
317
+ # }
318
+ # ],
319
+ # "partner_id": 123,
320
+ # "site_ids": [1234],
321
+ # "category_ids": [12345]
322
+ # }
323
+ #
324
+ # JSON example for URL-based self-classiifcation rule
325
+ # {
326
+ # "name": "URL Example",
327
+ # "type": "url",
328
+ # "urls": ["http://shop.yoursite.com"],
329
+ # "referrer": false,
330
+ # "exact": false,
331
+ # "partner_id": 123,
332
+ # "site_ids": [1234],
333
+ # "category_ids": [123456]
334
+ # }
335
+ # Returns: hash of created self-classification rule
336
+ def rule_create(body)
337
+ body = { partner_id: @partner_id }.merge(body)
338
+ request('POST', '/Services/WS/classificationRules', {}, body)
339
+ end
340
+
341
+ # Public: Update a self-classification rule
342
+ #
343
+ # rule_id:integer (MANDATORY) - id of classification rule to be updated
344
+ #
345
+ # for other parameters refer to documentation of rule_create(body)
346
+ #
347
+ # Returns: hash of updated self-classification rule
348
+ def rule_update(rule_id, body)
349
+ body = { partner_id: @partner_id }.merge(body)
350
+ request('PUT', "/Services/WS/classificationRules/#{rule_id}", {}, body)
351
+ end
352
+
353
+ private
354
+
355
+ def request(method, path, query, body = nil)
356
+ method.upcase!
357
+ signature = sign(method, path, query_values(query), body_sorted(body))
358
+
359
+ url = "https://#{domain}#{path}?#{query_url_formatted(query)}\
360
+ bkuid=#{api_user_key}&bksig=#{signature}"
361
+
362
+ response = case method
363
+ when 'GET'
364
+ HTTParty.get(url)
365
+ when 'POST'
366
+ HTTParty.post(url, body: body_sorted(body), headers: json_headers)
367
+ when 'PUT'
368
+ HTTParty.put(url, body: body_sorted(body), headers: json_headers)
369
+ else
370
+ fail ArgumentError, "request method '#{method}' not supported"
371
+ end.response
372
+ fail "HTTP Request Error: #{response.body}" if response.code != '200'
373
+ return response.code if response.body == '' || response.body.nil?
374
+ JSON.parse(response.body, symbolize_names: true)
375
+ end
376
+
377
+ def body_sorted(body)
378
+ # sort hash according to keys for correct signature computation
379
+ return body.sort_by { |key, _value| key.to_s }.to_h.to_json if body
380
+ end
381
+
382
+ def json_headers
383
+ {
384
+ 'Accept' => 'application/json',
385
+ 'Content-type' => 'application/json'
386
+ }
387
+ end
388
+
389
+ def query_values(query)
390
+ return nil unless query
391
+ query.values.join
392
+ end
393
+
394
+ def query_url_formatted(query)
395
+ return nil unless query
396
+ query.map { |k, v| "#{k}=#{v}" }.join('&') + '&'
397
+ end
398
+
399
+ # HMAC-SHA256(Secret key, HTTP_METHOD + URI_PATH + QUERY_ARG_VALUES + POST_DATA)
400
+ def sign(method, path, query, body)
401
+ string_to_sign = method + path + query.to_s + body.to_s
402
+ digest = OpenSSL::Digest.new('sha256')
403
+ hmac = OpenSSL::HMAC.digest(digest, @api_private_key, string_to_sign)
404
+ CGI.escape(Base64.strict_encode64(hmac))
405
+ end
406
+ end
407
+ end