eventbrite_sdk 3.0.2

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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +136 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +16 -0
  10. data/bin/setup +6 -0
  11. data/eventbrite_sdk.gemspec +28 -0
  12. data/lib/eventbrite_sdk.rb +135 -0
  13. data/lib/eventbrite_sdk/attendee.rb +26 -0
  14. data/lib/eventbrite_sdk/category.rb +14 -0
  15. data/lib/eventbrite_sdk/event.rb +75 -0
  16. data/lib/eventbrite_sdk/exceptions.rb +42 -0
  17. data/lib/eventbrite_sdk/lists/owned_event_orders_list.rb +27 -0
  18. data/lib/eventbrite_sdk/media.rb +77 -0
  19. data/lib/eventbrite_sdk/order.rb +25 -0
  20. data/lib/eventbrite_sdk/organizer.rb +20 -0
  21. data/lib/eventbrite_sdk/report.rb +49 -0
  22. data/lib/eventbrite_sdk/resource.rb +99 -0
  23. data/lib/eventbrite_sdk/resource/attributes.rb +158 -0
  24. data/lib/eventbrite_sdk/resource/null_schema_definition.rb +13 -0
  25. data/lib/eventbrite_sdk/resource/operations/attribute_schema.rb +57 -0
  26. data/lib/eventbrite_sdk/resource/operations/endpoint.rb +101 -0
  27. data/lib/eventbrite_sdk/resource/operations/list.rb +15 -0
  28. data/lib/eventbrite_sdk/resource/operations/relationships.rb +120 -0
  29. data/lib/eventbrite_sdk/resource/schema_definition.rb +50 -0
  30. data/lib/eventbrite_sdk/resource_list.rb +86 -0
  31. data/lib/eventbrite_sdk/subcategory.rb +12 -0
  32. data/lib/eventbrite_sdk/ticket_class.rb +60 -0
  33. data/lib/eventbrite_sdk/user.rb +28 -0
  34. data/lib/eventbrite_sdk/venue.rb +22 -0
  35. data/lib/eventbrite_sdk/version.rb +5 -0
  36. data/lib/eventbrite_sdk/webhook.rb +11 -0
  37. metadata +167 -0
@@ -0,0 +1,42 @@
1
+ module EventbriteSDK
2
+ class EventbriteAPIError < RuntimeError
3
+ attr_reader :message, :response
4
+
5
+ def initialize(msg = '', response = :none)
6
+ @message = msg
7
+ @response = response
8
+ end
9
+
10
+ # Returns a hash with AT LEAST 'error_description'.
11
+ # When an error is raised manually there will be no response!
12
+ # This is handled by using the specified message as the error_description.
13
+ def parsed_error
14
+ default = %({"error_description": "#{message}"})
15
+ value = response_value(:body, fallback: default)
16
+
17
+ JSON.parse(value)
18
+ end
19
+
20
+ # Returns the status code of the response, or :none if there is no response.
21
+ def status_code
22
+ response_value(:code)
23
+ end
24
+
25
+ private
26
+
27
+ def response_value(key, fallback: :none)
28
+ if response.respond_to?(key)
29
+ response.send(key)
30
+ else
31
+ fallback
32
+ end
33
+ end
34
+ end
35
+
36
+ class BadRequest < EventbriteAPIError; end
37
+ class Forbidden < EventbriteAPIError; end
38
+ class InvalidAttribute < EventbriteAPIError; end
39
+ class InternalServerError < EventbriteAPIError; end
40
+ class ResourceNotFound < EventbriteAPIError; end
41
+ class Unauthorized < EventbriteAPIError; end
42
+ end
@@ -0,0 +1,27 @@
1
+ module EventbriteSDK
2
+ module Lists
3
+ class OwnedEventOrdersList < ResourceList
4
+ def search(term)
5
+ send(:"term_provided_#{!!term}", term)
6
+
7
+ @pagination = { 'page_count' => 1, 'page_number' => 1 }
8
+
9
+ self
10
+ end
11
+
12
+ private
13
+
14
+ # Swaps the endpoint out to allow searches
15
+ def term_provided_true(term)
16
+ @url_base.sub!('owned_event_orders', 'search_owned_event_orders')
17
+ @query[:search_term] = term
18
+ end
19
+
20
+ # Swaps the endpoint out owned events, and removes the search_term query
21
+ def term_provided_false(_term)
22
+ @url_base.sub!('search_owned_event_orders', 'owned_event_orders')
23
+ @query.delete(:search_term)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,77 @@
1
+ module EventbriteSDK
2
+ # This module implements media upload to Eventbrite based on:
3
+ # https://docs.evbhome.com/apidocs/reference/uploads/?highlight=logo
4
+
5
+ class Media < Resource
6
+ resource_path 'media/:id'
7
+
8
+ attr_reader :image_type, :file
9
+
10
+ VALID_TYPES = {
11
+ event_logo: 'image-event-logo',
12
+ organizer_logo: 'image-organizer-logo',
13
+ user_photo: 'image-user-photo',
14
+ event_view_from_seat: 'image-event-view-from-seat',
15
+ }.freeze
16
+
17
+ schema_definition do
18
+ string 'crop_mask'
19
+ string 'original'
20
+ string 'id'
21
+ string 'url'
22
+ string 'aspect_ratio'
23
+ string 'edge_color'
24
+ string 'edge_color_set'
25
+ end
26
+
27
+ def upload!(image_type, file)
28
+ # Media uploads through the API involve a multiple step process:
29
+
30
+ # 1. Retrieve upload instructions + an upload token from the API
31
+ instructions = get_instructions(image_type)
32
+
33
+ # 2. Upload the file to the endpoint specified in the upload instructions
34
+ eventbrite_upload(file, instructions)
35
+
36
+ # 3. When the upload has finished, notify the API by re-sending the
37
+ # upload token from step 1
38
+ notify(instructions['upload_token'])
39
+
40
+ true
41
+ end
42
+
43
+ private
44
+
45
+ def get_instructions(image_type, request = EventbriteSDK)
46
+ type = VALID_TYPES[image_type]
47
+
48
+ unless type
49
+ raise ArgumentError.new(
50
+ "image_type needs to be one of #{VALID_TYPES.keys}"
51
+ )
52
+ end
53
+
54
+ request.get(url: path('upload'), query: { type: type })
55
+ end
56
+
57
+ def eventbrite_upload(file, instructions)
58
+ RestClient.post(
59
+ instructions['upload_url'],
60
+ instructions['upload_data'].merge(file: file),
61
+ multipart: true,
62
+ )
63
+ end
64
+
65
+ def notify(upload_token, request = EventbriteSDK)
66
+ response = request.post(
67
+ url: path('upload'), payload: { upload_token: upload_token }
68
+ )
69
+
70
+ if payload['crop_mask']
71
+ response = request.post(url: path(response['id']), payload: payload)
72
+ end
73
+
74
+ reload(response)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ module EventbriteSDK
2
+ class Order < Resource
3
+ resource_path 'orders/:id'
4
+
5
+ # Defines order#resend_confirmation_email and order#refund
6
+ #
7
+ # When an event has an id the POST is made, otherwise we return false
8
+ # POSTS to order/:id/(resend_confirmation_email|refunds)
9
+ define_api_actions :resend_confirmation_email, { refund: :refunds }
10
+
11
+ has_many :attendees, object_class: 'Attendee'
12
+ belongs_to :event, object_class: 'Event'
13
+
14
+ schema_definition do
15
+ string 'name'
16
+ string 'first_name'
17
+ string 'last_name'
18
+ string 'email'
19
+ string 'costs'
20
+ datetime 'created', read_only: true
21
+ datetime 'changed', read_only: true
22
+ string 'resource_uri', read_only: true
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ module EventbriteSDK
2
+ class Organizer < Resource
3
+ resource_path 'organizers/:id'
4
+
5
+ attributes_prefix 'organizer'
6
+
7
+ has_many :events, object_class: 'Event'
8
+
9
+ schema_definition do
10
+ string 'name'
11
+ string 'description.html'
12
+ string 'long_description.html'
13
+ string 'logo.id'
14
+ string 'website'
15
+ string 'twitter'
16
+ string 'facebook'
17
+ string 'instagram'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ module EventbriteSDK
2
+ class Report
3
+ STRING_KEYS = %i(
4
+ start_date
5
+ end_date
6
+ date_facet
7
+ event_status
8
+ timezone
9
+ group_by
10
+ ).freeze
11
+
12
+ VALID_REPORTS = %i(attendees sales).freeze
13
+
14
+ def initialize
15
+ @query = {}
16
+ end
17
+
18
+ def event_ids(*ids)
19
+ @query[:event_ids] = ids.join(',')
20
+
21
+ self
22
+ end
23
+
24
+ def filter_by(filters)
25
+ @query[:filter_by] = filters.to_json
26
+
27
+ self
28
+ end
29
+
30
+ STRING_KEYS.each do |method|
31
+ define_method(method) do |value|
32
+ @query[method] = value
33
+ self
34
+ end
35
+ end
36
+
37
+ def query
38
+ @query.dup # Don't allow mutation
39
+ end
40
+
41
+ def retrieve(type = nil, sdk = EventbriteSDK)
42
+ unless VALID_REPORTS.include?(type)
43
+ raise ArgumentError, "`:type` is not of #{VALID_REPORTS}"
44
+ end
45
+
46
+ sdk.get(url: "reports/#{type}", query: query)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ module EventbriteSDK
2
+ class Resource
3
+ include Operations::AttributeSchema
4
+ include Operations::Endpoint
5
+ include Operations::Relationships
6
+
7
+ def self.build(attrs)
8
+ new.tap do |instance|
9
+ instance.assign_attributes(attrs)
10
+ end
11
+ end
12
+
13
+ # Allows compile time definition of POST methods
14
+ #
15
+ # Example:
16
+ # class Event < Resource
17
+ # define_api_actions :publish, :unpublish
18
+ # end
19
+ #
20
+ # would defined instance methods like so:
21
+ #
22
+ # def publish
23
+ # !new? && EventbriteSDK.post(url: path('publish'))
24
+ # end
25
+ #
26
+ # def publish
27
+ # !new? && EventbriteSDK.post(url: path('unpublish'))
28
+ # end
29
+ def self.define_api_actions(*actions)
30
+ req = ->(inst, postfix) do
31
+ inst.instance_eval { !new? && EventbriteSDK.post(url: path(postfix)) }
32
+ end
33
+
34
+ actions.each do |action|
35
+ if action.is_a?(Hash)
36
+ method_name, postfix_path = action.flatten
37
+ else
38
+ method_name = postfix_path = action
39
+ end
40
+
41
+ define_method(method_name) { req.call(self, postfix_path) }
42
+ end
43
+ end
44
+
45
+ def initialize(hydrated_attrs = {})
46
+ reload hydrated_attrs
47
+ end
48
+
49
+ def new?
50
+ !id
51
+ end
52
+
53
+ def refresh!(request = EventbriteSDK)
54
+ unless new?
55
+ reload request.get(url: path)
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ def inspect
62
+ "#<#{self.class}: #{JSON.pretty_generate(@attrs.to_h)}>"
63
+ end
64
+
65
+ def save(postfixed_path = '', request = EventbriteSDK)
66
+ if changed? || !postfixed_path.empty?
67
+ response = request.post(url: path(postfixed_path),
68
+ payload: attrs.payload(self.class.prefix))
69
+
70
+ reload(response)
71
+
72
+ true
73
+ end
74
+ end
75
+
76
+ def to_json(opts = {})
77
+ attrs.to_json(opts)
78
+ end
79
+
80
+ def delete(request = EventbriteSDK)
81
+ response = request.delete(url: path)
82
+ response['deleted']
83
+ end
84
+
85
+ def read_attribute_for_serialization(attribute)
86
+ attrs[attribute]
87
+ end
88
+
89
+ private
90
+
91
+ def resource_class_from_string(klass)
92
+ EventbriteSDK.const_get(klass)
93
+ end
94
+
95
+ def reload(hydrated_attrs = {})
96
+ build_attrs(hydrated_attrs)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,158 @@
1
+ module EventbriteSDK
2
+ class Resource
3
+ class Attributes
4
+ attr_reader :attrs, :changes
5
+
6
+ def self.build(attrs, schema)
7
+ new({}, schema).tap do |instance|
8
+ instance.assign_attributes(attrs)
9
+ end
10
+ end
11
+
12
+ def initialize(hydrated_attrs = {}, schema = NullSchemaDefinition.new)
13
+ @attrs = {}
14
+ @changes = {}
15
+ @schema = schema
16
+
17
+ # Build out initial hash based on schema's defined keys
18
+ schema.defined_keys.each { |key| bury_attribute(key, nil) }
19
+
20
+ @attrs = attrs.merge(stringify_keys(hydrated_attrs))
21
+ end
22
+
23
+ def [](key)
24
+ public_send(key)
25
+ end
26
+
27
+ def assign_attributes(new_attrs)
28
+ stringify_keys(new_attrs).each do |attribute_key, value|
29
+ assign_value(attribute_key, value) if schema.writeable?(attribute_key)
30
+ end
31
+
32
+ nil
33
+ end
34
+
35
+ def changed?
36
+ changes.any?
37
+ end
38
+
39
+ def to_h
40
+ attrs.to_h
41
+ end
42
+
43
+ def to_json(opts = {})
44
+ to_h.to_json(opts)
45
+ end
46
+
47
+ def inspect
48
+ "#<#{self.class}: #{JSON.pretty_generate(@attrs.to_h)}>"
49
+ end
50
+
51
+ def reset!
52
+ changes.each do |attribute_key, (old_value, _current_value)|
53
+ bury_attribute(attribute_key, old_value)
54
+ end
55
+
56
+ @changes = {}
57
+
58
+ true
59
+ end
60
+
61
+ # Provides changeset in a format that can be thrown at an endpoint
62
+ #
63
+ # prefix: This is needed due to inconsistencies in the EB API
64
+ # Sometimes there's a prefix, sometimes there's not,
65
+ # sometimes it's singular, sometimes it's plural.
66
+ # Once the API gets a bit more nomalized we can remove this
67
+ # alltogether and infer a prefix based
68
+ # on the class name of the resource
69
+ def payload(prefix = nil)
70
+ changes.each_with_object({}) do |(attribute_key, (_, value)), payload|
71
+ key = if prefix
72
+ "#{prefix}.#{attribute_key}"
73
+ else
74
+ attribute_key
75
+ end
76
+
77
+ bury(key, value, payload)
78
+ end
79
+ end
80
+
81
+ def values
82
+ attrs.values
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :schema
88
+
89
+ def assign_value(attribute_key, value)
90
+ dirty_check(attribute_key, value)
91
+ add_rich_value(attribute_key)
92
+ bury_attribute(attribute_key, value)
93
+ end
94
+
95
+ def dirty_check(attribute_key, value)
96
+ initial_value = attrs.dig(*attribute_key.split('.'))
97
+
98
+ if initial_value != value
99
+ changes[attribute_key] = [initial_value, value]
100
+ end
101
+ end
102
+
103
+ def bury_attribute(attribute_key, value)
104
+ bury(attribute_key, value, attrs)
105
+ end
106
+
107
+ # Since we use dirty checking to determine what the payload is
108
+ # you can run into a case where a "rich media" field needs other attrs
109
+ # Namely timezone, so if a rich date changed, add the tz with it.
110
+ def add_rich_value(attribute_key)
111
+ if changes[attribute_key] && attribute_key =~ /\A(.+)\.utc\z/
112
+ key_prefix = Regexp.last_match(1)
113
+ tz = attrs.dig(key_prefix, 'timezone')
114
+
115
+ changes["#{key_prefix}.timezone"] = [tz, tz]
116
+ end
117
+ end
118
+
119
+ def bury(attribute_key, value, hash = {})
120
+ keys = attribute_key.split '.'
121
+
122
+ # Hand rolling #bury
123
+ # hopefully we get it in the next release of Ruby
124
+ keys.each_cons(2).reduce(hash) do |prev_attrs, (key, _)|
125
+ prev_attrs[key] ||= {}
126
+ end[keys.last] = value
127
+
128
+ hash
129
+ end
130
+
131
+ def method_missing(method_name, *_args, &_block)
132
+ requested_key = method_name.to_s
133
+
134
+ if attrs.has_key?(requested_key)
135
+ handle_requested_attr(attrs[requested_key])
136
+ else
137
+ super
138
+ end
139
+ end
140
+
141
+ def respond_to_missing?(method_name, _include_private = false)
142
+ attrs.has_key?(method_name.to_s) || super
143
+ end
144
+
145
+ def handle_requested_attr(value)
146
+ if value.is_a?(Hash)
147
+ self.class.new(value)
148
+ else
149
+ value
150
+ end
151
+ end
152
+
153
+ def stringify_keys(params)
154
+ params.to_h.map { |key, value| [key.to_s, value] }.to_h
155
+ end
156
+ end
157
+ end
158
+ end