adcloud 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.document +5 -0
  2. data/.gitignore +69 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +50 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +221 -0
  8. data/RELEASE_NOTES.md +14 -0
  9. data/Rakefile +8 -0
  10. data/adcloud.gemspec +27 -0
  11. data/lib/adcloud.rb +62 -0
  12. data/lib/adcloud/advertisement.rb +28 -0
  13. data/lib/adcloud/api_error.rb +33 -0
  14. data/lib/adcloud/authentication.rb +24 -0
  15. data/lib/adcloud/campaign.rb +69 -0
  16. data/lib/adcloud/connection.rb +46 -0
  17. data/lib/adcloud/customer.rb +9 -0
  18. data/lib/adcloud/entity.rb +84 -0
  19. data/lib/adcloud/exception_raiser.rb +22 -0
  20. data/lib/adcloud/media_file.rb +10 -0
  21. data/lib/adcloud/product.rb +10 -0
  22. data/lib/adcloud/report.rb +28 -0
  23. data/lib/adcloud/report_entry.rb +48 -0
  24. data/lib/adcloud/response_error_handler.rb +23 -0
  25. data/lib/adcloud/topic.rb +19 -0
  26. data/lib/adcloud/topic_discount.rb +16 -0
  27. data/lib/adcloud/version.rb +3 -0
  28. data/lib/adcloud/webhook.rb +35 -0
  29. data/lib/adcloud/webhook_config.rb +18 -0
  30. data/lib/adcloud/webhook_event.rb +18 -0
  31. data/test/adcloud/advertisement_test.rb +11 -0
  32. data/test/adcloud/authentication_test.rb +58 -0
  33. data/test/adcloud/campaign_test.rb +54 -0
  34. data/test/adcloud/connection_test.rb +78 -0
  35. data/test/adcloud/customer_test.rb +6 -0
  36. data/test/adcloud/entity_test.rb +159 -0
  37. data/test/adcloud/media_file_test.rb +7 -0
  38. data/test/adcloud/product_test.rb +5 -0
  39. data/test/adcloud/report_test.rb +34 -0
  40. data/test/adcloud/topic_test.rb +14 -0
  41. data/test/adcloud/webhook_event_test.rb +19 -0
  42. data/test/adcloud/webhook_test.rb +62 -0
  43. data/test/adcloud_test.rb +47 -0
  44. data/test/test_helper.rb +23 -0
  45. 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,9 @@
1
+ module Adcloud
2
+ class Customer < Adcloud::Entity
3
+ attribute :_meta, Hash
4
+ attribute :id, Integer
5
+ attribute :name, Boolean
6
+ attribute :modified, DateTime
7
+ attribute :created, DateTime
8
+ end
9
+ 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,10 @@
1
+ module Adcloud
2
+ class MediaFile < Adcloud::Entity
3
+ attribute :id, Integer
4
+ attribute :uploaded_file, String
5
+ attribute :product_id, Integer
6
+ attribute :ad_id, Integer
7
+ attribute :flash, Boolean
8
+ attribute :display, Boolean
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Adcloud
2
+ class Product < Adcloud::Entity
3
+ attribute :id, Integer
4
+ attribute :name, String
5
+ attribute :customer_id, Integer
6
+ attribute :default_prio, Integer
7
+ attribute :modified, DateTime
8
+ attribute :created, DateTime
9
+ end
10
+ 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