whiplash-app 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 26c09e0ec8b7a3952e2e6a687771cabeb0184d47df1322c9ec33056d23a9c1b5
4
- data.tar.gz: def14f8b736b5869ab0366422e6fd3822f1697eff1d4f5c631aac480ba666335
2
+ SHA1:
3
+ metadata.gz: 5b252f73755dcd2e6cc7d907d0009393a0661993
4
+ data.tar.gz: d38c18badde2221b2c9de962db701ca54a486739
5
5
  SHA512:
6
- metadata.gz: b8e8bdb3a247c92a628a54bc6047c900685b6a620b4b7241e0b1f2c6fbfd5177c6ac501ecadc03179240fc16e51743aa61d5024f7630fe7478c304ebc07c20b2
7
- data.tar.gz: d0a3b70f82d7671b5eee9da62a483b814401d53d020eeb848c2b03b7b0d1c114cadce55baf635d3f3b46303219c9f55506be4a6b54fc021286e9512a7a363c20
6
+ metadata.gz: ae66c0ded848674943ad97c2781236569d4d3cfff515ad30218b7025028660ce0cf10374f243d516b553781ca3a023460ca4b25e21713225b0a135c3ec6708cb
7
+ data.tar.gz: '04845cdd6a2eb36c1f05b43932f11bf25c6d32d0c91fbb74db2cb7f23b5ca9d84d3f8719fe58926831a4d26c1c07119aa03d3b74a92b7f1528f706fa169b37f1'
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .env
11
+ .ruby-version
data/README.md CHANGED
@@ -22,7 +22,27 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- ###Authentication
25
+ **NOTE: 0.4.0 introduces a breaking change and is NOT backward compatible with previous versions.**
26
+
27
+ To upgrade from < 0.4.0, you need to make two small changes:
28
+ 1. `Whiplash::App` must now be instantiated.
29
+ 2. Tokens are **not** automatically refreshed
30
+
31
+ Before:
32
+ ```ruby
33
+ api = Whiplash::App
34
+ ```
35
+
36
+ After:
37
+ ```ruby
38
+ api = Whiplash::App.new
39
+ api.refresh_token! # Since you don't have one yet
40
+ api.token # Confirm you've got a token
41
+ . . .
42
+ api.refresh_token! if api.token_expired?
43
+ ```
44
+
45
+ ### Authentication
26
46
  In order to authenticate, make sure the following `ENV` vars are set:
27
47
 
28
48
  ```ruby
@@ -30,85 +50,90 @@ ENV["WHIPLASH_CLIENT_ID"]
30
50
  ENV["WHIPLASH_CLIENT_SECRET"]
31
51
  ENV["WHIPLASH_CLIENT_SCOPE"]
32
52
  ```
33
- Once those are set, authentication is handled in app. While authentication is
34
- mostly baked into the calls, there is a method that allows for getting the app's
35
- token from the main app: `Whiplash::App.token`.
53
+
54
+ Once those are set, authentication is handled in app.
55
+
56
+ ### Oauth Client Credentials
57
+ You can authenticate using Oauth Client Credentials (i.e. auth an entire app).
58
+ You probably want this for apps that work offline, _on behalf_ of users or customers, or that don't work at the user/customer-level at all.
59
+
60
+ ```ruby
61
+ api = Whiplash::App.new
62
+ api.refresh_token! # Since you don't have one yet
63
+ api.token # Confirm you've got a token
64
+ ```
65
+
66
+ ### Oauth Authorization Code
67
+ You can also authenticate using Oauth Authorization Code (i.e. auth an individual user). This is most common for user-facing app's with a front end.
68
+
69
+ ```ruby
70
+ # Authenticate using Devise Omniauthenticateable strategy; you'll get oauth creds back as a hash
71
+ api = Whiplash::App.new(oauth_credentials_hash)
72
+ api.token # Confirm you've got a token
73
+ ```
36
74
 
37
75
  ### API URL
38
76
  In order to set your api url, you can use the following environment URL:
39
77
  ```
40
78
  ENV["WHIPLASH_API_URL"]
41
79
  ```
42
- If it isn't set, then the API URL defaults to either `https://testing.whiplashmerch.com` (test or dev environment) or `https://www.whiplashmerch.com` (prod environment).
80
+ If it isn't set, then the API URL defaults to either `https://sandbox.getwhiplash.com` (test or dev environment) or `https://www.getwhiplash.com` (prod environment).
81
+
82
+ ### Sending Customer ID and Shop ID headers
83
+ You can send the headers in `headers` array, like `{customer_id: 123, shop_id: 111}`.
84
+ Alternatively, you can set them on instantiation like `Whiplash::App.new(token, {customer_id: 123, shop_id: 111})`.
43
85
 
44
- ###Rails AR type calls
86
+ ### Rails AR type calls
45
87
 
46
88
  In order to make the use of the gem seem more "AR-ish", we've added AR oriented methods that can be used for basic object creation/deletion/updating/viewing. The basic gist of these AR style CRUD methods is that they will all follow the same pattern. If you are performing a collection action, such as `create` or `find`, the pattern is this:
47
89
 
48
90
  ```ruby
49
- Whiplash::App.create(resource, params, headers)
91
+ api.create(resource, params, headers)
50
92
  ```
51
93
 
52
94
  For member actions, such as `show`, or `destroy` methods, the pattern is this:
53
95
 
54
96
  ```ruby
55
- Whiplash::App.find(resource, id, headers)
56
- Whiplash::App.destroy(resource, id, headers)
97
+ api.find(resource, id, headers)
98
+ api.destroy(resource, id, headers)
57
99
  ```
58
100
 
59
101
  Finally, for `update` calls, it's a mixture of those:
60
102
 
61
103
  ```ruby
62
- Whiplash::App.update(resource, id, params_to_update, headers)
104
+ api.update(resource, id, params_to_update, headers)
63
105
  ```
64
106
 
65
107
  So, basic AR style calls can be performed like so:
66
108
 
67
109
  ```ruby
68
- Whiplash::App.find_all('orders', {}, { customer_id: 187 })
69
- Whiplash::App.find('orders', 1)
70
- Whiplash::App.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
71
- Whiplash::App.update('orders', 1, { key: "value"}, { customer_id: 187 } )
72
- Whiplash::App.destroy('orders', 1, { customer_id: 187 } )
73
- Whiplash::App.count('customers') #unlike other calls, which return Faraday responses, this call returns an integer.
110
+ api.find_all('orders', {}, { customer_id: 187 })
111
+ api.find('orders', 1)
112
+ api.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
113
+ api.update('orders', 1, { key: "value"}, { customer_id: 187 } )
114
+ api.destroy('orders', 1, { customer_id: 187 } )
115
+ api.count('customers')
74
116
  ```
75
117
 
76
- ###CRUD Wrapper methods
118
+ ### CRUD Wrapper methods
77
119
  In reality, all of these methods are simply wrapper methods around simple `GET/POST/PUT/DELETE` wrappers on Faraday, so if you want to get more granular,you can also make calls that simply reference the lower level REST verb:
78
120
 
79
121
  ```ruby
80
- Whiplash::App.get('orders', {}, {})
122
+ api.get('orders')
81
123
  ```
82
124
  Which will return all orders and roughly correspond to an index call. If you need to use `Whiplash::App` for nonRESTful calls, simply drop the full endpoint in as your first argument:
83
125
 
84
126
  ```ruby
85
- Whiplash::App.get('orders/non_restful_action', {}, {})
127
+ api.get('orders/non_restful_action', {}, {})
86
128
  ```
87
129
  `POST`, `PUT`, and `DELETE` calls can be performed in much the same way:
88
130
  ```ruby
89
- Whiplash::App.post(endpoint, params, headers) #POST request to the specified endpoint passing the payload in params
131
+ api.post(endpoint, params, headers) # POST request to the specified endpoint passing the payload in params
132
+ api.put(endpoint, params, headers) # PUT request to the specified endpoint passing the payload in params
133
+ api.delete(endpoint, params, headers) # DELETE request to the specified endpoint. Params would probably just be an id.
90
134
  ```
91
- ```ruby
92
- Whiplash::App.put(endpoint, params, headers) #PUT request to the specified endpoint passing the payload in params
93
- ```
94
- ```ruby
95
- Whiplash::App.delete(endpoint, params, headers) #DELETE request to the specified endpoint. Params would probably just be an id.
96
- ```
97
-
98
- *IMPORTANT!!!! PLEASE READ!*
99
- It's best if possible to use the AR style methods. They hide a lot of issues with the way Faraday handles params. For example, this should, in theory, work:
100
- ```ruby
101
- Whiplash::App.get('orders', {id: 1}, {customer_id: 187})
102
- ```
103
- BUT, due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
104
-
105
- The gem works best when the base CRUD methods are used ONLY for situations where a singular endpoint can't be reached, such as a cancel action, or uncancel, which would have to be done like so:
106
- ```ruby
107
- Whiplash::App.post('orders/1/cancel', {}, {customer_id: 187})
108
- ```
109
-
110
135
 
111
- ###Signing and Verifying.
136
+ ### Signing and Verifying.
112
137
  `whiplash-app` supports signing and verifying signatures like so:
113
138
  ```ruby
114
139
  Whiplash::App.signature(request_body)
@@ -118,10 +143,9 @@ and verifications are done like so:
118
143
  Whiplash::App.verified?(request)
119
144
  ```
120
145
 
121
- ###Caching
122
- `whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide that
123
- interface. However, if you intend to specify `REDIS` as your key-value store of
124
- choice, it's dead simple. Simply declare the following variables:
146
+ ### Caching
147
+ `whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide a local store, if needed.
148
+ However, if you intend to specify `REDIS` as your key-value store of choice, it's dead simple. Simply declare the following variables:
125
149
  ```
126
150
  ENV["REDIS_HOST"]
127
151
  ENV["REDIS_PORT"]
@@ -130,6 +154,19 @@ ENV["REDIS_NAMESPACE"]
130
154
  ```
131
155
  If those are provided, `moneta` will use your redis connection and will namespace your cache storage under the redis namespace. By default, if you do not declare a `REDIS_NAMESPACE` value, the app will default to the `WHIPLASH_CLIENT_ID`.
132
156
 
157
+ **For user-facing apps, best practice is to store the `oauth_credentials_hash` in a session variable.**
158
+
159
+ ### Gotchas
160
+ Due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
161
+ ```ruby
162
+ api.get('orders', {id: 1}, {customer_id: 187})
163
+ ```
164
+ Instead, you'd want to do:
165
+ ```ruby
166
+ api.get('orders/1', {}, {customer_id: 187})
167
+ ```
168
+
169
+
133
170
  ## Development
134
171
 
135
172
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module ApiConfig
4
4
 
5
5
  def api_url
@@ -1,7 +1,7 @@
1
1
  require "moneta"
2
2
  require "whiplash/app/moneta/namespace"
3
3
  module Whiplash
4
- module App
4
+ class App
5
5
  module Caching
6
6
 
7
7
  def cache_store
@@ -1,7 +1,6 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module Connections
4
- PER_PAGE_REQUEST_LIMIT = 50
5
4
 
6
5
  def app_request(options = {})
7
6
  if options[:params][:id]
@@ -9,94 +8,41 @@ module Whiplash
9
8
  else
10
9
  endpoint = options[:endpoint]
11
10
  end
12
-
13
- if options.dig(:options, :version)
14
- version = options.dig(:options, :version)
15
- else
16
- version = "api/v2"
17
- end
18
-
19
- connection(version).send(options[:method],
20
- endpoint,
21
- options[:params],
22
- sanitize_headers(options[:headers]))
23
- end
24
-
25
- def multi_page_get!(endpoint, params = {}, headers = nil)
26
- results = []
27
- page = 1
28
- params[:per_page] = PER_PAGE_REQUEST_LIMIT
29
- response = nil
30
-
31
- loop do
32
- partial_results_request = app_request(
33
- method: :get,
34
- endpoint: endpoint,
35
- params: params,
36
- headers: headers
37
- )
38
-
39
- if !partial_results_request.success?
40
- response = Faraday::Response.new(
41
- body: partial_results_request.body,
42
- status: partial_results_request.status,
43
- method: :get
44
- )
45
- break
46
- end
47
-
48
- results << partial_results_request.body
49
- results.flatten!
50
-
51
- page += 1
52
- params[:page] = page
53
-
54
- break if partial_results_request.body.size == 0
55
- break if partial_results_request.body.size < PER_PAGE_REQUEST_LIMIT
56
- end
57
-
58
- response ||= Faraday::Response.new(
59
- body: results,
60
- status: 200,
61
- method: :get
62
- )
63
-
11
+ options[:headers] ||= {}
12
+ options[:headers][:customer_id] ||= customer_id if customer_id
13
+ options[:headers][:shop_id] ||= shop_id if shop_id
14
+ connection.send(options[:method],
15
+ endpoint,
16
+ options[:params],
17
+ sanitize_headers(options[:headers]))
64
18
  end
65
19
 
66
- def delete(endpoint, params = {}, headers = nil, options = {})
20
+ def delete(endpoint, params = {}, headers = nil)
67
21
  app_request(method: :delete,
68
22
  endpoint: endpoint,
69
23
  params: params,
70
- headers: headers,
71
- options: options
72
- )
24
+ headers: headers)
73
25
  end
74
26
 
75
- def get(endpoint, params = {}, headers = nil, options = {})
27
+ def get(endpoint, params = {}, headers = nil)
76
28
  app_request(method: :get,
77
29
  endpoint: endpoint,
78
30
  params: params,
79
- headers: headers,
80
- options: options
81
- )
31
+ headers: headers)
82
32
  end
83
33
 
84
- def post(endpoint, params = {}, headers = nil, options = {})
34
+ def post(endpoint, params = {}, headers = nil)
85
35
  app_request(method: :post,
86
36
  endpoint: endpoint,
87
37
  params: params,
88
- headers: headers,
89
- options: options
90
- )
38
+ headers: headers)
91
39
  end
92
40
 
93
- def put(endpoint, params = {}, headers = nil, options = {})
41
+ def put(endpoint, params = {}, headers = nil)
94
42
  app_request(method: :put,
95
43
  endpoint: endpoint,
96
44
  params: params,
97
- headers: headers,
98
- options: options
99
- )
45
+ headers: headers)
100
46
  end
101
47
 
102
48
  def sanitize_headers(headers)
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module FinderMethods
4
4
 
5
5
  def count(resource, params = {}, headers = nil)
@@ -1,11 +1,6 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module Signing
4
-
5
- def request_body(body)
6
- body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
7
- end
8
-
9
4
  def signature(body)
10
5
  sha256 = OpenSSL::Digest::SHA256.new
11
6
  OpenSSL::HMAC.hexdigest(sha256,
@@ -17,6 +12,11 @@ module Whiplash
17
12
  request.headers["X-WHIPLASH-SIGNATURE"] == signature(body)
18
13
  end
19
14
 
15
+ private
16
+
17
+ def request_body(body)
18
+ body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
19
+ end
20
20
  end
21
21
  end
22
22
  end
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
3
- VERSION = "0.3.5"
2
+ class App
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
data/lib/whiplash/app.rb CHANGED
@@ -8,42 +8,71 @@ require "oauth2"
8
8
  require "faraday_middleware"
9
9
 
10
10
  module Whiplash
11
+ class App
12
+ include Whiplash::App::ApiConfig
13
+ include Whiplash::App::Caching
14
+ include Whiplash::App::Connections
15
+ include Whiplash::App::FinderMethods
16
+ extend Whiplash::App::Signing
11
17
 
12
- module App
13
- class << self
14
- include Whiplash::App::ApiConfig
15
- include Whiplash::App::Caching
16
- include Whiplash::App::Connections
17
- include Whiplash::App::FinderMethods
18
- include Whiplash::App::Signing
18
+ attr_accessor :customer_id, :shop_id, :token
19
19
 
20
- attr_accessor :customer_id, :shop_id
20
+ def initialize(token=nil, options={})
21
+ opts = options.with_indifferent_access
22
+ @token = format_token(token) unless token.nil?
23
+ @customer_id = options[:customer_id]
24
+ @shop_id = options[:shop_id]
25
+ end
21
26
 
22
- def client
23
- OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
24
- end
27
+ def client
28
+ OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
29
+ end
25
30
 
26
- def connection(version = "api/v2")
27
- out = Faraday.new [api_url, version].join("/") do |conn|
28
- conn.request :oauth2, token, token_type: "bearer"
29
- conn.request :json
30
- conn.response :json, :content_type => /\bjson$/
31
- conn.use :instrumentation
32
- conn.adapter Faraday.default_adapter
33
- end
34
- return out
31
+ def connection
32
+ out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
33
+ conn.request :oauth2, token.token, token_type: "bearer"
34
+ conn.request :json
35
+ conn.response :json, :content_type => /\bjson$/
36
+ conn.use :instrumentation
37
+ conn.adapter Faraday.default_adapter
35
38
  end
39
+ return out
40
+ end
41
+
42
+ def token=(oauth_token)
43
+ instance_variable_set("@token", format_token(oauth_token))
44
+ end
36
45
 
37
- def refresh_token!
38
- oauth_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
39
- new_token = oauth_token.token
46
+ def refresh_token!
47
+ if token.blank? # If we're storing locally, grab a new token and cache it
48
+ access_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
49
+ new_token = access_token.to_hash
40
50
  cache_store["whiplash_api_token"] = new_token
51
+ else
52
+ access_token = token.refresh!
41
53
  end
54
+ self.token = access_token
55
+ end
56
+
57
+ def token_expired?
58
+ return token.expired? unless token.blank?
59
+ return true unless cache_store.has_key?("whiplash_api_token")
60
+ return true if cache_store["whiplash_api_token"].blank?
61
+ false
62
+ end
42
63
 
43
- def token
44
- refresh_token! unless cache_store["whiplash_api_token"]
45
- return cache_store["whiplash_api_token"]
64
+ private
65
+ def format_token(oauth_token)
66
+ unless oauth_token.is_a?(OAuth2::AccessToken)
67
+ raise StandardError, "Token must either be a Hash or an OAuth2::AccessToken" unless oauth_token.is_a?(Hash)
68
+ oauth_token['expires'] = oauth_token['expires'].to_s # from_hash expects 'true'
69
+ if oauth_token.has_key?('token')
70
+ oauth_token['access_token'] = oauth_token['token']
71
+ oauth_token.delete('token')
72
+ end
73
+ oauth_token = OAuth2::AccessToken.from_hash(client, oauth_token)
46
74
  end
47
75
  end
76
+
48
77
  end
49
78
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whiplash-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Don Sullivan, Mark Dickson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-25 00:00:00.000000000 Z
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth2
@@ -137,7 +137,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  requirements: []
140
- rubygems_version: 3.4.6
140
+ rubyforge_project:
141
+ rubygems_version: 2.6.11
141
142
  signing_key:
142
143
  specification_version: 4
143
144
  summary: this gem provides connectivity to the Whiplash API for authentication and