iot_services 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 186a0027265b63444d4be2b5c93c5930c4d4acf149342768dc86c1997fc3c3f1
4
+ data.tar.gz: 58c454c92902aa8dc64d2dbac02eeb470d8132482286c75e9b5f54137ab8d932
5
+ SHA512:
6
+ metadata.gz: a4b85e3587c5841ca6256bb097917041ca400ed175d0568f3ecd30d9e65799a133925ed9e65322a16fc2e1b172ee82559ae02f3bac61155e14d222f584182d34
7
+ data.tar.gz: df2def8a661e87d4966aa63125b2dc6ba1078d88a78a94f8f72274c60e82d9bb8facaf156d8545ebc589fa5171957f212acc1ccf4107a9de0fdc99adaa006e3a
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,104 @@
1
+ Style/Documentation:
2
+ Enabled: false
3
+
4
+ Style/MethodDefParentheses:
5
+ Enabled: false
6
+
7
+ Style/ParallelAssignment:
8
+ Enabled: false
9
+
10
+ Style/FrozenStringLiteralComment:
11
+ Enabled: false
12
+
13
+ Style/FormatStringToken:
14
+ Enabled: false
15
+
16
+ Style/EmptyMethod:
17
+ Enabled: false
18
+
19
+ Style/GuardClause:
20
+ Enabled: false
21
+
22
+ Style/CommentAnnotation:
23
+ Enabled: false
24
+
25
+ Style/AsciiComments:
26
+ Enabled: false
27
+
28
+ Style/StabbyLambdaParentheses:
29
+ EnforcedStyle: require_no_parentheses
30
+
31
+ Style/MultilineIfModifier:
32
+ Enabled: false
33
+
34
+ Style/SafeNavigation:
35
+ Enabled: false
36
+
37
+ Style/FormatString:
38
+ Enabled: false
39
+
40
+ Style/AndOr:
41
+ Enabled: false
42
+
43
+ Style/Next:
44
+ Enabled: false
45
+
46
+ Style/NumericPredicate:
47
+ Enabled: false
48
+
49
+ Style/SymbolArray:
50
+ Enabled: false
51
+
52
+ Style/MutableConstant:
53
+ Enabled: false
54
+
55
+ Style/RescueModifier:
56
+ Enabled: false
57
+
58
+ Layout/LineLength:
59
+ Max: 120
60
+
61
+ Layout/SpaceInsideHashLiteralBraces:
62
+ Enabled: false
63
+
64
+ Layout/MultilineMethodCallIndentation:
65
+ Enabled: false
66
+
67
+ Layout/TrailingEmptyLines:
68
+ Enabled: false
69
+
70
+ Layout/FirstArrayElementIndentation:
71
+ EnforcedStyle: consistent
72
+
73
+ Layout/ParameterAlignment:
74
+ EnforcedStyle: with_fixed_indentation
75
+
76
+ Layout/SpaceInLambdaLiteral:
77
+ Enabled: false
78
+
79
+ Layout/EmptyLineAfterGuardClause:
80
+ Enabled: false
81
+
82
+ Metrics:
83
+ Enabled: false
84
+
85
+ Bundler:
86
+ Enabled: false
87
+
88
+ Lint/AssignmentInCondition:
89
+ Enabled: false
90
+
91
+ Lint/ShadowingOuterLocalVariable:
92
+ Enabled: false
93
+
94
+ Lint/AmbiguousRegexpLiteral:
95
+ Enabled: false
96
+
97
+ Lint/AmbiguousOperator:
98
+ Enabled: false
99
+
100
+ Lint/AmbiguousBlockAssociation:
101
+ Enabled: false
102
+
103
+ Naming/AccessorMethodName:
104
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.0
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in iot_services.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ iot_services (0.1.3)
5
+ activesupport (>= 4.0)
6
+ http (~> 4.3.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.0.3.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ zeitwerk (~> 2.2, >= 2.2.2)
17
+ addressable (2.7.0)
18
+ public_suffix (>= 2.0.2, < 5.0)
19
+ ast (2.4.0)
20
+ coderay (1.1.2)
21
+ concurrent-ruby (1.1.6)
22
+ diff-lcs (1.3)
23
+ domain_name (0.5.20190701)
24
+ unf (>= 0.0.5, < 1.0.0)
25
+ ffi (1.12.2)
26
+ ffi-compiler (1.0.1)
27
+ ffi (>= 1.0.0)
28
+ rake
29
+ formatador (0.2.5)
30
+ guard (2.16.1)
31
+ formatador (>= 0.2.4)
32
+ listen (>= 2.7, < 4.0)
33
+ lumberjack (>= 1.0.12, < 2.0)
34
+ nenv (~> 0.1)
35
+ notiffany (~> 0.0)
36
+ pry (>= 0.9.12)
37
+ shellany (~> 0.0)
38
+ thor (>= 0.18.1)
39
+ guard-compat (1.2.1)
40
+ guard-rspec (4.7.3)
41
+ guard (~> 2.1)
42
+ guard-compat (~> 1.1)
43
+ rspec (>= 2.99.0, < 4.0)
44
+ http (4.3.0)
45
+ addressable (~> 2.3)
46
+ http-cookie (~> 1.0)
47
+ http-form_data (~> 2.2)
48
+ http-parser (~> 1.2.0)
49
+ http-cookie (1.0.3)
50
+ domain_name (~> 0.5)
51
+ http-form_data (2.3.0)
52
+ http-parser (1.2.1)
53
+ ffi-compiler (>= 1.0, < 2.0)
54
+ i18n (1.8.5)
55
+ concurrent-ruby (~> 1.0)
56
+ jaro_winkler (1.5.4)
57
+ listen (3.2.1)
58
+ rb-fsevent (~> 0.10, >= 0.10.3)
59
+ rb-inotify (~> 0.9, >= 0.9.10)
60
+ lumberjack (1.2.4)
61
+ method_source (0.9.2)
62
+ minitest (5.14.2)
63
+ nenv (0.3.0)
64
+ notiffany (0.1.3)
65
+ nenv (~> 0.1)
66
+ shellany (~> 0.0)
67
+ parallel (1.19.1)
68
+ parser (2.7.0.4)
69
+ ast (~> 2.4.0)
70
+ pry (0.12.2)
71
+ coderay (~> 1.1.0)
72
+ method_source (~> 0.9.0)
73
+ public_suffix (4.0.6)
74
+ rainbow (3.0.0)
75
+ rake (12.3.3)
76
+ rb-fsevent (0.10.3)
77
+ rb-inotify (0.10.1)
78
+ ffi (~> 1.0)
79
+ rexml (3.2.4)
80
+ rspec (3.9.0)
81
+ rspec-core (~> 3.9.0)
82
+ rspec-expectations (~> 3.9.0)
83
+ rspec-mocks (~> 3.9.0)
84
+ rspec-core (3.9.1)
85
+ rspec-support (~> 3.9.1)
86
+ rspec-expectations (3.9.1)
87
+ diff-lcs (>= 1.2.0, < 2.0)
88
+ rspec-support (~> 3.9.0)
89
+ rspec-mocks (3.9.1)
90
+ diff-lcs (>= 1.2.0, < 2.0)
91
+ rspec-support (~> 3.9.0)
92
+ rspec-support (3.9.2)
93
+ rubocop (0.80.1)
94
+ jaro_winkler (~> 1.5.1)
95
+ parallel (~> 1.10)
96
+ parser (>= 2.7.0.1)
97
+ rainbow (>= 2.2.2, < 4.0)
98
+ rexml
99
+ ruby-progressbar (~> 1.7)
100
+ unicode-display_width (>= 1.4.0, < 1.7)
101
+ ruby-progressbar (1.10.1)
102
+ shellany (0.0.1)
103
+ thor (1.0.1)
104
+ thread_safe (0.3.6)
105
+ tzinfo (1.2.7)
106
+ thread_safe (~> 0.1)
107
+ unf (0.1.4)
108
+ unf_ext
109
+ unf_ext (0.0.7.7)
110
+ unicode-display_width (1.6.1)
111
+ zeitwerk (2.3.0)
112
+
113
+ PLATFORMS
114
+ ruby
115
+
116
+ DEPENDENCIES
117
+ guard
118
+ guard-rspec
119
+ iot_services!
120
+ pry
121
+ rake (~> 12.0)
122
+ rspec (~> 3.0)
123
+ rubocop (~> 0.80.0)
124
+
125
+ BUNDLED WITH
126
+ 2.1.4
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Emmanuel Nicolau
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.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # IOT Services
2
+
3
+ A Ruby library that provides access to the [IOT EVOlens](https://www.iot.es/tools/) API.
4
+
5
+ [![Build Status](https://travis-ci.org/eeng/iot_services.svg?branch=master)](https://travis-ci.org/eeng/iot_services)
6
+
7
+ ## Installation
8
+
9
+ You know, the usual:
10
+
11
+ ```ruby
12
+ gem 'iot_services'
13
+ ```
14
+
15
+ ## ECP Usage
16
+
17
+ First, you must instantiate the API client:
18
+
19
+ ```ruby
20
+ evolens = IOTServices::Evolens::Client.new(
21
+ base_url: ENV['EVOLENS_BASE_URL'],
22
+ client_id: ENV['EVOLENS_ECP_CLIENT_ID'],
23
+ client_secret: ENV['EVOLENS_ECP_CLIENT_SECRET'],
24
+ # http_opts: {logger: Logger.new(STDOUT)}
25
+ )
26
+ ```
27
+
28
+ Then, you can create a new wearer:
29
+
30
+ ```ruby
31
+ response = evolens.new_wearer(
32
+ userEmail: ENV['EVOLENS_ECP_EMAIL'],
33
+ wearerId: 'wearer@acme.com',
34
+ wearerCode: 'wearer@acme.com',
35
+ wearerEmail: 'wearer@acme.com',
36
+ gender: 'MALE',
37
+ birthYear: '1982',
38
+ locationCode: '28007',
39
+ termsAndConditions: 'Y',
40
+ rightEyePrescription: {sph: '+0.00', cyl: '-0.00', axis: '00º', add: '+1.00'},
41
+ leftEyePrescription: {sph: '+0.00', cyl: '-0.00', axis: '00º', add: '+1.00'},
42
+ labCode: 'OPTICALAB',
43
+ languageCode: 'es'
44
+ )
45
+ ```
46
+
47
+ And also send its lifestyle questionary:
48
+
49
+ ```ruby
50
+ response = evolens.new_questionary(
51
+ orderId: response[:orderId],
52
+ activities: [
53
+ {name: 'DRIVE', frequency: 'DURATION_A_LITTLE', isTopOne: false},
54
+ {name: 'READ', frequency: 'DURATION_ZERO', isTopOne: false},
55
+ {name: 'COMPUTER', frequency: 'DURATION_A_LOT', isTopOne: false},
56
+ {name: 'OFFICE', frequency: 'DURATION_A_LOT', isTopOne: false},
57
+ {name: 'RETAIL', frequency: 'DURATION_A_LOT', isTopOne: true}
58
+ ]
59
+ )
60
+ ```
61
+
62
+ Finally, in the response you may find the EVOlens ID:
63
+
64
+ ```ruby
65
+ response['orderCode'] # => "9772005ZRN87"
66
+ ```
67
+
68
+ ### Listing Providers and Designs
69
+
70
+ ```ruby
71
+ evolens.get_providers_and_designs
72
+ # => [{'ProviderName' => 'P1', 'Designs' => ['D1', 'D2', ...]}, ...]
73
+ ```
74
+
75
+ ### Authentication and Authorization
76
+
77
+ These steps are done automatically the first time a request to the API is executed, and then the authorization token is cached. So it's recommended that you reuse the client instance on every request.
78
+ However, if you want to explicitly do this, you can call the `login` method.
79
+ In addition, when the authorization token expires, a new one it automatically requested on the next API call.
80
+
81
+ ## ECP Registration
82
+
83
+ The API is a bit convoluted on this subject. It requires another set of secrets for an admin role, an additional login request, and finally, the ECP registration is done in two steps: First, an ECP "placeholder" must be created with the admin role, and then its credentials are associated with an ECP role.
84
+
85
+ Consequently, you'll need two client instances to make this happen:
86
+
87
+ ```ruby
88
+ adm = IOTServices::Evolens::Client.new(
89
+ base_url: ENV['EVOLENS_BASE_URL'],
90
+ client_id: ENV['EVOLENS_ADMIN_CLIENT_ID'],
91
+ client_secret: ENV['EVOLENS_ADMIN_CLIENT_SECRET'],
92
+ admin_credentials: {
93
+ email: ENV['EVOLENS_ADMIN_EMAIL'],
94
+ password: ENV['EVOLENS_ADMIN_PASSWORD']
95
+ }
96
+ )
97
+
98
+ ecp = IOTServices::Evolens::Client.new(
99
+ base_url: ENV['EVOLENS_BASE_URL'],
100
+ client_id: ENV['EVOLENS_ECP_CLIENT_ID'],
101
+ client_secret: ENV['EVOLENS_ECP_CLIENT_SECRET']
102
+ )
103
+ ```
104
+
105
+ Finally, you must call these two methods to complete the process:
106
+
107
+ ```ruby
108
+ adm.create_ecp(
109
+ store_id: 'THE_STORE_ID',
110
+ ecp_code: 'THE_ECP_CODE'
111
+ )
112
+
113
+ ecp.register_ecp(
114
+ store_code: 'THE_STORE_CODE',
115
+ ecp_code: 'THE_ECP_CODE',
116
+ email: 'SOME_EMAIL',
117
+ password: 'SOME_PASSWORD'
118
+ )
119
+ ```
120
+
121
+ If they don't throw any errors it means everything went well.
122
+
123
+ ## Development
124
+
125
+ 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.
126
+
127
+ ## Contributing
128
+
129
+ Bug reports and pull requests are welcome!
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'iot_services'
5
+
6
+ def new_clients
7
+ adm = IOTServices::Evolens::Client.new(
8
+ base_url: ENV['EVOLENS_BASE_URL'],
9
+ client_id: ENV['EVOLENS_ADMIN_CLIENT_ID'],
10
+ client_secret: ENV['EVOLENS_ADMIN_CLIENT_SECRET'],
11
+ admin_credentials: {
12
+ email: ENV['EVOLENS_ADMIN_EMAIL'],
13
+ password: ENV['EVOLENS_ADMIN_PASSWORD']
14
+ },
15
+ http_opts: {logger: Logger.new(STDOUT)}
16
+ )
17
+
18
+ ecp = IOTServices::Evolens::Client.new(
19
+ base_url: ENV['EVOLENS_BASE_URL'],
20
+ client_id: ENV['EVOLENS_ECP_CLIENT_ID'],
21
+ client_secret: ENV['EVOLENS_ECP_CLIENT_SECRET'],
22
+ http_opts: {logger: Logger.new(STDOUT)}
23
+ )
24
+
25
+ [adm, ecp]
26
+ end
27
+
28
+ require 'pry'
29
+ Pry.start
data/bin/setup ADDED
@@ -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,30 @@
1
+ require_relative 'lib/iot_services/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'iot_services'
5
+ spec.version = IOTServices::VERSION
6
+ spec.authors = ['Emmanuel Nicolau']
7
+ spec.email = ['emmanicolau@gmail.com']
8
+
9
+ spec.summary = 'IOT Services'
10
+ spec.description = 'IOT Services'
11
+ spec.homepage = 'https://github.com/eeng/iot_services'
12
+
13
+ # Specify which files should be added to the gem when it is released.
14
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
15
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
16
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ end
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'activesupport', '>= 4.0'
23
+ spec.add_dependency 'http', '~> 4.3.0'
24
+ spec.add_development_dependency 'guard'
25
+ spec.add_development_dependency 'guard-rspec'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'rake', '~> 12.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'rubocop', '~> 0.80.0'
30
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'active_support/core_ext/hash/reverse_merge'
4
+
5
+ require 'iot_services/version'
6
+ require 'iot_services/errors'
7
+ require 'iot_services/adapters/json_response'
8
+ require 'iot_services/adapters/http'
9
+ require 'iot_services/evolens/errors'
10
+ require 'iot_services/evolens/client'
@@ -0,0 +1,29 @@
1
+ require 'http'
2
+
3
+ module IOTServices
4
+ module Adapters
5
+ class HTTP
6
+ def initialize base_url: '', logger: nil
7
+ @base_url = base_url
8
+ @logger = logger
9
+ end
10
+
11
+ %i[get post put].each do |method|
12
+ define_method method do |url, args = {}|
13
+ request url, method, args
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def request url, method, headers: {}, json: nil
20
+ http = ::HTTP.headers(headers)
21
+ http = http.use(logging: {logger: @logger}) if @logger
22
+ response = http.send(method, "#{@base_url}#{url}", json: json)
23
+ JsonResponse.new(response.status, response.body)
24
+ rescue ::HTTP::Error => e
25
+ raise NetworkError, e
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ require 'json'
2
+
3
+ module IOTServices
4
+ module Adapters
5
+ class JsonResponse
6
+ attr_reader :status, :body
7
+
8
+ def initialize status, body
9
+ @status = status
10
+ @body = JSON.parse(body)
11
+ end
12
+
13
+ def ok?
14
+ @status == 200
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module IOTServices
2
+ class Error < StandardError; end
3
+
4
+ class NetworkError < Error; end
5
+ end
@@ -0,0 +1,184 @@
1
+ module IOTServices
2
+ module Evolens
3
+ class Client
4
+ attr_reader :token, :token_expires
5
+
6
+ def initialize(
7
+ base_url:,
8
+ client_id:,
9
+ client_secret:,
10
+ admin_credentials: nil, # Only needed for admin role endpoints
11
+ http_opts: {},
12
+ http: Adapters::HTTP.new(http_opts.merge(base_url: base_url)),
13
+ token: nil,
14
+ token_expires: nil
15
+ )
16
+ @base_url = base_url
17
+ @client_id = client_id
18
+ @client_secret = client_secret
19
+ @admin_credentials = admin_credentials
20
+ @http = http
21
+ @token = token
22
+ @token_expires = token_expires
23
+ end
24
+
25
+ def authenticate
26
+ response = @http.get('/oauth/authorize', headers: {'X-Client-Id' => @client_id})
27
+ on_ok_response response do
28
+ @token = response.body['data']['authorization']
29
+ end
30
+ self
31
+ end
32
+
33
+ def authorize
34
+ headers = {'X-Client-Id' => @client_id, 'X-Client-Secret' => @client_secret}
35
+ response = @http.get("/oauth/authorize/#{@token}", headers: headers)
36
+ on_ok_response response do
37
+ # I think it's safe to override the token obtained during the authentication step with this new one,
38
+ # as it's no longer needed on following requests.
39
+ @token = response.body['data']['token']
40
+ @token_expires = Time.parse(response.body['data']['expire'])
41
+ end
42
+ self
43
+ end
44
+
45
+ # To access admin role operations, like creating an ECP, the API requires an additional login step.
46
+ # Should not be needed to use this explicitly as it's called from the main login method.
47
+ def admin_panel_login
48
+ headers = {'Authorization' => @token, 'Content-Type' => 'application/json'}
49
+ response = @http.post('/ga/user/login', headers: headers, json: @admin_credentials)
50
+ on_ok_response response do
51
+ # I think it's safe to override the token as the admin panel token appears to have a shorter span.
52
+ # This way with can reuse the on_private_endpoint functionality, otherwise we would have to replicate
53
+ # the whole relogin behaviour for this different tokens.
54
+ @token = response.body['data'].first['token']
55
+ @token_expires = Time.parse(response.body['data'].first['tokenExpire'])
56
+ end
57
+ self
58
+ end
59
+
60
+ # Executes the authentication and authorization procedure only if we haven't obtain the tokens yet,
61
+ # or if they've expired. Idempotent operation.
62
+ def login
63
+ relogin if authorization_expired?
64
+ self
65
+ end
66
+
67
+ # Executes the authentication and authorization procedure to obtain the tokens needed for the subsequent requests.
68
+ def relogin
69
+ reset_auth_data
70
+ authenticate
71
+ authorize
72
+ admin_panel_login if @admin_credentials
73
+ end
74
+
75
+ # To create a visual profile, we first need to create the patient.
76
+ # Make sure you've provided ECP role credentials in the constructor.
77
+ def new_wearer data
78
+ on_private_endpoint do |headers|
79
+ @http.post('/ecp/store/wearer/new', headers: headers, json: data)
80
+ end
81
+ end
82
+
83
+ # Uploads a patients visual profile.
84
+ # Requires ECP role credentials.
85
+ def new_questionary data
86
+ on_private_endpoint do |headers|
87
+ @http.post('/ecp/store/wearer/new/questionary', headers: headers, json: data)
88
+ end
89
+ end
90
+
91
+ # Returns an [Array] of providers and its designs.
92
+ # Requires ECP role credentials.
93
+ def get_providers_and_designs
94
+ on_private_endpoint do |headers|
95
+ @http.get('/ecp/user/GetProvidersAndDesign', headers: headers)
96
+ end
97
+ end
98
+
99
+ # Creates an ECP "placeholder" that later should be associated with its email.
100
+ # Requires Admin role credentials.
101
+ def create_ecp store_id:, ecp_code:
102
+ json = {'EcpCode' => ecp_code}
103
+ on_private_endpoint do |headers|
104
+ @http.post("/ga/ecps/#{store_id}", headers: headers, json: json)
105
+ end.first
106
+ end
107
+
108
+ # Links an ECP "placeholder" with its email and password.
109
+ # Requires ECP role credentials.
110
+ def register_ecp store_code:, ecp_code:, email:, password:
111
+ json = {
112
+ storeCode: store_code,
113
+ ecpUserCode: ecp_code,
114
+ email: email,
115
+ password: password
116
+ }
117
+ on_private_endpoint do |headers|
118
+ @http.post('/ecp/user/register', headers: headers, json: json)
119
+ end
120
+ end
121
+
122
+ # Unlinks an ECP email, i.e. leaves it open to register another email
123
+ # Requires Admin role credentials.
124
+ def deregister_ecp ecp_id:, ecp_code:
125
+ on_private_endpoint do |headers|
126
+ @http.put("/ga/ecps/unlink?id=#{ecp_id}&EcpCode=#{ecp_code}", headers: headers)
127
+ end.first
128
+ end
129
+
130
+ # Returns the list of created ECPs.
131
+ # Requires Admin role credentials.
132
+ def get_ecps
133
+ on_private_endpoint do |headers|
134
+ @http.get('/ga/ecps', headers: headers)
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def on_ok_response response
141
+ case response.status
142
+ when 200
143
+ yield
144
+ when 401
145
+ raise UnauthorizedError, response
146
+ else
147
+ raise ServerError, response
148
+ end
149
+ end
150
+
151
+ def retry_auth_if_expired
152
+ retries ||= 0
153
+ login
154
+ yield
155
+ rescue UnauthorizedError
156
+ if (retries += 1) < 2
157
+ relogin
158
+ retry
159
+ else
160
+ raise
161
+ end
162
+ end
163
+
164
+ def authorization_expired?
165
+ @token.nil? || @token_expires.nil?
166
+ end
167
+
168
+ def reset_auth_data
169
+ @token = nil
170
+ @token_expires = nil
171
+ end
172
+
173
+ def on_private_endpoint
174
+ retry_auth_if_expired do
175
+ headers = {'Authorization' => @token, 'Content-Type' => 'application/json'}
176
+ response = yield headers
177
+ on_ok_response response do
178
+ response.body['data']
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,29 @@
1
+ module IOTServices
2
+ module Evolens
3
+ class ServerError < Error
4
+ attr_reader :code, :status
5
+
6
+ def initialize response
7
+ super response_message(response)
8
+ @code = response_code(response)
9
+ @status = response.status
10
+ end
11
+
12
+ def response_message response
13
+ if response.body['error']
14
+ response.body['error']['message']
15
+ elsif response.body['Message']
16
+ [response.body['Message'], response.body['MessageDetail']].join("\n")
17
+ else
18
+ 'Error response not recognized'
19
+ end
20
+ end
21
+
22
+ def response_code response
23
+ response.body['error']['code'] if response.body['error']
24
+ end
25
+ end
26
+
27
+ class UnauthorizedError < ServerError; end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module IOTServices
2
+ VERSION = '0.1.3'
3
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iot_services
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Emmanuel Nicolau
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '12.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '12.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.80.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.80.0
125
+ description: IOT Services
126
+ email:
127
+ - emmanicolau@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".rubocop.yml"
135
+ - ".ruby-version"
136
+ - ".travis.yml"
137
+ - Gemfile
138
+ - Gemfile.lock
139
+ - Guardfile
140
+ - LICENSE.txt
141
+ - README.md
142
+ - Rakefile
143
+ - bin/console
144
+ - bin/setup
145
+ - iot_services.gemspec
146
+ - lib/iot_services.rb
147
+ - lib/iot_services/adapters/http.rb
148
+ - lib/iot_services/adapters/json_response.rb
149
+ - lib/iot_services/errors.rb
150
+ - lib/iot_services/evolens/client.rb
151
+ - lib/iot_services/evolens/errors.rb
152
+ - lib/iot_services/version.rb
153
+ homepage: https://github.com/eeng/iot_services
154
+ licenses: []
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.7.3
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: IOT Services
176
+ test_files: []