cardmagic-etsy 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +13 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE +9 -0
  6. data/README.md +348 -0
  7. data/Rakefile +12 -0
  8. data/etsy.gemspec +34 -0
  9. data/lib/etsy.rb +223 -0
  10. data/lib/etsy/about.rb +15 -0
  11. data/lib/etsy/address.rb +47 -0
  12. data/lib/etsy/attribute_value.rb +46 -0
  13. data/lib/etsy/basic_client.rb +32 -0
  14. data/lib/etsy/category.rb +84 -0
  15. data/lib/etsy/country.rb +27 -0
  16. data/lib/etsy/favorite_listing.rb +26 -0
  17. data/lib/etsy/image.rb +44 -0
  18. data/lib/etsy/listing.rb +296 -0
  19. data/lib/etsy/model.rb +127 -0
  20. data/lib/etsy/payment_template.rb +33 -0
  21. data/lib/etsy/profile.rb +49 -0
  22. data/lib/etsy/receipt.rb +37 -0
  23. data/lib/etsy/request.rb +150 -0
  24. data/lib/etsy/response.rb +128 -0
  25. data/lib/etsy/section.rb +16 -0
  26. data/lib/etsy/secure_client.rb +131 -0
  27. data/lib/etsy/shipping_info.rb +27 -0
  28. data/lib/etsy/shipping_template.rb +41 -0
  29. data/lib/etsy/shop.rb +88 -0
  30. data/lib/etsy/transaction.rb +29 -0
  31. data/lib/etsy/user.rb +109 -0
  32. data/lib/etsy/variation/property_set.rb +71 -0
  33. data/lib/etsy/verification_request.rb +17 -0
  34. data/lib/etsy/version.rb +3 -0
  35. data/test/fixtures/about/getAbout.json +16 -0
  36. data/test/fixtures/address/getUserAddresses.json +12 -0
  37. data/test/fixtures/attribute_value/findAllListingPropertyValues.json +44 -0
  38. data/test/fixtures/category/findAllSubCategoryChildren.json +78 -0
  39. data/test/fixtures/category/findAllTopCategory.json +347 -0
  40. data/test/fixtures/category/findAllTopCategory.single.json +18 -0
  41. data/test/fixtures/category/findAllTopCategoryChildren.json +308 -0
  42. data/test/fixtures/category/getCategory.multiple.json +28 -0
  43. data/test/fixtures/category/getCategory.single.json +18 -0
  44. data/test/fixtures/country/getCountry.json +1 -0
  45. data/test/fixtures/favorite_listing/findAllFavoriteListings.json +1 -0
  46. data/test/fixtures/image/findAllListingImages.json +102 -0
  47. data/test/fixtures/listing/findAllListingActive.category.json +827 -0
  48. data/test/fixtures/listing/findAllShopListings.json +69 -0
  49. data/test/fixtures/listing/getListing.multiple.json +1 -0
  50. data/test/fixtures/listing/getListing.single.includeImages.json +1 -0
  51. data/test/fixtures/listing/getListing.single.json +1 -0
  52. data/test/fixtures/payment_template/getPaymentTemplate.json +1 -0
  53. data/test/fixtures/profile/new.json +28 -0
  54. data/test/fixtures/receipt/findAllShopReceipts.json +28 -0
  55. data/test/fixtures/section/getShopSection.json +18 -0
  56. data/test/fixtures/shipping_info/getShippingInfo.json +1 -0
  57. data/test/fixtures/shipping_template/getShippingTemplate.json +1 -0
  58. data/test/fixtures/shop/findAllShop.json +1 -0
  59. data/test/fixtures/shop/findAllShop.single.json +1 -0
  60. data/test/fixtures/shop/getShop.multiple.json +1 -0
  61. data/test/fixtures/shop/getShop.single.json +34 -0
  62. data/test/fixtures/transaction/findAllShopTransactions.json +1 -0
  63. data/test/fixtures/user/getUser.multiple.json +29 -0
  64. data/test/fixtures/user/getUser.single.json +13 -0
  65. data/test/fixtures/user/getUser.single.private.json +18 -0
  66. data/test/fixtures/user/getUser.single.withProfile.json +38 -0
  67. data/test/fixtures/user/getUser.single.withShops.json +41 -0
  68. data/test/test_helper.rb +45 -0
  69. data/test/unit/etsy/address_test.rb +61 -0
  70. data/test/unit/etsy/attribute_value_test.rb +67 -0
  71. data/test/unit/etsy/basic_client_test.rb +30 -0
  72. data/test/unit/etsy/category_test.rb +106 -0
  73. data/test/unit/etsy/country_test.rb +64 -0
  74. data/test/unit/etsy/favorite_listing_test.rb +44 -0
  75. data/test/unit/etsy/image_test.rb +51 -0
  76. data/test/unit/etsy/listing_test.rb +268 -0
  77. data/test/unit/etsy/model_test.rb +64 -0
  78. data/test/unit/etsy/payment_template_test.rb +68 -0
  79. data/test/unit/etsy/profile_test.rb +111 -0
  80. data/test/unit/etsy/receipt_test.rb +107 -0
  81. data/test/unit/etsy/request_test.rb +190 -0
  82. data/test/unit/etsy/response_test.rb +175 -0
  83. data/test/unit/etsy/section_test.rb +28 -0
  84. data/test/unit/etsy/secure_client_test.rb +132 -0
  85. data/test/unit/etsy/shipping_info_test.rb +24 -0
  86. data/test/unit/etsy/shipping_template_test.rb +24 -0
  87. data/test/unit/etsy/shop_about_test.rb +43 -0
  88. data/test/unit/etsy/shop_test.rb +116 -0
  89. data/test/unit/etsy/transaction_test.rb +61 -0
  90. data/test/unit/etsy/user_test.rb +250 -0
  91. data/test/unit/etsy/verification_request_test.rb +26 -0
  92. data/test/unit/etsy_test.rb +173 -0
  93. metadata +293 -0
@@ -0,0 +1,27 @@
1
+ module Etsy
2
+ class Country
3
+ include Etsy::Model
4
+
5
+ attribute :id, :from => :country_id
6
+ attributes :iso_country_code, :world_bank_country_code
7
+ attributes :name, :slug, :lat, :lon
8
+
9
+ def self.find_all
10
+ get("/countries")
11
+ end
12
+
13
+ def self.find(id)
14
+ get("/countries/#{id}")
15
+ end
16
+
17
+ def self.find_by_alpha2(alpha2)
18
+ alpha2 = alpha2.upcase
19
+ find_all.detect { |country| country.iso_country_code == alpha2}
20
+ end
21
+
22
+ def self.find_by_world_bank_country_code(world_bank_country_code)
23
+ world_bank_country_code = world_bank_country_code.upcase
24
+ find_all.detect { |country| country.world_bank_country_code == world_bank_country_code}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Etsy
2
+ class FavoriteListing
3
+ include Model
4
+
5
+ attributes :user_id, :listing_state, :listing_id, :create_date
6
+
7
+ #Create a new favorite listing
8
+ #
9
+ def self.create(user, listing, options = {})
10
+ options.merge!(:require_secure => true)
11
+ post("/users/#{user.id}/favorites/listings/#{listing.id}", options)
12
+ end
13
+
14
+ #Find all listings favorited by a user
15
+ #
16
+ def self.find_all_user_favorite_listings(user_id, options = {})
17
+ get_all("/users/#{user_id}/favorites/listings", options)
18
+ end
19
+
20
+ #Find a set of favorelistings associated with a listing_id
21
+ #
22
+ def self.find_all_listings_favored_by(listing_id, options = {})
23
+ get_all("/listings/#{listing_id}/favored-by", options)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ module Etsy
2
+
3
+ # = Image
4
+ #
5
+ # Represents an image resource of an Etsy listing and contains multiple sizes.
6
+ # Sizes available are:
7
+ #
8
+ # [square] The square image thumbnail (75x75 pixels)
9
+ # [small] The small image thumbnail (170x135 pixels)
10
+ # [thumbnail] The thumbnail for the image, no more than 570px wide
11
+ # [full] The full image for this listing, no more than 1500px wide
12
+ #
13
+ class Image
14
+
15
+ include Etsy::Model
16
+
17
+ attribute :square, :from => :url_75x75
18
+ attribute :small, :from => :url_170x135
19
+ attribute :thumbnail, :from => :url_570xN
20
+ attribute :height, :from => :full_height
21
+ attribute :width, :from => :full_width
22
+ attribute :full, :from => :url_fullxfull
23
+
24
+ # Fetch all images for a given listing.
25
+ #
26
+ def self.find_all_by_listing_id(listing_id, options = {})
27
+ get_all("/listings/#{listing_id}/images", options)
28
+ end
29
+
30
+ def self.create(listing, image_path, options = {})
31
+ options.merge!(:require_secure => true)
32
+ options[:image] = File.new(image_path)
33
+ options[:multipart] = true
34
+ post("/listings/#{listing.id}/images", options)
35
+ end
36
+
37
+ # Delete image
38
+ #
39
+ def self.destroy(listing, image, options = {})
40
+ options.merge!(:require_secure => true)
41
+ delete("/listings/#{listing.id}/images/#{image.id}", options)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,296 @@
1
+ module Etsy
2
+
3
+ # = Listing
4
+ #
5
+ # Represents a single Etsy listing. Has the following attributes:
6
+ #
7
+ # [id] The unique identifier for this listing
8
+ # [title] The title of this listing
9
+ # [description] This listing's full description
10
+ # [view_count] The number of times this listing has been viewed
11
+ # [url] The full URL to this listing's detail page
12
+ # [price] The price of this listing item
13
+ # [currency] The currency that the seller is using for this listing item
14
+ # [quantity] The number of items available for sale
15
+ # [tags] An array of tags that the seller has used for this listing
16
+ # [materials] Any array of materials that was used in the production of this item
17
+ # [state] The current state of the item
18
+ # [hue] The hue of the listing's primary image (HSV color).
19
+ # [saturation] The saturation of the listing's primary image (HSV color).
20
+ # [brightness] The value of the listing's primary image (HSV color).
21
+ # [black_and_white?] True if the listing's primary image is in black & white.
22
+ #
23
+ # Additionally, the following queries on this item are available:
24
+ #
25
+ # [active?] Is this listing active?
26
+ # [removed?] Has this listing been removed?
27
+ # [sold_out?] Is this listing sold out?
28
+ # [expired?] Has this listing expired?
29
+ # [alchemy?] Is this listing an Alchemy item? (i.e. requested by an Etsy user)
30
+ #
31
+ class Listing
32
+
33
+ include Etsy::Model
34
+
35
+ STATES = %w(active removed sold_out expired alchemy)
36
+ VALID_STATES = [:active, :expired, :inactive, :sold, :featured, :draft, :sold_out]
37
+
38
+ attribute :id, :from => :listing_id
39
+ attribute :view_count, :from => :views
40
+ attribute :created, :from => :creation_tsz
41
+ attribute :modified, :from => :last_modified_tsz
42
+ attribute :currency, :from => :currency_code
43
+ attribute :ending, :from => :ending_tsz
44
+ attribute :shop_section, :from => :shop_section_id
45
+
46
+ attributes :title, :description, :state, :url, :price, :quantity,
47
+ :tags, :materials, :hue, :saturation, :brightness, :is_black_and_white,
48
+ :featured_rank, :occasion, :num_favorers, :user_id,
49
+ :shipping_template_id, :who_made, :when_made,
50
+ :original_creation_tsz, :style, :category_path,
51
+ :taxonomy_id, :taxonomy_attributes
52
+
53
+ association :image, :from => 'Images'
54
+
55
+ def self.create(options = {})
56
+ options.merge!(:require_secure => true)
57
+ post("/listings", options)
58
+ end
59
+
60
+ def self.update(listing, options = {})
61
+ options.merge!(:require_secure => true)
62
+ put("/listings/#{listing.id}", options)
63
+ end
64
+
65
+ def self.destroy(listing, options = {})
66
+ options.merge!(:require_secure => true)
67
+ delete("/listings/#{listing.id}", options)
68
+ end
69
+
70
+ # Retrieve one or more listings by ID:
71
+ #
72
+ # Etsy::Listing.find(123)
73
+ #
74
+ # You can find multiple listings by passing an array of identifiers:
75
+ #
76
+ # Etsy::Listing.find([123, 456])
77
+ #
78
+ def self.find(*identifiers_and_options)
79
+ find_one_or_more('listings', identifiers_and_options)
80
+ end
81
+
82
+ # Retrieve listings for a given shop.
83
+ # By default, pulls back the first 25 active listings.
84
+ # Defaults can be overridden using :limit, :offset, and :state
85
+ #
86
+ # Available states are :active, :expired, :inactive, :sold, and :featured, :draft, :sold_out
87
+ # where :featured is a subset of the others.
88
+ #
89
+ # options = {
90
+ # :state => :expired,
91
+ # :limit => 100,
92
+ # :offset => 100,
93
+ # :token => 'toke',
94
+ # :secret => 'secret'
95
+ # }
96
+ # Etsy::Listing.find_all_by_shop_id(123, options)
97
+ #
98
+ def self.find_all_by_shop_id(shop_id, options = {})
99
+ state = options.delete(:state) || :active
100
+
101
+ raise(ArgumentError, self.invalid_state_message(state)) unless valid?(state)
102
+
103
+ if state == :sold
104
+ sold_listings(shop_id, options)
105
+ else
106
+ get_all("/shops/#{shop_id}/listings/#{state}", options)
107
+ end
108
+ end
109
+
110
+ # Retrieve active listings for a given category.
111
+ # By default, pulls back the first 25 active listings.
112
+ # Defaults can be overridden using :limit, :offset, and :state
113
+ #
114
+ # options = {
115
+ # :limit => 25,
116
+ # :offset => 100,
117
+ # :token => 'toke',
118
+ # :secret => 'secret'
119
+ # }
120
+ # Etsy::Listing.find_all_active_by_category("accessories", options)
121
+ #
122
+ def self.find_all_active_by_category(category, options = {})
123
+ options[:category] = category
124
+ get_all("/listings/active", options)
125
+ end
126
+
127
+ # The collection of images associated with this listing.
128
+ #
129
+ def images
130
+ @images ||= listing_images
131
+ end
132
+
133
+ # The primary image for this listing.
134
+ #
135
+ def image
136
+ images.first
137
+ end
138
+
139
+ # Listing category name
140
+ #
141
+ def category
142
+ path = category_path.join('/')
143
+ @category ||= Category.find(path)
144
+ end
145
+
146
+ # Returns the taxonomy defined attributes for the listing
147
+ #
148
+ def taxonomy_attributes(options={})
149
+ options.merge!(:require_secure => true)
150
+ self.class.get_all("/listings/#{id}/attributes", oauth.merge(options))
151
+ end
152
+
153
+ def variations(options={})
154
+ options.merge!(:require_secure => true)
155
+ self.class.get_all("/listings/#{id}/variations", oauth.merge(options))
156
+ end
157
+
158
+ # If these are your desired variations:
159
+ # - Dimensions: 1 x 2 inches
160
+ # - Dimensions: 2 x 4 inches
161
+ #
162
+ # Then you first have to find the property ID of the property you want to vary. Eg:
163
+ # Etsy::Variation::PropertySet.find_property_by_name("Dimensions").fetch("property_id")
164
+ #
165
+ # Then you can decide which options you want to set for this property.
166
+ # Eg:
167
+ # Etsy::Variation::PropertySet.qualifying_properties_for_property("Dimension")
168
+ # => [{
169
+ # "param"=>"dimensions_scale",
170
+ # "description"=>"Sizing Scale",
171
+ # "options"=>{
172
+ # "Inches" => 344,
173
+ # "Centimeters" => 345,
174
+ # "Other" => 346
175
+ # }
176
+ # }]
177
+ #
178
+ # Put it all together for a call to add_variations:
179
+
180
+ # property_id = Etsy::Variation::PropertySet.find_property_by_name("Dimensions").fetch("property_id")
181
+ # scale = Etsy::Variation::PropertySet.qualifying_properties_for_property("Dimensions").detect {|qp| qp.fetch("description") == "Sizing Scale"}
182
+ # my_listing.add_variations(
183
+ # :variations => [
184
+ # {"property_id" => property_id, "value" => "1 x 2", "is_available" => true, "price" => 1.23},
185
+ # {"property_id" => property_id, "value" => "2 x 4", "is_available" => true, "price" => 2.34}
186
+ # ],
187
+ # scale.fetch("param") => scale.fetch("options").fetch("Inches")
188
+ # )
189
+ def add_variations(options)
190
+ options[:variations] = JSON.dump(options.delete(:variations))
191
+ options[:require_secure] = true
192
+ self.class.post("/listings/#{id}/variations", options)
193
+ end
194
+
195
+ def update_variations(options)
196
+ options[:variations] = JSON.dump(options.delete(:variations))
197
+ options[:require_secure] = true
198
+ self.class.put("/listings/#{id}/variations", options)
199
+ end
200
+
201
+ def black_and_white?
202
+ is_black_and_white
203
+ end
204
+
205
+ STATES.each do |method_name|
206
+ define_method "#{method_name}?" do
207
+ state == method_name.sub('_', '')
208
+ end
209
+ end
210
+
211
+ # Time that this listing was created
212
+ #
213
+ def created_at
214
+ Time.at(created)
215
+ end
216
+
217
+ # Time that this listing was last modified
218
+ #
219
+ def modified_at
220
+ Time.at(modified)
221
+ end
222
+
223
+ # Time that this listing is ending (will be removed from store)
224
+ #
225
+ def ending_at
226
+ Time.at(ending)
227
+ end
228
+
229
+ #Return a list of users who have favorited this listing
230
+ #
231
+ def admirers(options = {})
232
+ options = options.merge(:access_token => token, :access_secret => secret) if (token && secret)
233
+ favorite_listings = FavoriteListing.find_all_listings_favored_by(id, options)
234
+ user_ids = favorite_listings.map {|f| f.user_id }.uniq
235
+ (user_ids.size > 0) ? Array(Etsy::User.find(user_ids, options)) : []
236
+ end
237
+
238
+ def is_supply
239
+ !!@result.fetch("is_supply")
240
+ end
241
+
242
+ private
243
+
244
+ def self.valid?(state)
245
+ VALID_STATES.include?(state)
246
+ end
247
+
248
+ def self.invalid_state_message(state)
249
+ "The state '#{state}' is invalid. Must be one of #{VALID_STATES.join(', ')}"
250
+ end
251
+
252
+ def self.sold_listings(shop_id, options = {})
253
+ includes = options.delete(:includes)
254
+
255
+ transactions = Transaction.find_all_by_shop_id(shop_id, options)
256
+ listing_ids = transactions.map {|t| t.listing_id }.uniq
257
+
258
+ options = options.merge(:includes => includes) if includes
259
+ (listing_ids.size > 0) ? Array(find(listing_ids, options)) : []
260
+ end
261
+
262
+ #Find all listings favored by a user
263
+ #
264
+ def self.find_all_user_favorite_listings(user_id, options = {})
265
+ favorite_listings = FavoriteListing.find_all_user_favorite_listings(user_id, options)
266
+ listing_ids = favorite_listings.map {|f| f.listing_id }.uniq
267
+ (listing_ids.size > 0) ? Array(find(listing_ids, options)) : []
268
+ end
269
+
270
+ #Find all listings that have been bought by a user
271
+ #
272
+ def self.bought_listings(user_id, options = {})
273
+ includes = options.delete(:includes)
274
+
275
+ transactions = Transaction.find_all_by_buyer_id(user_id, options)
276
+ listing_ids = transactions.map {|t| t.listing_id }.uniq
277
+
278
+ options = options.merge(:includes => includes) if includes
279
+ (listing_ids.size > 0) ? Array(find(listing_ids, options)) : []
280
+ end
281
+
282
+ private
283
+
284
+ def oauth
285
+ oauth = (token && secret) ? {:access_token => token, :access_secret => secret} : {}
286
+ end
287
+
288
+ def listing_images
289
+ if result && result["Images"]
290
+ result["Images"].map { |hash| Image.new(hash) }
291
+ else
292
+ Image.find_all_by_listing_id(id, oauth)
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,127 @@
1
+ module Etsy
2
+ module Model # :nodoc:all
3
+
4
+ module ClassMethods
5
+
6
+ def attribute(name, options = {})
7
+ define_method name do
8
+ @result[options.fetch(:from, name).to_s]
9
+ end
10
+ end
11
+
12
+ def attributes(*names)
13
+ names.each {|name| attribute(name) }
14
+ end
15
+
16
+ # FIXME: not quite sure where I'm going with this yet. KO.
17
+ def association(name, options = {})
18
+ define_method "associated_#{name}" do
19
+ @result[options.fetch(:from, name).to_s]
20
+ end
21
+ end
22
+
23
+ def get(endpoint, options = {})
24
+ objects = get_all(endpoint, options)
25
+ if objects.length == 0
26
+ nil
27
+ elsif objects.length == 1
28
+ objects[0]
29
+ else
30
+ objects
31
+ end
32
+ end
33
+
34
+ def get_all(endpoint, options={})
35
+ limit = options[:limit]
36
+
37
+ if limit
38
+ initial_offset = options.fetch(:offset, 0)
39
+ batch_size = options.fetch(:batch_size, 100)
40
+
41
+ result = []
42
+
43
+ if limit == :all
44
+ response = Request.get(endpoint, options.merge(:limit => batch_size, :offset => initial_offset))
45
+ result << response.result
46
+ limit = [response.total - batch_size - initial_offset, 0].max
47
+ initial_offset += batch_size
48
+ end
49
+
50
+ num_batches = limit / batch_size
51
+
52
+ num_batches.times do |batch|
53
+ total_offset = initial_offset + batch * batch_size
54
+ response = Request.get(endpoint, options.merge(:limit => batch_size, :offset => total_offset))
55
+ result << response.result
56
+ end
57
+
58
+ remainder = limit % batch_size
59
+
60
+ if remainder > 0
61
+ total_offset = initial_offset + num_batches * batch_size
62
+ response = Request.get(endpoint, options.merge(:limit => remainder, :offset => total_offset))
63
+ result << response.result
64
+ end
65
+ else
66
+ response = Request.get(endpoint, options)
67
+ result = response.result
68
+ end
69
+
70
+ [result].flatten.map do |data|
71
+ if options[:access_token] && options[:access_secret]
72
+ new(data, options[:access_token], options[:access_secret])
73
+ else
74
+ new(data)
75
+ end
76
+ end
77
+ end
78
+
79
+ def post(endpoint, options={})
80
+ Request.post(endpoint, options)
81
+ end
82
+
83
+ def put(endpoint, options={})
84
+ Request.put(endpoint, options)
85
+ end
86
+
87
+ def delete(endpoint, options={})
88
+ Request.delete(endpoint, options)
89
+ end
90
+
91
+ def find_one_or_more(endpoint, identifiers_and_options)
92
+ options = options_from(identifiers_and_options)
93
+ append = options.delete(:append_to_endpoint)
94
+ append = append.nil? ? "" : "/#{append}"
95
+ identifiers = identifiers_and_options
96
+ get("/#{endpoint}/#{identifiers.join(',')}#{append}", options)
97
+ end
98
+
99
+ def options_from(argument)
100
+ (argument.last.class == Hash) ? argument.pop : {}
101
+ end
102
+
103
+ end
104
+
105
+ def initialize(result = nil, token = nil, secret = nil)
106
+ @result = result
107
+ @token = token
108
+ @secret = secret
109
+ end
110
+
111
+ def token
112
+ @token
113
+ end
114
+
115
+ def secret
116
+ @secret
117
+ end
118
+
119
+ def result
120
+ @result
121
+ end
122
+
123
+ def self.included(other)
124
+ other.extend ClassMethods
125
+ end
126
+ end
127
+ end