etsy 0.1.0 → 0.2.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 (38) hide show
  1. data/README.rdoc +102 -19
  2. data/Rakefile +5 -5
  3. data/lib/etsy.rb +104 -26
  4. data/lib/etsy/basic_client.rb +26 -0
  5. data/lib/etsy/image.rb +19 -16
  6. data/lib/etsy/listing.rb +30 -27
  7. data/lib/etsy/model.rb +12 -49
  8. data/lib/etsy/request.rb +41 -18
  9. data/lib/etsy/response.rb +17 -11
  10. data/lib/etsy/secure_client.rb +79 -0
  11. data/lib/etsy/shop.rb +42 -18
  12. data/lib/etsy/user.rb +39 -34
  13. data/lib/etsy/verification_request.rb +17 -0
  14. data/lib/etsy/version.rb +4 -4
  15. data/test/fixtures/image/findAllListingImages.json +102 -0
  16. data/test/fixtures/listing/findAllShopListingsActive.json +69 -0
  17. data/test/fixtures/shop/findAllShop.json +1 -0
  18. data/test/fixtures/shop/findAllShop.single.json +1 -0
  19. data/test/fixtures/shop/getShop.multiple.json +1 -0
  20. data/test/fixtures/shop/getShop.single.json +32 -0
  21. data/test/fixtures/user/getUser.multiple.json +29 -0
  22. data/test/fixtures/user/getUser.single.json +13 -0
  23. data/test/fixtures/user/getUser.single.private.json +18 -0
  24. data/test/test_helper.rb +27 -42
  25. data/test/unit/etsy/basic_client_test.rb +28 -0
  26. data/test/unit/etsy/image_test.rb +36 -15
  27. data/test/unit/etsy/listing_test.rb +84 -66
  28. data/test/unit/etsy/request_test.rb +121 -39
  29. data/test/unit/etsy/response_test.rb +20 -20
  30. data/test/unit/etsy/secure_client_test.rb +110 -0
  31. data/test/unit/etsy/shop_test.rb +74 -40
  32. data/test/unit/etsy/user_test.rb +65 -61
  33. data/test/unit/etsy/verification_request_test.rb +26 -0
  34. data/test/unit/etsy_test.rb +91 -10
  35. metadata +61 -17
  36. data/test/fixtures/getShopDetails.json +0 -70
  37. data/test/fixtures/getShopListings.json +0 -135
  38. data/test/fixtures/getUserDetails.json +0 -34
data/lib/etsy/model.rb CHANGED
@@ -1,64 +1,27 @@
1
1
  module Etsy
2
2
  module Model # :nodoc:all
3
-
3
+
4
4
  module ClassMethods
5
-
6
- def attribute(name, options = {})
7
- from = options.fetch(:from, name)
8
5
 
9
- class_eval <<-CODE
10
- def #{name}
11
- @result['#{from}']
12
- end
13
- CODE
6
+ def attribute(name, options = {})
7
+ define_method name do
8
+ @result[options.fetch(:from, name).to_s]
9
+ end
14
10
  end
15
-
11
+
16
12
  def attributes(*names)
17
13
  names.each {|name| attribute(name) }
18
14
  end
19
15
 
20
- def finder(type, endpoint)
21
- parameter = endpoint.scan(/:\w+/).first
22
- parameter.sub!(/^:/, '')
23
-
24
- endpoint.sub!(":#{parameter}", '#{' + parameter + '}')
25
-
26
- send("find_#{type}", parameter, endpoint)
27
- end
28
-
29
- def find_all(parameter, endpoint)
30
- class_eval <<-CODE
31
- def self.find_all_by_#{parameter}(#{parameter})
32
- response = Request.get("#{endpoint}")
33
- response.result.map {|listing| new(listing) }
34
- end
35
- CODE
36
- end
37
-
38
- def find_one(parameter, endpoint)
39
- class_eval <<-CODE
40
- def self.find_by_#{parameter}(#{parameter})
41
- response = Request.get("#{endpoint}")
42
- new response.result
43
- end
44
- CODE
45
- end
46
-
47
16
  end
48
-
49
- module InstanceMethods
50
-
51
- def initialize(result = nil)
52
- @result = result
53
- end
54
-
17
+
18
+ def initialize(result = nil)
19
+ @result = result
55
20
  end
56
-
21
+
57
22
  def self.included(other)
58
- other.send(:extend, Etsy::Model::ClassMethods)
59
- other.send(:include, Etsy::Model::InstanceMethods)
23
+ other.extend ClassMethods
60
24
  end
61
-
62
-
25
+
63
26
  end
64
27
  end
data/lib/etsy/request.rb CHANGED
@@ -1,48 +1,71 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = Request
4
- #
4
+ #
5
5
  # A basic wrapper around GET requests to the Etsy JSON API
6
6
  #
7
7
  class Request
8
-
9
- # The base URL for API requests
10
- def self.base_url
11
- "http://beta-api.etsy.com/v1"
8
+
9
+ def self.host # :nodoc:
10
+ 'openapi.etsy.com'
12
11
  end
13
-
12
+
14
13
  # Perform a GET request for the resource with optional parameters - returns
15
14
  # A Response object with the payload data
16
15
  def self.get(resource_path, parameters = {})
17
16
  request = Request.new(resource_path, parameters)
18
17
  Response.new(request.get)
19
18
  end
20
-
19
+
21
20
  # Create a new request for the resource with optional parameters
22
21
  def initialize(resource_path, parameters = {})
23
22
  @resource_path = resource_path
24
23
  @parameters = parameters
25
24
  end
26
25
 
26
+ def base_path # :nodoc:
27
+ parts = ['v2']
28
+ parts << 'sandbox' if Etsy.environment == :sandbox
29
+ parts << (secure? ? 'private' : 'public')
30
+
31
+ "/#{parts.join('/')}"
32
+ end
33
+
27
34
  # Perform a GET request against the API endpoint and return the raw
28
35
  # response data
29
36
  def get
30
- Net::HTTP.get(endpoint_uri)
37
+ client.get(endpoint_url)
31
38
  end
32
-
39
+
40
+ def client # :nodoc:
41
+ @client ||= secure? ? secure_client : basic_client
42
+ end
43
+
33
44
  def parameters # :nodoc:
34
45
  @parameters.merge(:api_key => Etsy.api_key, :detail_level => 'high')
35
46
  end
36
-
47
+
37
48
  def query # :nodoc:
38
49
  parameters.map {|k,v| "#{k}=#{v}"}.join('&')
39
50
  end
40
-
41
- def endpoint_uri # :nodoc:
42
- uri = URI.parse("#{self.class.base_url}#{@resource_path}")
43
- uri.query = query
44
- uri
51
+
52
+ def endpoint_url # :nodoc:
53
+ "#{base_path}#{@resource_path}?#{query}"
54
+ end
55
+
56
+ private
57
+
58
+ def secure_client
59
+ SecureClient.new(:access_token => @parameters[:access_token], :access_secret => @parameters[:access_secret])
60
+ end
61
+
62
+ def basic_client
63
+ BasicClient.new(self.class.host)
45
64
  end
46
-
65
+
66
+ def secure?
67
+ Etsy.access_mode == :read_write && !@parameters[:access_token].nil? && !@parameters[:access_secret].nil?
68
+ end
69
+
47
70
  end
48
- end
71
+ end
data/lib/etsy/response.rb CHANGED
@@ -1,30 +1,36 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = Response
4
- #
4
+ #
5
5
  # Basic wrapper around the Etsy JSON response data
6
6
  #
7
7
  class Response
8
-
9
- # Create a new response based on the raw JSON
10
- def initialize(data)
11
- @data = data
8
+
9
+ # Create a new response based on the raw HTTP response
10
+ def initialize(raw_response)
11
+ @raw_response = raw_response
12
12
  end
13
-
13
+
14
14
  # Convert the raw JSON data to a hash
15
15
  def to_hash
16
- @hash ||= JSON.parse(@data)
16
+ @hash ||= JSON.parse(data)
17
17
  end
18
-
18
+
19
19
  # Number of records in the response results
20
20
  def count
21
21
  to_hash['count']
22
22
  end
23
-
23
+
24
24
  # Results of the API request
25
25
  def result
26
26
  count == 1 ? to_hash['results'].first : to_hash['results']
27
27
  end
28
-
28
+
29
+ private
30
+
31
+ def data
32
+ @raw_response.body
33
+ end
34
+
29
35
  end
30
36
  end
@@ -0,0 +1,79 @@
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
+ @consumer ||= OAuth::Consumer.new(Etsy.api_key, Etsy.api_secret, {
26
+ :site => 'http://openapi.etsy.com',
27
+ :request_token_path => '/v2/sandbox/oauth/request_token',
28
+ :access_token_path => '/v2/sandbox/oauth/access_token',
29
+ :authorize_url => 'https://www.etsy.com/oauth/signin'
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
+ private
73
+
74
+ def has_access_data?
75
+ !@attributes[:access_token].nil? && !@attributes[:access_secret].nil?
76
+ end
77
+
78
+ end
79
+ end
data/lib/etsy/shop.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = Shop
4
4
  #
5
- # Represents a single Etsy shop. Users may or may not have an associated shop -
6
- # check the result of User#seller? to find out.
5
+ # Represents a single Etsy shop. Users may or may not have an associated shop.
7
6
  #
8
7
  # A shop has the following attributes:
9
8
  #
@@ -11,40 +10,65 @@ module Etsy
11
10
  # [title] A brief heading for the shop's main page
12
11
  # [announcement] An announcement to buyers (displays on the shop's home page)
13
12
  # [message] The message sent to users who buy from this shop
14
- # [banner_image_url] The full URL to the shops's banner image
15
- # [listing_count] The total number of active listings contained in this shop
13
+ # [image_url] The full URL to the shops's banner image
14
+ # [active_listings_count] The number of active listings present in this shop
16
15
  #
17
16
  class Shop
18
-
17
+
19
18
  include Etsy::Model
20
19
 
21
- finder :one, '/shops/:user_id'
22
-
20
+ attributes :title, :announcement, :user_id
21
+
22
+ attribute :id, :from => :shop_id
23
+ attribute :image_url, :from => 'image_url_760x100'
24
+ attribute :active_listings_count, :from => 'listing_active_count'
25
+ attribute :updated, :from => :last_updated_tsz
26
+ attribute :created, :from => :creation_tsz
23
27
  attribute :name, :from => :shop_name
24
- attribute :updated, :from => :last_updated_epoch
25
- attribute :created, :from => :creation_epoch
26
28
  attribute :message, :from => :sale_message
27
29
 
28
- attributes :banner_image_url, :listing_count, :title, :announcement, :user_id
29
-
30
+ # Retrieve one or more shops by name or ID:
31
+ #
32
+ # Etsy::Shop.find('reagent')
33
+ #
34
+ # You can find multiple shops by passing an array of identifiers:
35
+ #
36
+ # Etsy::Shop.find(['reagent', 'littletjane'])
37
+ #
38
+ def self.find(*identifiers)
39
+ response = Request.get("/shops/#{identifiers.join(',')}")
40
+ shops = [response.result].flatten.map {|data| new(data) }
41
+
42
+ (identifiers.length == 1) ? shops[0] : shops
43
+ end
44
+
45
+ # Retrieve a list of all shops. By default it fetches 25 at a time, but that can
46
+ # be configured by passing the :limit and :offset parameters:
47
+ #
48
+ # Etsy::Shop.all(:limit => 100, :offset => 100)
49
+ #
50
+ def self.all(options = {})
51
+ response = Request.get("/shops", options)
52
+ response.result.map {|data| new(data) }
53
+ end
54
+
30
55
  # Time that this shop was created
31
56
  #
32
57
  def created_at
33
58
  Time.at(created)
34
59
  end
35
-
60
+
36
61
  # Time that this shop was last updated
37
62
  #
38
63
  def updated_at
39
64
  Time.at(updated)
40
65
  end
41
-
42
- # A collection of listings in this user's shop. See Etsy::Listing for
43
- # more information
66
+
67
+ # The collection of active listings associated with this shop
44
68
  #
45
69
  def listings
46
- Listing.find_all_by_user_id(user_id)
70
+ @listings ||= Listing.find_all_by_shop_id(id)
47
71
  end
48
-
72
+
49
73
  end
50
74
  end
data/lib/etsy/user.rb CHANGED
@@ -1,54 +1,59 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = User
4
4
  #
5
5
  # Represents a single Etsy user - has the following attributes:
6
6
  #
7
7
  # [id] The unique identifier for this user
8
8
  # [username] This user's username
9
- # [url] The full URL to this user's profile page / shop (if seller)
10
- # [city] The user's city / state (optional)
11
- # [gender] The user's gender
12
- # [bio] User's biography
9
+ # [email] This user's email address (authenticated calls only)
13
10
  #
14
11
  class User
15
-
12
+
16
13
  include Etsy::Model
17
-
18
- finder :one, '/users/:username'
19
-
20
- attribute :username, :from => :user_name
14
+
21
15
  attribute :id, :from => :user_id
22
- attribute :joined, :from => :join_epoch
23
- attribute :seller, :from => :is_seller
24
- attribute :last_login, :from => :last_login_epoch
25
-
26
- attributes :url, :city, :gender, :bio
27
-
28
- # This user's shop, returns nil if user is not a seller. See Etsy::Shop
29
- # for more information.
16
+ attribute :username, :from => :login_name
17
+ attribute :email, :from => :primary_email
18
+ attribute :created, :from => :creation_tsz
19
+
20
+ # Retrieve one or more users by name or ID:
30
21
  #
31
- def shop
32
- Shop.find_by_user_id(id) if seller?
22
+ # Etsy::User.find('reagent')
23
+ #
24
+ # You can find multiple users by passing an array of identifiers:
25
+ #
26
+ # Etsy::User.find(['reagent', 'littletjane'])
27
+ #
28
+ def self.find(*identifiers)
29
+ self.get("/users/#{identifiers.join(',')}")
33
30
  end
34
-
35
- # Is this user a seller?
31
+
32
+ # Retrieve the currently authenticated user.
36
33
  #
37
- def seller?
38
- seller
34
+ def self.myself(token, secret)
35
+ self.get("/users/__SELF__", :access_token => token, :access_secret => secret)
39
36
  end
40
-
41
- # Time that this user joined the site
37
+
38
+ # The shop associated with this user.
42
39
  #
43
- def joined_at
44
- Time.at(joined)
40
+ def shop
41
+ @shop ||= Shop.find(username)
45
42
  end
46
-
47
- # Time that this user last logged in
43
+
44
+ # Time that this user was created
48
45
  #
49
- def last_login_at
50
- Time.at(last_login)
46
+ def created_at
47
+ Time.at(created)
48
+ end
49
+
50
+ private
51
+
52
+ def self.get(query, options={})
53
+ response = Request.get(query, options)
54
+ users = [response.result].flatten.map {|data| new(data) }
55
+
56
+ (users.length == 1) ? users[0] : users
51
57
  end
52
-
53
58
  end
54
- end
59
+ end