adcloud 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|