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,33 @@
1
+ module Etsy
2
+ class PaymentTemplate
3
+ include Etsy::Model
4
+
5
+ attribute :id, :from => :payment_template_id
6
+ attributes :allow_check, :allow_mo, :allow_other, :allow_paypal, :allow_cc
7
+ attributes :paypal_email, :name, :first_line, :second_line, :city, :state
8
+ attributes :zip, :country_id
9
+
10
+ def self.create(options = {})
11
+ options.merge!(:require_secure => true)
12
+ post("/payments/templates", options)
13
+ end
14
+
15
+ def self.find(id, credentials = {})
16
+ options = {
17
+ :access_token => credentials[:access_token],
18
+ :access_secret => credentials[:access_secret],
19
+ :require_secure => true
20
+ }
21
+ get("/payments/templates/#{id}", options)
22
+ end
23
+
24
+ def self.find_by_user(user, credentials = {})
25
+ options = {
26
+ :access_token => credentials[:access_token],
27
+ :access_secret => credentials[:access_secret],
28
+ :require_secure => true
29
+ }
30
+ get("/users/#{user.id}/payments/templates", options)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ module Etsy
2
+
3
+ # = Profile
4
+ #
5
+ # Represents a profile resource of an Etsy user.
6
+ #
7
+ class Profile
8
+
9
+ include Etsy::Model
10
+
11
+ attribute :id, :from => :user_profile_id
12
+ attribute :user_id
13
+ attribute :bio
14
+ attribute :username, :from => :login_name
15
+ attribute :gender
16
+ attribute :birth_day
17
+ attribute :birth_month
18
+ attribute :birth_year
19
+ attribute :joined, :from => :join_tsz
20
+ attribute :favorite_materials, :from => :materials
21
+ attribute :country_id
22
+ attribute :city
23
+ attribute :location
24
+ attribute :region
25
+ attribute :avatar_id
26
+ attribute :image, :from => :image_url_75x75
27
+ attribute :lat
28
+ attribute :lon
29
+ attribute :transaction_buy_count
30
+ attribute :transaction_sold_count
31
+ attribute :is_seller
32
+ attribute :first_name
33
+ attribute :last_name
34
+
35
+ def materials
36
+ favorite_materials ? favorite_materials.split(',') : []
37
+ end
38
+
39
+ # Time that this user joined Etsy
40
+ #
41
+ def joined_at
42
+ Time.at(joined)
43
+ end
44
+
45
+ def seller?
46
+ is_seller
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ module Etsy
2
+ class Receipt
3
+ include Model
4
+
5
+ attribute :id, :from => :receipt_id
6
+ attribute :buyer_id, :from => :buyer_user_id
7
+
8
+ attributes :quantity, :listing_id, :name, :first_line, :second_line, :city, :state, :zip, :country_id,
9
+ :payment_email, :buyer_email, :creation_tsz
10
+
11
+ def self.find_all_by_shop_id(shop_id, options = {})
12
+ get_all("/shops/#{shop_id}/receipts", options)
13
+ end
14
+
15
+ def self.find_all_by_shop_id_and_status(shop_id, status, options = {})
16
+ get_all("/shops/#{shop_id}/receipts/#{status}", options)
17
+ end
18
+
19
+ def created_at
20
+ Time.at(creation_tsz)
21
+ end
22
+
23
+ def buyer
24
+ @buyer ||= User.find(buyer_id)
25
+ end
26
+
27
+ def transactions
28
+ unless @transactions
29
+ options = {}
30
+ options = options.merge(:access_token => token, :access_secret => secret) if (token && secret)
31
+ @transactions = Transaction.find_all_by_receipt_id(id, options)
32
+ end
33
+ @transactions
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,150 @@
1
+ module Etsy
2
+
3
+ # = Request
4
+ #
5
+ # A basic wrapper around GET requests to the Etsy JSON API
6
+ #
7
+ class Request
8
+
9
+ # Perform a GET request for the resource with optional parameters - returns
10
+ # A Response object with the payload data
11
+ def self.get(resource_path, parameters = {})
12
+ request = Request.new(resource_path, parameters)
13
+ Response.new(request.get)
14
+ end
15
+
16
+ def self.post(resource_path, parameters = {})
17
+ request = Request.new(resource_path, parameters)
18
+ Response.new(request.post)
19
+ end
20
+
21
+ def self.put(resource_path, parameters = {})
22
+ request = Request.new(resource_path, parameters)
23
+ Response.new(request.put)
24
+ end
25
+
26
+ def self.delete(resource_path, parameters = {})
27
+ request = Request.new(resource_path, parameters)
28
+ Response.new(request.delete)
29
+ end
30
+
31
+
32
+
33
+ # Create a new request for the resource with optional parameters
34
+ def initialize(resource_path, parameters = {})
35
+ parameters = parameters.dup
36
+ @token = parameters.delete(:access_token) || Etsy.credentials[:access_token]
37
+ @secret = parameters.delete(:access_secret) || Etsy.credentials[:access_secret]
38
+ raise("Secure connection required. Please provide your OAuth credentials via :access_token and :access_secret in the parameters") if parameters.delete(:require_secure) && !secure?
39
+ @multipart_request = parameters.delete(:multipart)
40
+ @resource_path = resource_path
41
+ @resources = parameters.delete(:includes)
42
+ if @resources.class == String
43
+ @resources = @resources.split(',').map {|r| {:resource => r}}
44
+ elsif @resources.class == Array
45
+ @resources = @resources.map do |r|
46
+ if r.class == String
47
+ {:resource => r}
48
+ else
49
+ r
50
+ end
51
+ end
52
+ end
53
+ parameters = parameters.merge(:api_key => Etsy.api_key) unless secure?
54
+ @parameters = parameters
55
+ end
56
+
57
+ def base_path # :nodoc:
58
+ "/v2"
59
+ end
60
+
61
+ # Perform a GET request against the API endpoint and return the raw
62
+ # response data
63
+ def get
64
+ client.get(endpoint_url)
65
+ end
66
+
67
+ def post
68
+ if multipart?
69
+ client.post_multipart(endpoint_url(:include_query => false), @parameters)
70
+ else
71
+ client.post(endpoint_url)
72
+ end
73
+ end
74
+
75
+ def put
76
+ client.put(endpoint_url)
77
+ end
78
+
79
+ def delete
80
+ client.delete(endpoint_url)
81
+ end
82
+
83
+ def client # :nodoc:
84
+ @client ||= secure? ? secure_client : basic_client
85
+ end
86
+
87
+ def parameters # :nodoc:
88
+ @parameters
89
+ end
90
+
91
+ def resources # :nodoc:
92
+ @resources
93
+ end
94
+
95
+ def query # :nodoc:
96
+ to_url(parameters.merge(:includes => resources.to_a.map { |r| association(r) }))
97
+ end
98
+
99
+ def to_url(val)
100
+ if val.is_a? Array
101
+ to_url(val.join(','))
102
+ elsif val.is_a? Hash
103
+ val.reject { |k, v|
104
+ k.nil? || v.nil? || (k.respond_to?(:empty?) && k.empty?) || (v.respond_to?(:empty?) && v.empty?)
105
+ }.map { |k, v| "#{to_url(k.to_s)}=#{to_url(v)}" }.join('&')
106
+ else
107
+ URI.escape(val.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
108
+ end
109
+ end
110
+
111
+ def association(options={}) # :nodoc:
112
+ s = options[:resource].dup
113
+
114
+ if options.include? :fields
115
+ s << "(#{[options[:fields]].flatten.join(',')})"
116
+ end
117
+
118
+ if options.include?(:limit) || options.include?(:offset)
119
+ s << ":#{options.fetch(:limit, 25)}:#{options.fetch(:offset, 0)}"
120
+ end
121
+
122
+ s
123
+ end
124
+
125
+ def endpoint_url(options = {}) # :nodoc:
126
+ url = "#{base_path}#{@resource_path}"
127
+ url += "?#{query}" if options.fetch(:include_query, true)
128
+ url
129
+ end
130
+
131
+ def multipart?
132
+ !!@multipart_request
133
+ end
134
+
135
+ private
136
+
137
+ def secure_client
138
+ SecureClient.new(:access_token => @token, :access_secret => @secret)
139
+ end
140
+
141
+ def basic_client
142
+ BasicClient.new
143
+ end
144
+
145
+ def secure?
146
+ !@token.nil? && !@secret.nil?
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,128 @@
1
+ module Etsy
2
+
3
+ class OAuthTokenRevoked < StandardError; end
4
+ class MissingShopID < StandardError; end
5
+ class TemporaryIssue < StandardError; end
6
+ class ResourceUnavailable < TemporaryIssue; end
7
+ class ExceededRateLimit < TemporaryIssue; end
8
+ class InvalidUserID < StandardError; end
9
+ class EtsyJSONInvalid < StandardError
10
+ attr_reader :code, :data
11
+ def initialize(args)
12
+ @code = args[:code]
13
+ @data = args[:data]
14
+ end
15
+ end
16
+
17
+
18
+ # = Response
19
+ #
20
+ # Basic wrapper around the Etsy JSON response data
21
+ #
22
+ class Response
23
+
24
+ # Create a new response based on the raw HTTP response
25
+ def initialize(raw_response)
26
+ @raw_response = raw_response
27
+ end
28
+
29
+ # Convert the raw JSON data to a hash
30
+ def to_hash
31
+ validate!
32
+ @hash ||= json
33
+ end
34
+
35
+ def body
36
+ @raw_response.body
37
+ end
38
+
39
+ def code
40
+ @raw_response.code
41
+ end
42
+
43
+ # Number of records in the response results
44
+ def count
45
+ if paginated?
46
+ to_hash['results'].nil? ? 0 : to_hash['results'].size
47
+ else
48
+ to_hash['count']
49
+ end
50
+ end
51
+
52
+ # Results of the API request
53
+ def result
54
+ if success?
55
+ results = to_hash['results'] || []
56
+ count == 1 ? results.first : results
57
+ else
58
+ Etsy.silent_errors ? [] : validate!
59
+ end
60
+ end
61
+
62
+ # Total number of results of the request
63
+ def total
64
+ @total ||= to_hash['count']
65
+ end
66
+
67
+ def success?
68
+ !!(code =~ /2\d\d/)
69
+ end
70
+
71
+ def paginated?
72
+ !!to_hash['pagination']
73
+ end
74
+
75
+ private
76
+
77
+ def data
78
+ @raw_response.body
79
+ end
80
+
81
+ def json
82
+ @hash ||= JSON.parse(data)
83
+ end
84
+
85
+ def validate!
86
+ raise OAuthTokenRevoked if token_revoked?
87
+ raise MissingShopID if missing_shop_id?
88
+ raise InvalidUserID if invalid_user_id?
89
+ raise TemporaryIssue if temporary_etsy_issue?
90
+ raise ResourceUnavailable if resource_unavailable?
91
+ raise ExceededRateLimit if exceeded_rate_limit?
92
+ raise EtsyJSONInvalid.new({:code => code, :data => data}) unless valid_json?
93
+ true
94
+ end
95
+
96
+ def valid_json?
97
+ json
98
+ return true
99
+ rescue JSON::ParserError
100
+ return false
101
+ end
102
+
103
+ def token_revoked?
104
+ data == "oauth_problem=token_revoked"
105
+ end
106
+
107
+ def missing_shop_id?
108
+ data =~ /Shop with PK shop_id/
109
+ end
110
+
111
+ def invalid_user_id?
112
+ data =~ /is not a valid user_id/
113
+ end
114
+
115
+ def temporary_etsy_issue?
116
+ data =~ /Temporary Etsy issue/
117
+ end
118
+
119
+ def resource_unavailable?
120
+ data =~ /Resource temporarily unavailable/
121
+ end
122
+
123
+ def exceeded_rate_limit?
124
+ data =~ /You have exceeded/
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,16 @@
1
+ module Etsy
2
+ class Section
3
+ include Etsy::Model
4
+
5
+ attributes :title, :rank, :user_id, :active_listing_count
6
+ attribute :id, :from => :shop_section_id
7
+
8
+ def self.find_by_shop(shop)
9
+ get("/shops/#{shop.id}/sections")
10
+ end
11
+
12
+ def self.find(shop, id)
13
+ get("/shops/#{shop.id}/sections/#{id}")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,131 @@
1
+ module Etsy
2
+
3
+ # = SecureClient
4
+ #
5
+ # Used for generating tokens and calling API methods that require authentication.
6
+ #
7
+ class SecureClient
8
+
9
+ # Create a new client with the necessary parameters. Accepts the following
10
+ # key/value pairs:
11
+ #
12
+ # :request_token
13
+ # :request_secret
14
+ # :access_token
15
+ # :access_secret
16
+ #
17
+ # The request token / secret is useful for generating the access token. Pass
18
+ # the access token / secret when initializing from stored credentials.
19
+ #
20
+ def initialize(attributes = {})
21
+ @attributes = attributes
22
+ end
23
+
24
+ def consumer # :nodoc:
25
+ path = '/v2/oauth/'
26
+ @consumer ||= OAuth::Consumer.new(Etsy.api_key, Etsy.api_secret, {
27
+ :site => "#{Etsy.protocol}://#{Etsy.host}",
28
+ :request_token_path => "#{path}request_token?scope=#{Etsy.permission_scopes.join('+')}",
29
+ :access_token_path => "#{path}access_token"
30
+ })
31
+ end
32
+
33
+ # Generate a request token.
34
+ #
35
+ def request_token
36
+ consumer.get_request_token(:oauth_callback => Etsy.callback_url)
37
+ end
38
+
39
+ # Access token for this request, either generated from the request token or taken
40
+ # from the :access_token parameter.
41
+ #
42
+ def access_token
43
+ @attributes[:access_token] || client.token
44
+ end
45
+
46
+ # Access secret for this request, either generated from the request token or taken
47
+ # from the :access_secret parameter.
48
+ #
49
+ def access_secret
50
+ @attributes[:access_secret] || client.secret
51
+ end
52
+
53
+ def client_from_request_data # :nodoc:
54
+ request_token = OAuth::RequestToken.new(consumer, @attributes[:request_token], @attributes[:request_secret])
55
+ request_token.get_access_token(:oauth_verifier => @attributes[:verifier])
56
+ end
57
+
58
+ def client_from_access_data # :nodoc:
59
+ OAuth::AccessToken.new(consumer, @attributes[:access_token], @attributes[:access_secret])
60
+ end
61
+
62
+ def client # :nodoc:
63
+ @client ||= has_access_data? ? client_from_access_data : client_from_request_data
64
+ end
65
+
66
+ # Fetch a raw response from the specified endpoint.
67
+ #
68
+ def get(endpoint)
69
+ client.get(endpoint)
70
+ end
71
+
72
+ def post(endpoint)
73
+ client.post(endpoint)
74
+ end
75
+
76
+ def put(endpoint)
77
+ client.put(endpoint)
78
+ end
79
+
80
+ def delete(endpoint)
81
+ client.delete(endpoint)
82
+ end
83
+
84
+ def post_multipart(endpoint, params = {})
85
+ client = Net::HTTP.new(Etsy.host, Etsy.protocol == "http" ? 80 : 443)
86
+ client.use_ssl = true if Etsy.protocol == "https"
87
+
88
+ client.start do |http|
89
+ req = Net::HTTP::Post.new(endpoint)
90
+ add_multipart_data(req, params)
91
+ add_oauth(req)
92
+ res = http.request(req)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # Encodes the request as multipart
99
+ def add_multipart_data(req, params)
100
+ crlf = "\r\n"
101
+ boundary = Time.now.to_i.to_s(16)
102
+ req["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
103
+ body = ""
104
+ params.each do |key,value|
105
+ esc_key = CGI.escape(key.to_s)
106
+ body << "--#{boundary}#{crlf}"
107
+ if value.respond_to?(:read)
108
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{crlf}"
109
+ body << "Content-Type: image/jpeg#{crlf*2}"
110
+ body << open(value.path, "rb") {|io| io.read}
111
+ else
112
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{crlf*2}#{value}"
113
+ end
114
+ body << crlf
115
+ end
116
+ body << "--#{boundary}--#{crlf*2}"
117
+ req.body = body
118
+ req["Content-Length"] = req.body.size
119
+ end
120
+
121
+ # Uses the OAuth gem to add the signed Authorization header
122
+ def add_oauth(req)
123
+ client.sign!(req)
124
+ end
125
+
126
+ def has_access_data?
127
+ !@attributes[:access_token].nil? && !@attributes[:access_secret].nil?
128
+ end
129
+
130
+ end
131
+ end