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.
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