singleplatform 0.1.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +20 -5
- data/LICENSE.txt +21 -0
- data/README.md +29 -14
- data/Rakefile +0 -9
- data/lib/singleplatform.rb +0 -3
- data/lib/singleplatform/client.rb +34 -5
- data/lib/singleplatform/client/locations.rb +16 -6
- data/lib/singleplatform/client/menus.rb +1 -0
- data/lib/singleplatform/client/photos.rb +9 -2
- data/lib/singleplatform/error.rb +39 -0
- data/lib/singleplatform/request.rb +44 -0
- data/lib/singleplatform/response.rb +65 -0
- data/lib/singleplatform/version.rb +1 -1
- data/spec/singleplatform/client/locations_spec.rb +87 -0
- data/spec/singleplatform/client/menus_spec.rb +41 -0
- data/spec/singleplatform/client/photos_spec.rb +41 -0
- data/spec/singleplatform/client_spec.rb +37 -0
- data/spec/singleplatform_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/fixtures/location.json +114 -0
- data/spec/support/fixtures/menus.json +374 -0
- data/spec/support/fixtures/photos.json +76 -0
- data/spec/support/fixtures/updated_since.json +233 -0
- metadata +17 -6
- data/lib/singleplatform/client/request.rb +0 -24
- data/test/singleplatform/test_client.rb +0 -15
- data/test/test_singleplatform.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c145adab5f968aa717643063bfad1482d70ed11e
|
4
|
+
data.tar.gz: d5311c4cb78d5363440580d00dde210609b578bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2717d6cb948eb3732df5f9f1fed81598f83a12019d2d065f60cb8fed4a9293410b617032bab46e4c24f621c6f88aab3ce7f4b5afd99607783fe55d95335b0137
|
7
|
+
data.tar.gz: 919cfa303a3406c364ad532a646012114bb609b1333927502097814a4d9b54b5ae5131dffc7317e400645bcac61cf36ccfd5fc2674e2dab690732ad379543655
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,23 +1,38 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
4
|
+
addressable (2.4.0)
|
5
|
+
crack (0.4.3)
|
6
|
+
safe_yaml (~> 1.0.0)
|
7
|
+
diff-lcs (1.2.5)
|
8
|
+
hashdiff (0.3.0)
|
5
9
|
hashie (3.4.3)
|
6
10
|
httparty (0.14.0)
|
7
11
|
multi_xml (>= 0.5.2)
|
8
12
|
multi_xml (0.5.5)
|
13
|
+
rspec (2.99.0)
|
14
|
+
rspec-core (~> 2.99.0)
|
15
|
+
rspec-expectations (~> 2.99.0)
|
16
|
+
rspec-mocks (~> 2.99.0)
|
17
|
+
rspec-core (2.99.2)
|
18
|
+
rspec-expectations (2.99.2)
|
19
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
20
|
+
rspec-mocks (2.99.4)
|
21
|
+
safe_yaml (1.0.4)
|
22
|
+
webmock (2.1.0)
|
23
|
+
addressable (>= 2.3.6)
|
24
|
+
crack (>= 0.3.2)
|
25
|
+
hashdiff
|
9
26
|
|
10
27
|
PLATFORMS
|
11
28
|
ruby
|
12
29
|
|
13
30
|
DEPENDENCIES
|
14
31
|
bundler
|
15
|
-
dotenv
|
16
32
|
hashie
|
17
33
|
httparty
|
18
|
-
|
19
|
-
|
20
|
-
ruby 2.2.2p95
|
34
|
+
rspec
|
35
|
+
webmock
|
21
36
|
|
22
37
|
BUNDLED WITH
|
23
38
|
1.12.5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Jeff Gharakhanian
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -42,13 +42,13 @@ With an initialized client, you can request information on locations in SinglePl
|
|
42
42
|
client.location('nobu')
|
43
43
|
```
|
44
44
|
|
45
|
-
This will return a
|
45
|
+
This will return a ```Singleplatform::Response``` object. Access the response body or code with ```.body``` and ```.code``` respectively. Calling ```.body``` returns a ```Hashie::Mash``` pseudo object which allows you to access all location attributes with dot notation.
|
46
46
|
|
47
47
|
```ruby
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
response = client.location('nobu') # => #<Singleplatform::Response ... >
|
49
|
+
response.body.name # => "Nobu"
|
50
|
+
response.body.attributes # => #<Hashie::Mash ... >
|
51
|
+
response.body.attributes.drive_thru # => false
|
52
52
|
```
|
53
53
|
|
54
54
|
See SinglePlatform's API documentation for a full list of attributes.
|
@@ -58,27 +58,42 @@ See SinglePlatform's API documentation for a full list of attributes.
|
|
58
58
|
You can retrieve locations en masse by calling:
|
59
59
|
|
60
60
|
```ruby
|
61
|
-
client.locations_updated_since('2016-08-01', limit: 100)
|
61
|
+
response = client.locations_updated_since('2016-08-01', limit: 100)
|
62
62
|
```
|
63
63
|
|
64
|
-
Results are paginated. The maximum (and default) limit per page is 5000.
|
64
|
+
Results are paginated. The maximum (and default) limit per page is 5000. To access the next page of results, call:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
response.next
|
68
|
+
```
|
65
69
|
|
66
70
|
### Menus
|
67
|
-
SinglePlatform locations have menus or lists of products and services that you can access with a configured API client. The following call returns an Array of Hashie::Mash objects.
|
71
|
+
SinglePlatform locations have menus or lists of products and services that you can access with a configured API client. The following call returns a ```Singleplatform::Response``` object whose body contains an Array of ```Hashie::Mash``` objects.
|
68
72
|
|
69
73
|
```ruby
|
70
|
-
|
71
|
-
|
74
|
+
response = client.menus_for('nobu') # => #<Array ... >
|
75
|
+
response.body.first.name # => "Dinner Menu"
|
72
76
|
```
|
73
77
|
|
74
78
|
See SinglePlatform's API documentation for the Menu schema with a full list of attributes.
|
75
79
|
|
76
80
|
### Photos
|
77
|
-
|
81
|
+
|
82
|
+
#### Fetching photos for a particular location
|
83
|
+
|
84
|
+
Many SinglePlatform locations have photos, both at the business and menu-item level. Returns a ```Singleplatform::Response``` object whose body is an Array of menus.
|
78
85
|
|
79
86
|
```ruby
|
80
|
-
|
81
|
-
|
82
|
-
|
87
|
+
response = client.photos_for('no') # => #<Singleplatform::Response ... >
|
88
|
+
response.body.first.type # => "Product"
|
89
|
+
response.body.first.url # => "http://xyz.cloudfront.net/.../39bf7671bc7d006f4cef72d94eee24aeec7615d2.jpg"
|
83
90
|
```
|
84
91
|
|
92
|
+
#### Fetching all photos updated since a given date
|
93
|
+
|
94
|
+
Similarly to ```locations_updated_since```, calling ```photos_updated_since``` returns a set of paginated results. Get the next page of results by calling ```next``` on the ```Singleplatform:::Response``` object.
|
95
|
+
|
96
|
+
````ruby
|
97
|
+
response = client.photos_updated_since('2016-09-01') # => #<Singleplatform::Response ... >
|
98
|
+
response
|
99
|
+
````
|
data/Rakefile
CHANGED
data/lib/singleplatform.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
require 'singleplatform/
|
1
|
+
require 'singleplatform/request'
|
2
|
+
require 'singleplatform/response'
|
3
|
+
require 'singleplatform/error'
|
2
4
|
require 'singleplatform/client/locations'
|
3
5
|
require 'singleplatform/client/menus'
|
4
6
|
require 'singleplatform/client/photos'
|
@@ -9,9 +11,7 @@ module Singleplatform
|
|
9
11
|
class Client
|
10
12
|
attr_accessor :base_url, :client_id, :client_secret
|
11
13
|
|
12
|
-
BASE_URL
|
13
|
-
CLIENT_ID = ENV['CLIENT_ID'].freeze
|
14
|
-
CLIENT_SECRET = ENV['CLIENT_SECRET'].freeze
|
14
|
+
BASE_URL = 'http://publishing-api.singleplatform.com'
|
15
15
|
|
16
16
|
include Singleplatform::Client::Locations
|
17
17
|
include Singleplatform::Client::Menus
|
@@ -24,6 +24,7 @@ module Singleplatform
|
|
24
24
|
@base_url = BASE_URL
|
25
25
|
@client_id = args[:client_id]
|
26
26
|
@client_secret = args[:client_secret]
|
27
|
+
raise Error::MissingCredentialsError if credentials_missing?
|
27
28
|
end
|
28
29
|
|
29
30
|
# Form the complete URL for a given endpoint
|
@@ -34,7 +35,7 @@ module Singleplatform
|
|
34
35
|
# @param params [Hash]
|
35
36
|
# @return [String]
|
36
37
|
def generate_url(path, params = {})
|
37
|
-
params['client']
|
38
|
+
params['client'] ||= client_id
|
38
39
|
signature_base_string = "#{path}?#{URI.encode_www_form(params)}"
|
39
40
|
"#{base_url}#{signature_base_string}&signature=#{generate_signature(signature_base_string)}"
|
40
41
|
end
|
@@ -47,5 +48,33 @@ module Singleplatform
|
|
47
48
|
key = OpenSSL::HMAC.digest('sha1', client_secret, base_string)
|
48
49
|
CGI::escape(Base64.encode64(key).chomp)
|
49
50
|
end
|
51
|
+
|
52
|
+
# Helper method to determine if credentials are missing and, if
|
53
|
+
# so, raise Error::MissingCredentials
|
54
|
+
#
|
55
|
+
# @return [Boolean]
|
56
|
+
def credentials_missing?
|
57
|
+
client_id.nil? || client_secret.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Helper method to determine if any number of params are nil,
|
61
|
+
# empty or just spaces.
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
def valid_params?(*args)
|
65
|
+
args.map { |a| return false if a.nil? || a.to_s.gsub(/\s/, '').empty? }
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
# Helper method to determine if a date is valid
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
def valid_date?(date)
|
73
|
+
d = date.split('-')
|
74
|
+
d.map! { |d| d.to_i }
|
75
|
+
Date.valid_date?(d[0], d[1], d[2])
|
76
|
+
rescue TypeError
|
77
|
+
return false
|
78
|
+
end
|
50
79
|
end
|
51
80
|
end
|
@@ -4,11 +4,20 @@ module Singleplatform
|
|
4
4
|
# Fetch information about a specific location
|
5
5
|
#
|
6
6
|
# @param id [String]
|
7
|
-
# @
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
url
|
7
|
+
# @return [Singleplatform::Response]
|
8
|
+
def location(id)
|
9
|
+
raise Error::InvalidLocationError unless valid_params?(id)
|
10
|
+
url = generate_url("/locations/#{id}/")
|
11
|
+
Request.get(url)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fetch location, menus and photo data for a specific location
|
15
|
+
#
|
16
|
+
# @param id [String]
|
17
|
+
# @return [Singleplatform::Response]
|
18
|
+
def all_for(id)
|
19
|
+
raise Error::InvalidLocationError unless valid_params?(id)
|
20
|
+
url = generate_url("/locations/#{id}/all")
|
12
21
|
Request.get(url)
|
13
22
|
end
|
14
23
|
|
@@ -17,8 +26,9 @@ module Singleplatform
|
|
17
26
|
# @param date [String]
|
18
27
|
# @param options [Hash]
|
19
28
|
# @option options [Fixnum] :limit Maximum (default) 5000 per page
|
20
|
-
# @return [
|
29
|
+
# @return [Singleplatform::Response]
|
21
30
|
def locations_updated_since(date, options = {})
|
31
|
+
raise Error::InvalidDateError unless valid_date?(date)
|
22
32
|
url = generate_url(
|
23
33
|
'/locations/updated_since/',
|
24
34
|
{ date: date }.merge(options)
|
@@ -8,6 +8,7 @@ module Singleplatform
|
|
8
8
|
# @option options [String] :format Short menu available ('short')
|
9
9
|
# @return [Hashie::Mash]
|
10
10
|
def menus_for(id, options = {})
|
11
|
+
raise Error::InvalidLocationError unless valid_params?(id)
|
11
12
|
url = generate_url("/locations/#{id}/menus", options)
|
12
13
|
Request.get(url)
|
13
14
|
end
|
@@ -10,6 +10,7 @@ module Singleplatform
|
|
10
10
|
# @option options [String] :type (interior, exterior, item, logo, uncategorized)
|
11
11
|
# @return [Hashie::Mash]
|
12
12
|
def photos_for(id, options = {})
|
13
|
+
raise Error::InvalidLocationError unless valid_params?(id)
|
13
14
|
url = generate_url("/location/#{id}/photos", options)
|
14
15
|
Request.get(url)
|
15
16
|
end
|
@@ -17,9 +18,15 @@ module Singleplatform
|
|
17
18
|
# Fetch photos added/updated since a given date
|
18
19
|
#
|
19
20
|
# @param date [String]
|
21
|
+
# @param options [Hash]
|
22
|
+
# @option options [Fixnum] :limit Maximum (default) 5000 per page
|
20
23
|
# @return [Hashie::Mash]
|
21
|
-
def photos_updated_since(date)
|
22
|
-
|
24
|
+
def photos_updated_since(date, options = {})
|
25
|
+
raise Error::InvalidDateError unless valid_params?(date)
|
26
|
+
url = generate_url(
|
27
|
+
'/photos/updated_since/',
|
28
|
+
{ date: date }.merge(options)
|
29
|
+
)
|
23
30
|
Request.get(url)
|
24
31
|
end
|
25
32
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Singleplatform
|
2
|
+
module Error
|
3
|
+
class Base < StandardError
|
4
|
+
def initialize(msg)
|
5
|
+
super(msg)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MissingCredentialsError < Base
|
10
|
+
def initialize(msg = "You must initialize a client with ID and Secret.")
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ApiError < Base
|
16
|
+
def initialize(msg = "Oops! There was an error.")
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class MissingParametersError < Base
|
22
|
+
def initialize(msg = "Your request is missing required parameters.")
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class InvalidLocationError < Base
|
28
|
+
def initialize(msg = "Your request must supply a valid location.")
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class InvalidDateError < Base
|
34
|
+
def initialize(msg = "Your request must supply a valid date.")
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module Singleplatform
|
5
|
+
class Request
|
6
|
+
# Make an HTTP get request to given URL
|
7
|
+
#
|
8
|
+
# @param url [String]
|
9
|
+
# @return [Singleplatform::Response]
|
10
|
+
def self.get(url)
|
11
|
+
tries ||= 3
|
12
|
+
response = HTTParty.get(url)
|
13
|
+
rescue
|
14
|
+
sleep 3
|
15
|
+
if tries -= 1 > 0
|
16
|
+
retry
|
17
|
+
end
|
18
|
+
raise(
|
19
|
+
Error::RequestError,
|
20
|
+
"Unable to transmit request to SinglePlatform. Try again later or contact technical support."
|
21
|
+
)
|
22
|
+
else
|
23
|
+
raise(
|
24
|
+
Error::ApiError,
|
25
|
+
"#{response.code}: #{response['errorMessage']}"
|
26
|
+
) if response.code != 200
|
27
|
+
Response.new(
|
28
|
+
code: response.code,
|
29
|
+
body: self.parse_response_body(response.body),
|
30
|
+
# Pass the calling method to the Response object so it knows which
|
31
|
+
# method to call when API results are iterable
|
32
|
+
origin: caller_locations(1,1)[0].label
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Transform API JSON response to Hashie::Mash pseudo object
|
37
|
+
#
|
38
|
+
# @return [Hashie::Mash]
|
39
|
+
def self.parse_response_body(body)
|
40
|
+
return body unless JSON.parse(body)
|
41
|
+
Hashie::Mash.new(JSON.parse(body)).data
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Singleplatform
|
4
|
+
class Response
|
5
|
+
attr_accessor :code, :body, :next_page, :origin
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@code = args[:code]
|
9
|
+
@body = args[:body]
|
10
|
+
@next_page = args[:body].respond_to?(:next) ? args[:body].next : nil
|
11
|
+
@origin = args[:origin].to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
# An iterator for retrieving the next page of results from
|
15
|
+
# API response.
|
16
|
+
#
|
17
|
+
# @note Will only work with Client#locations_updated_since and
|
18
|
+
# #photos_updated_since at this time.
|
19
|
+
#
|
20
|
+
# @return [Hashie::Mash]
|
21
|
+
def next
|
22
|
+
return nil if next_page.nil? || next_page.empty?
|
23
|
+
params = prepare_params(next_page)
|
24
|
+
client = Singleplatform.new(
|
25
|
+
client_id: ENV['CLIENT_ID'],
|
26
|
+
client_secret: ENV['CLIENT_SECRET']
|
27
|
+
)
|
28
|
+
new_page = client.public_send(
|
29
|
+
origin.to_sym,
|
30
|
+
params.delete('date'), params
|
31
|
+
)
|
32
|
+
refresh(new_page)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Update Response instance variables
|
38
|
+
#
|
39
|
+
# @param [Singleplatform::Response]
|
40
|
+
# @return [Singleplatform::Response]
|
41
|
+
def refresh(response)
|
42
|
+
@code = response.code
|
43
|
+
@body = response.body
|
44
|
+
@next_page = response.next_page
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
# Take any given URL and parse its query params.
|
49
|
+
#
|
50
|
+
# @param url [String]
|
51
|
+
# @return [Hash]
|
52
|
+
def parse_params(url)
|
53
|
+
CGI::parse(url.split('?')[-1])
|
54
|
+
end
|
55
|
+
|
56
|
+
# Include additional params required for accessing API
|
57
|
+
#
|
58
|
+
# @return [Hash]
|
59
|
+
def prepare_params(url)
|
60
|
+
params = parse_params(url)
|
61
|
+
params['client'] = ENV['CLIENT_ID']
|
62
|
+
params
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|