ecwid_api 0.0.2 → 0.2.3

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 (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