ecwid_api 0.0.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +8 -0
  3. data/README.md +123 -31
  4. data/ecwid_api.gemspec +10 -8
  5. data/lib/ecwid_api.rb +18 -29
  6. data/lib/ecwid_api/api.rb +12 -0
  7. data/lib/ecwid_api/api/base.rb +18 -0
  8. data/lib/ecwid_api/api/categories.rb +56 -0
  9. data/lib/ecwid_api/api/customers.rb +53 -0
  10. data/lib/ecwid_api/api/orders.rb +36 -0
  11. data/lib/ecwid_api/api/product_combinations.rb +48 -0
  12. data/lib/ecwid_api/api/product_types.rb +56 -0
  13. data/lib/ecwid_api/api/products.rb +148 -0
  14. data/lib/ecwid_api/category.rb +53 -4
  15. data/lib/ecwid_api/client.rb +65 -58
  16. data/lib/ecwid_api/customer.rb +10 -0
  17. data/lib/ecwid_api/entity.rb +151 -29
  18. data/lib/ecwid_api/error.rb +10 -0
  19. data/lib/ecwid_api/o_auth.rb +106 -0
  20. data/lib/ecwid_api/order.rb +118 -0
  21. data/lib/ecwid_api/order_item.rb +17 -0
  22. data/lib/ecwid_api/paged_ecwid_response.rb +57 -0
  23. data/lib/ecwid_api/paged_enumerator.rb +66 -0
  24. data/lib/ecwid_api/person.rb +7 -0
  25. data/lib/ecwid_api/product.rb +65 -0
  26. data/lib/ecwid_api/product_combination.rb +30 -0
  27. data/lib/ecwid_api/product_type.rb +18 -0
  28. data/lib/ecwid_api/product_type_attribute.rb +27 -0
  29. data/lib/ecwid_api/unpaged_ecwid_response.rb +38 -0
  30. data/lib/ecwid_api/version.rb +1 -1
  31. data/lib/ext/string.rb +9 -1
  32. data/spec/api/categories_spec.rb +31 -0
  33. data/spec/api/customers_spec.rb +20 -0
  34. data/spec/api/orders_spec.rb +30 -0
  35. data/spec/api/product_types_spec.rb +20 -0
  36. data/spec/api/products_spec.rb +20 -0
  37. data/spec/category_spec.rb +1 -6
  38. data/spec/client_spec.rb +4 -32
  39. data/spec/entity_spec.rb +120 -8
  40. data/spec/fixtures/categories.json +28 -22
  41. data/spec/fixtures/classes.json +44 -0
  42. data/spec/fixtures/customers.json +48 -0
  43. data/spec/fixtures/order.json +162 -0
  44. data/spec/fixtures/orders.json +303 -0
  45. data/spec/fixtures/products.json +141 -0
  46. data/spec/helpers/client.rb +34 -0
  47. data/spec/oauth_spec.rb +40 -0
  48. data/spec/order_item_spec.rb +12 -0
  49. data/spec/order_spec.rb +71 -0
  50. data/spec/paged_enumerator_spec.rb +38 -0
  51. data/spec/spec_helper.rb +3 -3
  52. metadata +93 -37
  53. data/lib/ecwid_api/category_api.rb +0 -62
  54. data/spec/category_api_spec.rb +0 -36
  55. data/spec/ecwid_api_spec.rb +0 -15
  56. data/spec/helpers/faraday.rb +0 -30
@@ -0,0 +1,36 @@
1
+ require_relative "../paged_ecwid_response"
2
+
3
+ module EcwidApi
4
+ module Api
5
+ # Public: This is the Ecwid API for Orders. It abstracts the end-points
6
+ # of the Ecwid API that deal with orders.
7
+ class Orders < Base
8
+ # Public: Gets Orders from the Ecwid API
9
+ #
10
+ # params - optional parameters that can be used to filter the request.
11
+ # For a list of params, please refer to the API documentation:
12
+ # http://kb.ecwid.com/w/page/43697230/Order%20API
13
+ # Note that the limit and offset parameters will be overridden
14
+ # since all orders will be returned and enumerated
15
+ #
16
+ # Returns a PagedEnumerator of `EcwidApi::Order` objects
17
+ def all(params = {})
18
+ PagedEcwidResponse.new(client, "orders", params) do |order_hash|
19
+ Order.new(order_hash, client: client)
20
+ end
21
+ end
22
+
23
+ # Public: Finds a an Order given an Ecwid id
24
+ #
25
+ # id - an Integer that is the Ecwid Order number
26
+ #
27
+ # Returns an EcwidApi::Order if found, nil if not
28
+ def find(id)
29
+ response = client.get("orders/#{id}")
30
+ if response.success?
31
+ Order.new(response.body, client: client)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ module EcwidApi
2
+ module Api
3
+ class ProductCombinations < Base
4
+ include Api
5
+ include Enumerable
6
+
7
+ attr_reader :product
8
+
9
+ def initialize(product, client)
10
+ @product = product
11
+ super(client)
12
+ end
13
+
14
+ def all
15
+ response = client.get("products/#{product.id}/combinations")
16
+
17
+ if response.success?
18
+ response.body.map do |data|
19
+ ProductCombination.new(data, client: client, product: product)
20
+ end
21
+ end
22
+ end
23
+
24
+ def each(&block)
25
+ all = self.all || []
26
+
27
+ all.each(&block)
28
+ end
29
+
30
+ def find(id)
31
+ response = client.get("products/#{product.id}/combinations/#{id}")
32
+
33
+ if response.success?
34
+ ProductCombination.new(response.body, product: product, client: client)
35
+ end
36
+ end
37
+
38
+ def create(params)
39
+ response = client.post("products/#{product.id}/combinations", params)
40
+ find(response.body["id"])
41
+ end
42
+
43
+ def delete_all!
44
+ client.delete("products/#{product.id}/combinations")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ require_relative "../unpaged_ecwid_response"
2
+
3
+ module EcwidApi
4
+ module Api
5
+ class ProductTypes < Base
6
+ # Public: Get all of the ProductType objects for the Ecwid store
7
+ #
8
+ # Returns an Array of ProductType objects
9
+ # NOTE: This endpoint does not behave like other Ecwid endpoints in that
10
+ # it does not return paged results. It simply returns every
11
+ # result in an array, without a wrapper with an "items" property.
12
+ def all(params = {})
13
+ UnpagedEcwidResponse.new(client, "classes") do |product_type_hash|
14
+ ProductType.new(product_type_hash, client: client)
15
+ end
16
+ end
17
+
18
+ # Public: Finds a single product_type by product_type ID
19
+ #
20
+ # id - an Ecwid product_type ID
21
+ #
22
+ # Returns a ProductType object, or nil if one can't be found
23
+ def find(id)
24
+ response = client.get("classes/#{id}")
25
+ if response.success?
26
+ ProductType.new(response.body, client: client)
27
+ end
28
+ end
29
+
30
+ # Public: Creates a new ProductType
31
+ #
32
+ # params - a Hash
33
+ #
34
+ # Raises an Error if there is a problem
35
+ #
36
+ # Returns a ProductType object
37
+ def create(params)
38
+ response = client.post("classes", params)
39
+ find(response.body["id"])
40
+ end
41
+
42
+ # Public: Updates an existing ProductType
43
+ #
44
+ # id - the Ecwid product_type ID
45
+ # params - a Hash
46
+ #
47
+ # Raises an Error if there is a problem
48
+ #
49
+ # Returns a ProductType object
50
+ def update(id, params)
51
+ client.put("classes/#{id}", params)
52
+ find(id)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,148 @@
1
+ require_relative "../paged_ecwid_response"
2
+
3
+ module EcwidApi
4
+ module Api
5
+ class Products < Base
6
+ # Public: Get all of the Product objects for the Ecwid store
7
+ # params - a hash of request parameters. Parameters can be
8
+ # keyword string Search term. Use quotes to search for exact match.
9
+ # Ecwid searches products over multiple fields:
10
+ # * title
11
+ # * description
12
+ # * SKU
13
+ # * product options
14
+ # * category name
15
+ # * gallery image descriptions
16
+ # * attribute values (except for hidden attributes).
17
+ # If your keywords contain special characters,
18
+ # it may make sense to URL encode them before making a request
19
+ # priceFrom number Minimum product price
20
+ # priceTo number Maximum product price
21
+ # category number Category ID. To get Store Front Page products, specify `&category=0` in the request
22
+ # withSubcategories boolean `true/false`: defines whether Ecwid should search in subcategories of the
23
+ # category you set in `category` field. Ignored if `category` field is not set.
24
+ # `false` is the default value
25
+ # sortBy string Sort order. Supported values:
26
+ # * `RELEVANCE` default
27
+ # * `DEFINED_BY_STORE_OWNER`
28
+ # * `ADDED_TIME_DESC`
29
+ # * `ADDED_TIME_ASC`
30
+ # * `NAME_ASC`
31
+ # * `NAME_DESC`
32
+ # * `PRICE_ASC`
33
+ # * `PRICE_DESC`
34
+ # * `UPDATED_TIME_ASC`
35
+ # * `UPDATED_TIME_DESC`
36
+ # . If request is applicable to a specific category (i.e. `category` is set),
37
+ # then `DEFINED_BY_STORE_OWNER` sort method is used
38
+ # offset number Offset from the beginning of the returned items list (for paging)
39
+ # limit number Maximum number of returned items. Maximum allowed value: `100`. Default value: `100`
40
+ # createdFrom string Product creation date/time (lower bound). Supported formats:
41
+ # * UNIX timestamp
42
+ # Examples:
43
+ # * `1447804800`
44
+ # createdTo string Product creation date/time (upper bound). Supported formats:
45
+ # * UNIX timestamp
46
+ # updatedFrom string Product last update date/time (lower bound). Supported formats:
47
+ # * UNIX timestamp
48
+ # updatedTo string Product last update date/time (upper bound). Supported formats:
49
+ # * UNIX timestamp
50
+ # enabled boolean `true` to get only enabled products, `false` to get only disabled products
51
+ # inStock boolean `true` to get only products in stock, `false` to get out of stock products
52
+ # sku string Product or variation SKU. Ecwid will return details of a product containing that SKU,
53
+ # if SKU value is an exact match. If SKU is specified, other search parameters are ignored,
54
+ # except for product ID.
55
+ # productId number Internal Ecwid product ID or multiple productIds separated by a comma. If this field is
56
+ # specified, other search parameters are ignored.
57
+ # baseUrl string Storefront URL for Ecwid to use when returning product URLs in the url field.
58
+ # If not specified, Ecwid will use the storefront URL specified in the store settings
59
+ # cleanUrls boolean If `true`, Ecwid will return the SEO-friendly clean URL (without hash `'#'`) in the `url` field.
60
+ # If `false`, Ecwid will return URL in the old format (with hash `'#'`). We recommend using
61
+ # `true` value if merchant’s website supports clean SEO-friendly URL feature
62
+ # onsale string Use `"onsale"` to get on sale items only or `"notonsale"` for items not currently on sale.
63
+ # option_{optionName} string Filter by product option values. Format: `option_{optionName}=param[,param]`,
64
+ # where optionName is the attribute name and param is the attribute value.
65
+ # You can place several values separated by comma. In that case, values will be connected
66
+ # through logical “OR”, and if the product has at least one of them it will get to the
67
+ # search results. Example:
68
+ # `option_Size=S,M,L&option_Color=Red,Black`
69
+ # attribute_{attributeName} string Filter by product attribute values. Format: `attribute_{attributeName}=param[,param]`,
70
+ # where attributeName is the attribute name and param is the attribute value.
71
+ # You can place several values separated by comma. In that case, values will be connected
72
+ # through logical “OR”, and if the product has at least one of them it will get to the
73
+ # search results. Example:
74
+ # `attribute_Brand=Apple&attribute_Capacity=32GB,64GB`
75
+ # lang string Preferred language for the product fields in search results.
76
+ # If a certain field does not have the translation available for the set language,
77
+ # the default language text will be used for that field.
78
+ # Returns an Array of Product objects
79
+ def all(params = {})
80
+ PagedEcwidResponse.new(client, "products", params) do |product_hash|
81
+ Product.new(product_hash, client: client)
82
+ end
83
+ end
84
+
85
+ # Public: Finds a single product by product ID
86
+ #
87
+ # id - an Ecwid product ID
88
+ # params - a hash of request parameters. Parameters can be
89
+ # baseUrl string Storefront URL for Ecwid to use when returning product URLs in the url field.
90
+ # If not specified, Ecwid will use the storefront URL specified in the
91
+ # cleanUrls boolean If `true`, Ecwid will return the SEO-friendly clean URL (without hash '#')
92
+ # in the url field. If `false`, Ecwid will return URL in the old format (with hash '#').
93
+ # We recommend using `true` value if merchant’s website supports clean
94
+ # lang string Preferred language for the product fields in search results. If a certain field does
95
+ # not have the translation available for the set language, the default language text
96
+ # will be used for that field.
97
+ # Returns a Product object, or nil if one can't be found
98
+ def find(id, params = {})
99
+ response = client.get("products/#{id}", params)
100
+ if response.success?
101
+ Product.new(response.body, client: client)
102
+ end
103
+ end
104
+
105
+ # Public: Finds a single Product by SKU
106
+ #
107
+ # sku - a SKU of a product
108
+ # params - a hash of request parameters. Parameters can be
109
+ # baseUrl string Storefront URL for Ecwid to use when returning product URLs in the url field.
110
+ # If not specified, Ecwid will use the storefront URL specified in the
111
+ # cleanUrls boolean If `true`, Ecwid will return the SEO-friendly clean URL (without hash '#')
112
+ # in the url field. If `false`, Ecwid will return URL in the old format (with hash '#').
113
+ # We recommend using `true` value if merchant’s website supports clean
114
+ # lang string Preferred language for the product fields in search results. If a certain field does
115
+ # not have the translation available for the set language, the default language text
116
+ # will be used for that field.
117
+ # Returns a Product object, or nil if one can't be found
118
+ def find_by_sku(sku, params = {})
119
+ all(params.merge(keyword: sku)).find { |product| product[:sku] == sku }
120
+ end
121
+
122
+ # Public: Creates a new Product
123
+ #
124
+ # params - a Hash
125
+ #
126
+ # Raises an Error if there is a problem
127
+ #
128
+ # Returns a Product object
129
+ def create(params)
130
+ response = client.post("products", params)
131
+ find(response.body["id"])
132
+ end
133
+
134
+ # Public: Updates an existing Product
135
+ #
136
+ # id - the Ecwid product ID
137
+ # params - a Hash
138
+ #
139
+ # Raises an Error if there is a problem
140
+ #
141
+ # Returns a Product object
142
+ def update(id, params)
143
+ client.put("products/#{id}", params)
144
+ find(id)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -1,14 +1,63 @@
1
1
  module EcwidApi
2
2
  class Category < Entity
3
+ self.url_root = "categories"
4
+
5
+ ecwid_reader :id, :parentId, :orderBy, :thumbnailUrl, :originalImageUrl,
6
+ :name, :url, :productCount, :description, :enabled, :productIds
7
+
8
+ ecwid_writer :name, :parentId, :orderBy, :description, :enabled, :productIds
9
+
3
10
  # Public: Returns an Array of sub categories for the Category
4
- def sub_categories
5
- @sub_categories ||= client.categories.all(id)
11
+ def sub_categories(params = {})
12
+ @sub_categories ||= client.categories.all(params.merge(parent: id))
13
+ end
14
+
15
+ # Public: Returns an Array of all of the sub categories (deep) for the
16
+ # Category
17
+ def all_sub_categories(params = {})
18
+ @all_sub_categories ||= sub_categories(params) + sub_categories.flat_map do |sub|
19
+ sub.all_sub_categories(params)
20
+ end
6
21
  end
7
22
 
8
23
  # Public: Returns the parent EcwidApi::Category, or nil if there isn't one
9
24
  def parent
10
- parent_id = data["parentId"]
11
- client.categories.find(parent_id) if parent_id
25
+ @parent ||= begin
26
+ parent_id = data["parentId"]
27
+ client.categories.find(parent_id) if parent_id
28
+ end
29
+ end
30
+
31
+ def parents
32
+ if parent
33
+ parent.parents + [parent]
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ # Public: Returns the Products that belong to the Category
40
+ #
41
+ # params - a Hash of values that can be used for a Prdocut search
42
+ #
43
+ # Returns an Enumeration of Products
44
+ def products(params = {})
45
+ @products ||= product_ids.map do |product_id|
46
+ client.products.find(product_id)
47
+ end
48
+ end
49
+
50
+ def product_ids
51
+ super || []
52
+ end
53
+
54
+ # Public: Uploads an image for the Category
55
+ #
56
+ # filename - a String that is the path to a local file or a URL
57
+ #
58
+ # Returns a Faraday::Response object
59
+ def upload_image!(filename)
60
+ client.post_image("categories/#{id}/image", filename)
12
61
  end
13
62
  end
14
63
  end
@@ -1,5 +1,4 @@
1
- require 'faraday'
2
- require 'faraday_middleware'
1
+ require "open-uri"
3
2
 
4
3
  module EcwidApi
5
4
  # Public: Client objects manage the connection and interface to a single Ecwid
@@ -7,89 +6,97 @@ module EcwidApi
7
6
  #
8
7
  # Examples
9
8
  #
10
- # client = EcwidApi::Client.new do |config|
11
- # config.store_id = '12345'
12
- # config.url = 'http://app.ecwid.com/api/v1'
13
- # config.order_secret_key = 'ORDER_SECRET_KEY'
14
- # config.product_secret_key = 'PRODUCT_SECRET_KEY'
15
- # end
9
+ # client = EcwidApi::Client.new(store_id: '12345', token: 'the access_token')
10
+ # client.get "/products"
16
11
  #
17
12
  class Client
13
+ extend Forwardable
14
+
18
15
  # The default base URL for the Ecwid API
19
- DEFAULT_URL = "https://app.ecwid.com/api/v1"
16
+ DEFAULT_URL = "https://app.ecwid.com/api/v3"
20
17
 
21
18
  # Public: Returns the Ecwid Store ID
22
19
  attr_reader :store_id
20
+ attr_reader :token
21
+ attr_reader :adapter
22
+
23
+ attr_reader :connection, :categories, :customers, :orders, :products, :product_types
23
24
 
24
- # Public: Gets or sets the Order API Secret Key for the Ecwid Store
25
- attr_accessor :order_secret_key
25
+ # Public: Initializes a new Client to interact with the API
26
+ #
27
+ # store_id - the Ecwid store_id to interact with
28
+ # token - the authorization token provided by oAuth. See the
29
+ # Authentication class
30
+ #
31
+ def initialize(store_id, token, adapter = Faraday.default_adapter)
32
+ @store_id, @token, @adapter = store_id, token, adapter
26
33
 
27
- # Public: Gets or sets the default Product API Secret Key for the Ecwid Store
28
- attr_accessor :product_secret_key
34
+ @connection = Faraday.new store_url do |conn|
35
+ conn.request :oauth2, token, param_name: :token
36
+ conn.request :json
29
37
 
30
- def initialize
31
- yield(self) if block_given?
32
- raise Error.new("The store_id is required") unless store_id
38
+ conn.response :json, content_type: /\bjson$/
39
+ conn.response :logger
40
+
41
+ conn.adapter adapter
42
+ end
43
+
44
+ @categories = Api::Categories.new(self)
45
+ @customers = Api::Customers.new(self)
46
+ @orders = Api::Orders.new(self)
47
+ @products = Api::Products.new(self)
48
+ @product_types = Api::ProductTypes.new(self)
33
49
  end
34
50
 
35
- # Public: Returns the base URL of the Ecwid API
36
- def url
37
- @url || DEFAULT_URL
51
+ # Public: The URL of the API for the Ecwid Store
52
+ def store_url
53
+ "#{DEFAULT_URL}/#{store_id}"
38
54
  end
39
55
 
40
- # Public: Sets the base URL for the Ecwid API
41
- def url=(url)
42
- reset_connection
43
- @url = url
56
+ def_delegators :connection, :get
57
+
58
+ def post(*args, &block)
59
+ raise_on_failure connection.post(*args, &block)
44
60
  end
45
61
 
46
- # Public: Sets the Ecwid Store ID
47
- def store_id=(store_id)
48
- reset_connection
49
- @store_id = store_id
62
+ def put(*args, &block)
63
+ raise_on_failure connection.put(*args, &block)
50
64
  end
51
65
 
52
- # Public: The URL of the API for the Ecwid Store
53
- def store_url
54
- "#{url}/#{store_id}"
66
+ def delete(*args, &block)
67
+ raise_on_failure connection.delete(*args, &block)
55
68
  end
56
69
 
57
- # Public: Sends a GET request to the Ecwid API
70
+ # Public: A helper method for POSTing an image
58
71
  #
59
- # path - The String path for the URL of the request without the base URL
60
- # params - A Hash of query string parameters
61
- #
62
- # Examples
63
- #
64
- # # Gets the Categories where the parent Category is 1
65
- # client.get("categories", parent: 1)
66
- # # => #<Faraday::Response>
72
+ # url - the URL to POST the image to
73
+ # filename - the path or URL to the image to upload
67
74
  #
68
75
  # Returns a Faraday::Response
69
- def get(path, params={})
70
- connection.get(path, params)
71
- end
72
-
73
- # Public: Returns the Category API
74
- def categories
75
- @categories ||= CategoryApi.new(self)
76
+ #
77
+ def post_image(url, filename)
78
+ post(url) do |req|
79
+ req.body = open(filename).read
80
+ end
76
81
  end
77
82
 
78
83
  private
79
84
 
80
- # Private: Resets the connection.
85
+ # Private: Raises a ResponseError if the request failed
81
86
  #
82
- # Should be used if the base URL to the Ecwid API changes
83
- def reset_connection
84
- @connection = nil
85
- end
86
-
87
- # Private: Returns a Faraday connection to interface with the Ecwid API
88
- def connection
89
- @connection ||= Faraday.new store_url do |conn|
90
- conn.response :json, content_type: /\bjson$/
91
- conn.adapter Faraday.default_adapter
87
+ # response - a Faraday::Response object that is the result of a request
88
+ #
89
+ # Raises ResponseError if the request wasn't successful
90
+ #
91
+ # Returns the original response if the request was successful
92
+ #
93
+ #
94
+ def raise_on_failure(response)
95
+ if response.success?
96
+ response
97
+ else
98
+ raise ResponseError.new(response)
92
99
  end
93
100
  end
94
101
  end
95
- end
102
+ end