adcloud 0.7.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.
- data/.document +5 -0
- data/.gitignore +69 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +20 -0
- data/README.md +221 -0
- data/RELEASE_NOTES.md +14 -0
- data/Rakefile +8 -0
- data/adcloud.gemspec +27 -0
- data/lib/adcloud.rb +62 -0
- data/lib/adcloud/advertisement.rb +28 -0
- data/lib/adcloud/api_error.rb +33 -0
- data/lib/adcloud/authentication.rb +24 -0
- data/lib/adcloud/campaign.rb +69 -0
- data/lib/adcloud/connection.rb +46 -0
- data/lib/adcloud/customer.rb +9 -0
- data/lib/adcloud/entity.rb +84 -0
- data/lib/adcloud/exception_raiser.rb +22 -0
- data/lib/adcloud/media_file.rb +10 -0
- data/lib/adcloud/product.rb +10 -0
- data/lib/adcloud/report.rb +28 -0
- data/lib/adcloud/report_entry.rb +48 -0
- data/lib/adcloud/response_error_handler.rb +23 -0
- data/lib/adcloud/topic.rb +19 -0
- data/lib/adcloud/topic_discount.rb +16 -0
- data/lib/adcloud/version.rb +3 -0
- data/lib/adcloud/webhook.rb +35 -0
- data/lib/adcloud/webhook_config.rb +18 -0
- data/lib/adcloud/webhook_event.rb +18 -0
- data/test/adcloud/advertisement_test.rb +11 -0
- data/test/adcloud/authentication_test.rb +58 -0
- data/test/adcloud/campaign_test.rb +54 -0
- data/test/adcloud/connection_test.rb +78 -0
- data/test/adcloud/customer_test.rb +6 -0
- data/test/adcloud/entity_test.rb +159 -0
- data/test/adcloud/media_file_test.rb +7 -0
- data/test/adcloud/product_test.rb +5 -0
- data/test/adcloud/report_test.rb +34 -0
- data/test/adcloud/topic_test.rb +14 -0
- data/test/adcloud/webhook_event_test.rb +19 -0
- data/test/adcloud/webhook_test.rb +62 -0
- data/test/adcloud_test.rb +47 -0
- data/test/test_helper.rb +23 -0
- metadata +251 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class Advertisement < Adcloud::Entity
|
3
|
+
attribute :id, Integer
|
4
|
+
attribute :product_id, Integer
|
5
|
+
attribute :type, String
|
6
|
+
attribute :advertisement_design_id, Integer
|
7
|
+
attribute :target_url, String
|
8
|
+
attribute :name, String
|
9
|
+
attribute :logo, Boolean
|
10
|
+
attribute :image_text, Boolean
|
11
|
+
attribute :flash, Boolean
|
12
|
+
attribute :display, Boolean
|
13
|
+
attribute :tag, Boolean
|
14
|
+
attribute :postview_type, Integer
|
15
|
+
attribute :tag_html, String
|
16
|
+
attribute :image_alt, String
|
17
|
+
attribute :text_headline, String
|
18
|
+
attribute :text_body, String
|
19
|
+
attribute :text_link, String
|
20
|
+
attribute :postview_url, String
|
21
|
+
attribute :keywords, Array
|
22
|
+
attribute :exclusion_keywords, Array
|
23
|
+
attribute :machine_keywords, Array
|
24
|
+
attribute :locations, Array
|
25
|
+
attribute :modified, DateTime
|
26
|
+
attribute :created, DateTime
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Adcloud
|
2
|
+
|
3
|
+
class ApiError < StandardError;
|
4
|
+
|
5
|
+
attr_accessor :meta, :response
|
6
|
+
|
7
|
+
def initialize(response)
|
8
|
+
self.response = response
|
9
|
+
end
|
10
|
+
|
11
|
+
def meta
|
12
|
+
self.response.body["_meta"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def details
|
16
|
+
self.meta["details"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
self.meta["type"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def message
|
24
|
+
self.meta["message"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def status
|
28
|
+
self.meta["status"]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Adcloud
|
2
|
+
|
3
|
+
class AuthenticationError < StandardError ; end
|
4
|
+
|
5
|
+
class Authentication
|
6
|
+
|
7
|
+
attr_accessor :client_id, :client_secret, :token
|
8
|
+
|
9
|
+
def initialize(attr)
|
10
|
+
@client_id = attr[:client_id]
|
11
|
+
@client_secret = attr[:client_secret]
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate!
|
15
|
+
response = Connection.new.connection(false).post "oauth/access_token", {:client_id => self.client_id, :client_secret => self.client_secret, :grant_type => "none"}
|
16
|
+
if response.success?
|
17
|
+
@token = response.body['_meta']["access_token"]
|
18
|
+
else
|
19
|
+
raise AuthenticationError.new(@client_id => "Could not authenticate")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class Campaign < Adcloud::Entity
|
3
|
+
TYPES = {
|
4
|
+
cpc: 2,
|
5
|
+
cpx: 3,
|
6
|
+
cpx_plus: 4,
|
7
|
+
cpm: 5,
|
8
|
+
fixed_daily_costs: 6
|
9
|
+
}
|
10
|
+
|
11
|
+
DELIVERY_TYPES = {
|
12
|
+
topic: 1,
|
13
|
+
channel: 2
|
14
|
+
}
|
15
|
+
|
16
|
+
attribute :_meta, Hash
|
17
|
+
attribute :id, Integer
|
18
|
+
attribute :bidding_enabled, Boolean
|
19
|
+
attribute :name, String
|
20
|
+
attribute :customer_id, Integer
|
21
|
+
attribute :company_id, Integer
|
22
|
+
attribute :company_name, String
|
23
|
+
attribute :product_id, Integer
|
24
|
+
attribute :product_name, String
|
25
|
+
attribute :status, Integer
|
26
|
+
attribute :language_id, Integer
|
27
|
+
attribute :start_date, Date
|
28
|
+
attribute :end_date, Date
|
29
|
+
attribute :delivery_boost, Float
|
30
|
+
attribute :frequency_capping, Integer
|
31
|
+
attribute :frequency_capping_days, Integer
|
32
|
+
attribute :cookie_lifetime, Integer
|
33
|
+
attribute :cookie_lifetime_view, Integer
|
34
|
+
attribute :fallback, Boolean
|
35
|
+
attribute :keywords, String
|
36
|
+
attribute :exclusion_keywords, Array
|
37
|
+
attribute :keyword_lifetime, Integer
|
38
|
+
attribute :comment, String
|
39
|
+
attribute :budget_limit, Float
|
40
|
+
attribute :budget_limit_allowed, Boolean
|
41
|
+
attribute :unit_price_maximum, Float
|
42
|
+
attribute :locations, Array
|
43
|
+
attribute :type, Integer
|
44
|
+
attribute :delivery_external, Boolean
|
45
|
+
attribute :delivery_internal, Boolean
|
46
|
+
attribute :delivery_type, Integer
|
47
|
+
attribute :deactivated_on, DateTime
|
48
|
+
attribute :modified, DateTime
|
49
|
+
attribute :created, DateTime
|
50
|
+
# attribute :fixed_price, # missing
|
51
|
+
# attribute :mobile_targeting, # missing
|
52
|
+
|
53
|
+
# @return [void] Validate the campaign against the api
|
54
|
+
def validate
|
55
|
+
result = connection.get('campaigns/validate', { campaign: self.attributes_for_create })
|
56
|
+
if result && result["_meta"] && result["_meta"]["status"] == 226
|
57
|
+
@errors = self.errors.merge(result["_meta"]["details"])
|
58
|
+
else
|
59
|
+
raise AdcloudSucks::InvalidApiResponse.new('Empty response for campaign validation')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean] True when campaign is valid - otherwise false
|
64
|
+
def valid?
|
65
|
+
self.validate
|
66
|
+
self.errors.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Adcloud
|
2
|
+
|
3
|
+
class Connection < Adcloud::Entity
|
4
|
+
|
5
|
+
attr_accessor :authentication
|
6
|
+
|
7
|
+
def authentication
|
8
|
+
@authentication ||= begin
|
9
|
+
auth = Adcloud::Authentication.new(:client_id => Adcloud.config.client_id, :client_secret => Adcloud.config.client_secret)
|
10
|
+
auth.authenticate!
|
11
|
+
auth
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def url
|
16
|
+
"#{Adcloud.config.protocol}://#{Adcloud.config.host}:#{Adcloud.config.port}/#{Adcloud.config.api_version}/"
|
17
|
+
end
|
18
|
+
|
19
|
+
def authentication_token
|
20
|
+
self.authentication.token
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection(auth = true)
|
24
|
+
auth_header = auth && { :Authorization => "Bearer #{authentication_token}" } || {}
|
25
|
+
connection ||= Faraday.new(:url => url, :headers => {}.merge(auth_header)) do |faraday|
|
26
|
+
faraday.request :url_encoded # form-encode POST params
|
27
|
+
# log requests to STDOUT
|
28
|
+
faraday.response :logger if Adcloud.config.debug
|
29
|
+
faraday.use ResponseErrorHandler
|
30
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
31
|
+
faraday.response :json, :content_type => /\bjson$/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def post(path, params = {})
|
36
|
+
response = connection.post path, params
|
37
|
+
response.body
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(path, params = {})
|
41
|
+
response = connection.get path, params
|
42
|
+
response.body
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Adcloud
|
2
|
+
|
3
|
+
class Entity
|
4
|
+
include Virtus
|
5
|
+
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
attribute :_meta, Hash
|
9
|
+
|
10
|
+
def connection
|
11
|
+
self.class.connection
|
12
|
+
end
|
13
|
+
|
14
|
+
def meta
|
15
|
+
self._meta
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Boolean] True when successfully created - otherwise false
|
19
|
+
def create
|
20
|
+
result = connection.post(self.class.api_endpoint, { self.class.api_name => attributes_for_create })
|
21
|
+
if self.respond_to?(:id=) && !result['id'].nil?
|
22
|
+
self.id = result['id']
|
23
|
+
end
|
24
|
+
true
|
25
|
+
rescue Adcloud::BadRequestError => ex
|
26
|
+
derive_errors_from_error(ex)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
attr_accessor :api_endpoint, :connection
|
32
|
+
|
33
|
+
def api_endpoint
|
34
|
+
@api_endpoint ||= self.name.demodulize.tableize
|
35
|
+
end
|
36
|
+
|
37
|
+
def api_name
|
38
|
+
self.name.demodulize.underscore
|
39
|
+
end
|
40
|
+
|
41
|
+
def connection
|
42
|
+
@connection ||= Connection.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array] Entities matching the criteria or all
|
46
|
+
def all(filter = {}, page = 1, per_page = 50)
|
47
|
+
result = connection.get(self.api_endpoint, :filter => filter, :page => page, :per_page => per_page)
|
48
|
+
result["items"].map { |raw_campaign| self.new(raw_campaign) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Object] The entity with the unique identifier
|
52
|
+
def find(id)
|
53
|
+
result = connection.get("#{self.api_endpoint}/#{id}")
|
54
|
+
self.new(result)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Enitity] Object has errors when creation failed
|
58
|
+
def create(params = {})
|
59
|
+
entity = self.new(params)
|
60
|
+
entity.create
|
61
|
+
entity
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Hash] Errors hash
|
66
|
+
def errors
|
67
|
+
@errors || {}
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# Set the campaign errors from the api response
|
73
|
+
def derive_errors_from_error(error)
|
74
|
+
@errors = self.errors.merge(error.details)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Hash] Attributes without those required for campaign creation
|
78
|
+
def attributes_for_create
|
79
|
+
self.attributes.reject { |i| [:id, :_meta].include?(i) }
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Adcloud
|
2
|
+
|
3
|
+
class ExceptionRaiser
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
case response.status
|
7
|
+
when 400
|
8
|
+
raise Adcloud::BadRequestError.new(response)
|
9
|
+
when 401
|
10
|
+
raise Adcloud::Unauthorized.new(response)
|
11
|
+
when 404
|
12
|
+
raise Adcloud::NotFoundError.new(response)
|
13
|
+
when 500
|
14
|
+
raise Adcloud::ServerError.new(response)
|
15
|
+
else
|
16
|
+
raise StandardError.new("Could not handle status #{response.status}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class Report
|
3
|
+
include Virtus
|
4
|
+
|
5
|
+
attribute :_meta, Hash
|
6
|
+
attribute :items, Array[Adcloud::ReportEntry]
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :api_endpoint, :connection
|
10
|
+
|
11
|
+
def find_by_date(date)
|
12
|
+
result = connection.get(self.api_endpoint, { filter: { date: date.to_s } })
|
13
|
+
return self.new(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
def api_endpoint
|
17
|
+
@api_endpoint ||= self.name.demodulize.tableize
|
18
|
+
end
|
19
|
+
|
20
|
+
def connection
|
21
|
+
@connection ||= Connection.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define this after class methods are added
|
26
|
+
self.api_endpoint = 'reports/advertiser'
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class ReportEntry
|
3
|
+
include Virtus
|
4
|
+
|
5
|
+
attribute :date, Date
|
6
|
+
attribute :website_id, Integer
|
7
|
+
attribute :topic_id, Integer
|
8
|
+
attribute :ad_placement_id, Integer
|
9
|
+
attribute :product_id, Integer
|
10
|
+
attribute :ad_id, Integer
|
11
|
+
attribute :booking_id, Integer
|
12
|
+
attribute :page_type, Integer
|
13
|
+
attribute :ad_position, Integer
|
14
|
+
attribute :ad_count, Integer
|
15
|
+
attribute :ad_impressions, Integer
|
16
|
+
attribute :clicks, Integer
|
17
|
+
attribute :clicks_overdelivered, Integer
|
18
|
+
attribute :clicks_filtered, Integer
|
19
|
+
attribute :clicks_image, Integer
|
20
|
+
attribute :clicks_head, Integer
|
21
|
+
attribute :clicks_txt, Integer
|
22
|
+
attribute :clicks_link, Integer
|
23
|
+
attribute :postview_conversions, Integer
|
24
|
+
attribute :postview_conversions_overdelivered, Integer
|
25
|
+
attribute :postview_conversions_filtered, Integer
|
26
|
+
attribute :cancellations, Integer
|
27
|
+
attribute :publisher, String
|
28
|
+
attribute :website, String
|
29
|
+
attribute :position, String
|
30
|
+
attribute :design_id, Integer
|
31
|
+
attribute :design, String
|
32
|
+
attribute :topic, String
|
33
|
+
attribute :ad, String
|
34
|
+
attribute :booking, String
|
35
|
+
attribute :product, String
|
36
|
+
attribute :ad_impressions_costs, Float
|
37
|
+
attribute :clicks_costs, Float
|
38
|
+
attribute :postclick_conversions, Integer
|
39
|
+
attribute :postclick_conversions_overdelivered, Integer
|
40
|
+
attribute :postclick_conversions_filtered, Integer
|
41
|
+
attribute :postclick_conversions_costs, Float
|
42
|
+
attribute :postview_conversions_costs, Float
|
43
|
+
attribute :costs, Float
|
44
|
+
attribute :language, String
|
45
|
+
attribute :country, String
|
46
|
+
attribute :currency, String
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Adcloud
|
4
|
+
|
5
|
+
class ResponseErrorHandler < Faraday::Response::Middleware
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
@app.call(env).on_complete do
|
9
|
+
response = env[:response]
|
10
|
+
# Todo comment in when ready
|
11
|
+
# raise AdcloudSucks::InvalidApiResponse unless response.body.has_key?("_meta")
|
12
|
+
if response.success?
|
13
|
+
response.body
|
14
|
+
else
|
15
|
+
Adcloud.logger.debug { response.inspect }
|
16
|
+
ExceptionRaiser.new(response)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|