shoptet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5707a03aee00c4da7081d5240e584e722e3acd3223a4e0a58c224c98b9a7dc77
4
+ data.tar.gz: d58ec4a17e917631a70bc2e662f5f16edeaf321b2a3f77df8a63450c17995380
5
+ SHA512:
6
+ metadata.gz: 4b0e431494bcbc05f7c75c8cf50e298cfece53d8421c74f63fcd424dd7efdaa6e4ed44d7bfc8c2a5846d2819eab620933bfa85700613bf83673fc00f4381da93
7
+ data.tar.gz: 372d9d14962d9aefc387678197c7db28b95ccf9717a41a6517bd5043c1fe0a86b462f1935d2bf6c6fd261ffd637a8ef025500f1c6f2da9ee6d5ce2ba6a2c45a8
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in shoptet.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shoptet (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ io-console (0.5.6)
10
+ irb (1.2.7)
11
+ reline (>= 0.1.5)
12
+ reline (0.1.6)
13
+ io-console (~> 0.5)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ irb
20
+ shoptet!
21
+
22
+ BUNDLED WITH
23
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Premysl Donat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,88 @@
1
+ # Shoptet
2
+
3
+ This is Ruby API wrapper for [Shoptet API](https://developers.shoptet.com/) which provides access to e-shop data for addon developers.
4
+
5
+ This is currently under development and hasn't been released to Rubygems yet.
6
+
7
+ # How to install
8
+
9
+ Currently only through Github:
10
+ ```
11
+ gem 'shoptet', github: 'Masa331/shoptet'
12
+ ```
13
+
14
+ # How to use
15
+
16
+ ## Setup
17
+
18
+ First instantiate Shoptet which then represents connection to one specific shop
19
+ ```
20
+ api = Shoptet.new(oauth_url, oauth_token, api_token, on_token_error)
21
+ ```
22
+ And now you can access the data
23
+ ```
24
+ api.price_lists
25
+ # => returns Enumerator with all price lists
26
+
27
+ ```
28
+ ### params for `::new`
29
+
30
+ #### * oauth_url
31
+ string with the oauth url for the partner shop under which is the addon registered.
32
+
33
+ #### oauth_token
34
+ string or nil with oauth token(the one you get during addon instalation process) used for creating api tokens. This gem can function without it but isn't able to auto re-create api tokens when necessary then.
35
+
36
+ #### api_token
37
+ string or nil with api token for accessing actual data. If it's not provided or is expired then this gem will request new one with the provided oauth token.
38
+
39
+ #### on_token_error
40
+ proc with what happens when missing or expired api token is encountered. If it's not provided then the default behaviour is to request new api token and store and use the new token only in api instance. The default proc looks like this
41
+ ```
42
+ DEFAULT_ON_TOKEN_ERROR = -> (api) do
43
+ api.api_token = api.new_api_token
44
+ end
45
+ ```
46
+
47
+ Custom proc can be used to add some special logic for this event like for example storing the new token also somewhere in the databse. The proc will be called with the api instance.
48
+
49
+ For the common scenario when working with Rails and ActiveRecord this gem also provides proc which stores the new token in ActiveRecord model. The proc can be instantiated like this `Shoptet.ar_on_token_error(my_model_instance)` and expects the model to have `#api_token` setter defined. The exact behaviour can be seen in the [code]().
50
+
51
+ ## Parallel requests
52
+
53
+ This gem fires only one network request at a time(of course) so some paralelization or whatnot is up to you.
54
+
55
+ ## Exceptions
56
+
57
+ This gem fires special exceptions on various events.
58
+
59
+ `Shoptet::Error` when some general error occurs
60
+ `Shoptet::AddonSuspended` when you are trying to access api for shop which has your addon suspended
61
+ `Shoptet::NoRights` when you try to access api endpoint which is not approved
62
+ `Shoptet::SlowDown` when 429 is returned from the api
63
+ `Shoptet::InvalidOauthToken` when invalid oauth token is used
64
+
65
+ ## Collection endpoints
66
+
67
+ Methods for accessing collections(products, orders, ...) automatically handle pagination so you don't have to do it manually. These collection methods return instances of `Enumerator` on which you can run standard methods like `#each`, `#map`, etc.
68
+
69
+ Also they accept hash with params which will be passed to Shoptet api. Through this you can set various filters and pagination.
70
+
71
+ * `Shoptet#products(api_params: {})`
72
+ * `Shoptet#products_changes(api_params: {})`
73
+ * `Shoptet#supplies(api_params: {})`
74
+ * `Shoptet#product_categories(api_params: {})`
75
+ * `Shoptet#price_lists(api_params: {})`
76
+
77
+ ## Detail endpoints
78
+
79
+ * `Shoptet#product(guid)`
80
+ * `Shoptet#order(code)`
81
+
82
+ ## Other
83
+
84
+ * `Shoptet#new_api_token` - this returns new api token created with oauth token
85
+
86
+ ## License
87
+
88
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'shoptet'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,206 @@
1
+ require_relative 'shoptet/request'
2
+
3
+ class Shoptet
4
+ class Error < StandardError
5
+ #TODO: check that this works
6
+ attr_reader :additional_data
7
+
8
+ def initialize message, additional_data = {}
9
+ super(message)
10
+ @additional_data = additional_data
11
+ end
12
+ end
13
+ class AddonSuspended < StandardError; end
14
+
15
+ DEFAULT_ON_TOKEN_ERROR = -> (api) do
16
+ api.api_token = api.new_api_token
17
+ end
18
+
19
+ def self.version
20
+ '0.0.1'
21
+ end
22
+
23
+ def self.ar_on_token_error(model)
24
+ -> (api) do
25
+ model.with_lock do
26
+ model.reload
27
+
28
+ if model.api_token != api.api_token
29
+ api.api_token = model.api_token
30
+ else
31
+ new_token = api.new_api_token
32
+ api.api_token = new_token
33
+ model.api_token = new_token
34
+ model.save!
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ attr_accessor :api_token
41
+
42
+ def initialize(oauth_url, oauth_token, api_token = nil, on_token_error = nil)
43
+ @oauth_url = oauth_url
44
+ @oauth_token = oauth_token
45
+ @api_token = api_token
46
+ @on_token_error = on_token_error || DEFAULT_ON_TOKEN_ERROR
47
+ end
48
+
49
+ def shop_info api_params = {}
50
+ request 'https://api.myshoptet.com/api/eshop'
51
+ end
52
+
53
+ def warehouses api_params = {}
54
+ enumerize 'https://api.myshoptet.com/api/stocks', api_params
55
+ end
56
+
57
+ def products api_params = {}
58
+ enumerize "https://api.myshoptet.com/api/products", api_params
59
+ end
60
+
61
+ def supplies warehouse_id, api_params = {}
62
+ uri = "https://api.myshoptet.com/api/stocks/#{warehouse_id}/supplies"
63
+ enumerize uri, api_params
64
+ end
65
+
66
+ def product_categories api_params = {}
67
+ enumerize 'https://api.myshoptet.com/api/categories', api_params
68
+ end
69
+
70
+ def products_changes api_params = {}
71
+ uri = 'https://api.myshoptet.com/api/products/changes'
72
+ enumerize uri, api_params
73
+ end
74
+
75
+ def price_lists api_params = {}
76
+ enumerize 'https://api.myshoptet.com/api/pricelists', api_params
77
+ end
78
+
79
+ def prices price_list_id, api_params = {}
80
+ uri = "https://api.myshoptet.com/api/pricelists/#{price_list_id}"
81
+ enumerize uri, api_params, 'pricelist'
82
+ end
83
+
84
+ def orders api_params = {}
85
+ enumerize 'https://api.myshoptet.com/api/orders', api_params
86
+ end
87
+
88
+ def orders_changes api_params = {}
89
+ uri = 'https://api.myshoptet.com/api/orders/changes'
90
+ enumerize uri, api_params
91
+ end
92
+
93
+ def order code, api_params = {}
94
+ uri = "https://api.myshoptet.com/api/orders/#{code}"
95
+ result = request assemble_uri(uri, api_params)
96
+ result.dig('data', 'order').merge(detail_url: uri)
97
+ end
98
+
99
+ def product guid, api_params = {}
100
+ uri = "https://api.myshoptet.com/api/products/#{guid}"
101
+ result = request assemble_uri(uri, api_params)
102
+ result.dig('data')
103
+ end
104
+
105
+ def new_api_token
106
+ headers = { 'Authorization' => "Bearer #{@oauth_token}" }
107
+
108
+ result = Shoptet::Request.get @oauth_url, headers
109
+ handle_errors result
110
+
111
+ # error = result['error']
112
+ # errors = result['errors'] || []
113
+ #
114
+ # #TODO: unite error handling with #request
115
+ # if error || errors.any?
116
+ # additional_data = {
117
+ # uri: @oauth_url,
118
+ # headers: scrub_sensitive_headers(headers)
119
+ # }
120
+ #
121
+ # raise Error.new result, additional_data
122
+ # else
123
+ # result.fetch 'access_token'
124
+ # end
125
+
126
+ result.fetch 'access_token'
127
+ end
128
+
129
+ private
130
+
131
+ def assemble_uri base, params = {}
132
+ u = URI(base)
133
+ u.query = URI.encode_www_form(params) if params.any?
134
+
135
+ u.to_s
136
+ end
137
+
138
+ def enumerize base_uri, filters = {}, data_key = nil
139
+ data_key ||= URI(base_uri).path.split('/').last
140
+ uri = assemble_uri base_uri, filters
141
+ size_proc = -> () { request(uri)['data']['paginator']['totalCount'] }
142
+
143
+ Enumerator.new(size_proc) do |y|
144
+ first_page = request uri
145
+ total_pages = first_page.dig('data', 'paginator', 'pageCount') || 0
146
+ other_pages = 2..total_pages
147
+
148
+ first_page.dig('data', data_key).each { y.yield _1.merge(page_url: uri) }
149
+
150
+ other_pages.each do |page|
151
+ uri = assemble_uri base_uri, filters.merge(page: page)
152
+ result = request uri
153
+ result.dig('data', data_key).each { y.yield _1.merge(page_url: uri) }
154
+ end
155
+ end
156
+ end
157
+
158
+ def request uri
159
+ headers = { 'Shoptet-Access-Token' => @api_token,
160
+ 'Content-Type' => 'application/vnd.shoptet.v1.0' }
161
+
162
+ result = Shoptet::Request.get uri, headers
163
+ token_errors = handle_errors result
164
+
165
+ if token_errors.any?
166
+ @on_token_error.call self
167
+ request uri
168
+ else
169
+ result
170
+ end
171
+ end
172
+
173
+ def handle_errors result
174
+ error = result['error']
175
+ errors = result['errors'] || []
176
+ token_errors, non_token_errors = errors.partition { |err| ['invalid-token', 'expired-token'].include? err['errorCode'] }
177
+
178
+ if error || non_token_errors.any?
179
+ if error == 'addon_suspended'
180
+ raise AddonSuspended
181
+ else
182
+ additional_data = {
183
+ uri: uri,
184
+ headers: scrub_sensitive_headers(headers)
185
+ }
186
+
187
+ raise Error.new result, additional_data
188
+ end
189
+ end
190
+
191
+ token_errors
192
+ end
193
+
194
+ def scrub_sensitive_headers headers
195
+ scrubbed = {}
196
+
197
+ to_scrub = ['Shoptet-Access-Token', 'Authorization']
198
+ to_scrub.each do |header|
199
+ if headers[header]
200
+ scrubbed[header] = "#{headers[header][0..20]}..."
201
+ end
202
+ end
203
+
204
+ headers.merge scrubbed
205
+ end
206
+ end
@@ -0,0 +1,32 @@
1
+ require 'net/http'
2
+
3
+ #TODO: keep_alive_timeout ?
4
+
5
+ class Shoptet
6
+ class Request
7
+ def self.get uri, headers
8
+ attempt ||= 0
9
+ attempt += 1
10
+
11
+ parsed_uri = URI(uri)
12
+
13
+ http = Net::HTTP.new parsed_uri.host, parsed_uri.port
14
+ http.use_ssl = true
15
+ http.open_timeout = 10
16
+ http.read_timeout = 10
17
+ http.write_timeout = 10
18
+ http.ssl_timeout = 10
19
+
20
+ request = Net::HTTP::Get.new parsed_uri
21
+ headers.each do |key, value|
22
+ request[key] = value
23
+ end
24
+
25
+ response = http.request request
26
+
27
+ JSON.parse response.body
28
+ rescue Net::OpenTimeout
29
+ retry if attempt < 4
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/shoptet'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'shoptet'
5
+ spec.version = Shoptet.version
6
+ spec.authors = ['Premysl Donat']
7
+ spec.email = ['pdonat@seznam.cz']
8
+
9
+ spec.summary = 'API wrapper for interacting with Shoptet api'
10
+ spec.description = spec.summary
11
+ spec.homepage = 'https://github.com/Masa331/shoptet'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = spec.homepage
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency 'irb'
26
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shoptet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Premysl Donat
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: irb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: API wrapper for interacting with Shoptet api
28
+ email:
29
+ - pdonat@seznam.cz
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - bin/console
41
+ - bin/setup
42
+ - lib/shoptet.rb
43
+ - lib/shoptet/request.rb
44
+ - shoptet.gemspec
45
+ homepage: https://github.com/Masa331/shoptet
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ homepage_uri: https://github.com/Masa331/shoptet
50
+ source_code_uri: https://github.com/Masa331/shoptet
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.3.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.1.4
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: API wrapper for interacting with Shoptet api
70
+ test_files: []