noyo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +57 -0
- data/.codecov.yml +13 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +73 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +89 -0
- data/README.md +176 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/noyo.rb +3 -0
- data/lib/noyo_accounts.rb +4 -0
- data/lib/noyo_accounts/client.rb +6 -0
- data/lib/noyo_accounts/client/services.rb +8 -0
- data/lib/noyo_accounts/client/services/auth_service.rb +45 -0
- data/lib/noyo_api.rb +6 -0
- data/lib/noyo_api/api.rb +13 -0
- data/lib/noyo_api/client.rb +9 -0
- data/lib/noyo_api/client/services.rb +8 -0
- data/lib/noyo_api/client/services/base_service.rb +37 -0
- data/lib/noyo_api/client/user_agent.rb +15 -0
- data/lib/noyo_api/client/version.rb +5 -0
- data/lib/noyo_api/configuration.rb +28 -0
- data/lib/noyo_fulfillment.rb +5 -0
- data/lib/noyo_fulfillment/api_response.rb +32 -0
- data/lib/noyo_fulfillment/models.rb +25 -0
- data/lib/noyo_fulfillment/models/api_resource.rb +191 -0
- data/lib/noyo_fulfillment/models/api_resource_collection.rb +41 -0
- data/lib/noyo_fulfillment/models/base_model.rb +115 -0
- data/lib/noyo_fulfillment/models/contact.rb +19 -0
- data/lib/noyo_fulfillment/models/demographic_change.rb +8 -0
- data/lib/noyo_fulfillment/models/dependent.rb +15 -0
- data/lib/noyo_fulfillment/models/employee.rb +108 -0
- data/lib/noyo_fulfillment/models/group.rb +25 -0
- data/lib/noyo_fulfillment/models/group_enrollment.rb +20 -0
- data/lib/noyo_fulfillment/models/group_plans.rb +15 -0
- data/lib/noyo_fulfillment/models/group_plans/add.rb +11 -0
- data/lib/noyo_fulfillment/models/group_plans/base.rb +22 -0
- data/lib/noyo_fulfillment/models/group_plans/dental.rb +13 -0
- data/lib/noyo_fulfillment/models/group_plans/life.rb +11 -0
- data/lib/noyo_fulfillment/models/group_plans/long_term_disability.rb +11 -0
- data/lib/noyo_fulfillment/models/group_plans/medical.rb +12 -0
- data/lib/noyo_fulfillment/models/group_plans/short_term_disability.rb +11 -0
- data/lib/noyo_fulfillment/models/group_plans/vision.rb +11 -0
- data/lib/noyo_fulfillment/models/individual_enrollment.rb +32 -0
- data/lib/noyo_fulfillment/models/location.rb +16 -0
- data/lib/noyo_fulfillment/models/member_request.rb +40 -0
- data/lib/noyo_fulfillment/models/member_transaction.rb +27 -0
- data/lib/noyo_fulfillment/models/mixins.rb +9 -0
- data/lib/noyo_fulfillment/models/mixins/has_person.rb +29 -0
- data/lib/noyo_fulfillment/models/mixins/nested_under.rb +64 -0
- data/lib/noyo_fulfillment/models/new_hire.rb +7 -0
- data/lib/noyo_fulfillment/models/open_enrollment.rb +7 -0
- data/lib/noyo_fulfillment/models/person.rb +18 -0
- data/lib/noyo_fulfillment/models/qualifying_life_event.rb +8 -0
- data/lib/noyo_fulfillment/models/termination.rb +9 -0
- data/noyo.gemspec +47 -0
- metadata +232 -0
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/noyo.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module NoyoAccounts
|
4
|
+
module Client
|
5
|
+
module Services
|
6
|
+
class AuthService < NoyoApi::Client::Services::BaseService
|
7
|
+
include HTTParty
|
8
|
+
include NoyoApi::Client::UserAgent
|
9
|
+
base_uri NoyoApi::Api.config.accounts_base_uri
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
# Get the latest config value, in case it changed
|
13
|
+
self.class.base_uri(NoyoApi::Api.config.accounts_base_uri)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.authorize_path
|
17
|
+
'/auth/public/token'
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorize(client_id, client_secret)
|
21
|
+
auth = {
|
22
|
+
username: client_id,
|
23
|
+
password: client_secret,
|
24
|
+
}
|
25
|
+
body = {
|
26
|
+
grant_type: 'client_credentials',
|
27
|
+
}
|
28
|
+
options = {
|
29
|
+
body: body.to_json,
|
30
|
+
basic_auth: auth,
|
31
|
+
headers: {
|
32
|
+
'Content-Type' => 'application/json',
|
33
|
+
'Accept' => 'application/json',
|
34
|
+
'User-Agent' => self.class.user_agent,
|
35
|
+
},
|
36
|
+
}
|
37
|
+
result = self.class.post(self.class.authorize_path, options)
|
38
|
+
raise 'Bad credentials.' if result.code == 401
|
39
|
+
|
40
|
+
parse_httparty_response(result)&.[]('access_token')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/noyo_api.rb
ADDED
data/lib/noyo_api/api.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module NoyoApi
|
2
|
+
module Client
|
3
|
+
module Services
|
4
|
+
class BaseService
|
5
|
+
def self.root_path
|
6
|
+
'/api/v1'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.resource
|
10
|
+
class_name = name.split('::').last
|
11
|
+
class_name.gsub(/(.*)(Service)/, '\\1').downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.resource_path
|
15
|
+
File.join(root_path, resource)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def headers(*_args)
|
21
|
+
{
|
22
|
+
'Content-Type' => 'application/json',
|
23
|
+
'Accept' => 'application/json',
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_httparty_response(httparty_response)
|
28
|
+
response = httparty_response.parsed_response
|
29
|
+
if response.is_a? String
|
30
|
+
response = JSON.parse(response).with_indifferent_access
|
31
|
+
end
|
32
|
+
response
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module NoyoApi
|
2
|
+
class Configuration
|
3
|
+
CONFIG_SETTERS = [
|
4
|
+
:client_id, :client_secret, :access_token, :accounts_base_uri, :fulfillment_base_uri,
|
5
|
+
:verbose,
|
6
|
+
].freeze
|
7
|
+
REQUIRED_SETTERS = [ :client_id, :client_secret ].freeze
|
8
|
+
attr_accessor(*CONFIG_SETTERS)
|
9
|
+
|
10
|
+
def accounts_base_uri
|
11
|
+
@accounts_base_uri || 'https://accounts.development.noyoconnect.com'
|
12
|
+
end
|
13
|
+
|
14
|
+
def fulfillment_base_uri
|
15
|
+
@fulfillment_base_uri || 'https://fulfillment.development.noyoconnect.com'
|
16
|
+
end
|
17
|
+
|
18
|
+
def unconfigured?
|
19
|
+
unconfigured_setters = REQUIRED_SETTERS.map do |setter|
|
20
|
+
value = send(setter)
|
21
|
+
value.nil? ||
|
22
|
+
(value != true && value != false && (!value.respond_to?(:empty?) || value.empty?))
|
23
|
+
end
|
24
|
+
|
25
|
+
unconfigured_setters.any?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module NoyoFulfillment
|
2
|
+
class ServerError < StandardError
|
3
|
+
def initialize(msg = 'There seems to be a problem server side. Please contact ' \
|
4
|
+
"Noyo at eng-admin@gonoyo.com for a fix for the error at #{Time.now}.")
|
5
|
+
super(msg)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ApiResponse
|
10
|
+
attr_accessor :meta, :response
|
11
|
+
|
12
|
+
def initialize(response)
|
13
|
+
body = response.parsed_response
|
14
|
+
if body.is_a? String
|
15
|
+
parsed_body = JSON.parse(body)
|
16
|
+
if parsed_body.is_a?(Hash)
|
17
|
+
body = parsed_body.with_indifferent_access
|
18
|
+
elsif parsed_body.is_a?(Array)
|
19
|
+
body = parsed_body.map(&:with_indifferent_access)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
raise ServerError unless body.is_a?(Hash) || body.is_a?(Array)
|
23
|
+
|
24
|
+
if body.is_a?(Hash) && body.key?('meta')
|
25
|
+
@meta = body['meta']
|
26
|
+
@response = body['response']
|
27
|
+
else
|
28
|
+
@response = body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'noyo_fulfillment/models/mixins'
|
2
|
+
require 'noyo_fulfillment/models/base_model'
|
3
|
+
require 'noyo_fulfillment/models/api_resource'
|
4
|
+
require 'noyo_fulfillment/models/api_resource_collection'
|
5
|
+
require 'noyo_fulfillment/models/person'
|
6
|
+
require 'noyo_fulfillment/models/employee'
|
7
|
+
require 'noyo_fulfillment/models/dependent'
|
8
|
+
require 'noyo_fulfillment/models/group'
|
9
|
+
require 'noyo_fulfillment/models/contact'
|
10
|
+
require 'noyo_fulfillment/models/location'
|
11
|
+
require 'noyo_fulfillment/models/group_enrollment'
|
12
|
+
require 'noyo_fulfillment/models/group_plans'
|
13
|
+
require 'noyo_fulfillment/models/new_hire'
|
14
|
+
require 'noyo_fulfillment/models/termination'
|
15
|
+
require 'noyo_fulfillment/models/demographic_change'
|
16
|
+
require 'noyo_fulfillment/models/open_enrollment'
|
17
|
+
require 'noyo_fulfillment/models/qualifying_life_event'
|
18
|
+
require 'noyo_fulfillment/models/member_request'
|
19
|
+
require 'noyo_fulfillment/models/member_transaction'
|
20
|
+
require 'noyo_fulfillment/models/individual_enrollment'
|
21
|
+
|
22
|
+
module NoyoFulfillment
|
23
|
+
module Models
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
# Used for underscore, pluralize
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
module NoyoFulfillment
|
6
|
+
class ApiResource < NoyoFulfillment::BaseModel
|
7
|
+
include HTTParty
|
8
|
+
include NoyoApi::Client::UserAgent
|
9
|
+
base_uri NoyoApi::Api.config.fulfillment_base_uri
|
10
|
+
|
11
|
+
@@PAGE_SIZE = 20
|
12
|
+
@@MAX_AUTH_ATTEMPTS = 5
|
13
|
+
|
14
|
+
def initialize(params={})
|
15
|
+
super(params)
|
16
|
+
# Get the latest config value, in case it changed
|
17
|
+
self.class.base_uri(NoyoApi::Api.config.fulfillment_base_uri)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.resource_path_name
|
21
|
+
class_name.underscore.pluralize
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.resource_url(**_)
|
25
|
+
if self == ApiResource
|
26
|
+
message = 'ApiResource is an abstract class. You should perform actions on its subclasses.'
|
27
|
+
raise NotImplementedError, message
|
28
|
+
end
|
29
|
+
"/api/v1/#{resource_path_name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.single_resource_url(id, **_)
|
33
|
+
"#{resource_url}/#{id}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def single_resource_url
|
37
|
+
self.class.single_resource_url(self&.id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.perform_request_authenticated(*args)
|
41
|
+
if args.length > 2 && !args[2].nil?
|
42
|
+
options = args[2]
|
43
|
+
else
|
44
|
+
options = {}
|
45
|
+
end
|
46
|
+
recursion_depth = options[:recursion_depth] || 0
|
47
|
+
|
48
|
+
if NoyoApi::Api.config.access_token.nil?
|
49
|
+
authenticate
|
50
|
+
end
|
51
|
+
|
52
|
+
if !NoyoApi::Api.config.access_token.nil?
|
53
|
+
options[:headers] ||= {}
|
54
|
+
options[:headers] = options[:headers].merge(headers)
|
55
|
+
|
56
|
+
if NoyoApi::Api.config.verbose
|
57
|
+
puts "Calling API with action: #{args[0]}"
|
58
|
+
puts "Calling API with URL: #{args[1]}"
|
59
|
+
puts "Calling API with options: #{options.to_json}"
|
60
|
+
end
|
61
|
+
|
62
|
+
response = send(args[0], args[1], options)
|
63
|
+
body = get_body(response)
|
64
|
+
|
65
|
+
case response.code
|
66
|
+
when 401
|
67
|
+
reauthenticated = handle_unauthenticated(body)
|
68
|
+
if reauthenticated
|
69
|
+
recursion_depth += 1
|
70
|
+
if recursion_depth < @@MAX_AUTH_ATTEMPTS
|
71
|
+
options[:recursion_depth] = recursion_depth
|
72
|
+
perform_request_authenticated(args[0], args[1], options)
|
73
|
+
else
|
74
|
+
puts "Couldn't successfully reauthenticate." if NoyoApi::Api.config.verbose
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
when 422
|
79
|
+
if NoyoApi::Api.config.verbose
|
80
|
+
puts "Body of response: #{body}"
|
81
|
+
end
|
82
|
+
response
|
83
|
+
else
|
84
|
+
response
|
85
|
+
end
|
86
|
+
elsif access_token.nil?
|
87
|
+
raise 'Could not log in. Please try again later.'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.get_body(response)
|
92
|
+
if response.nil?
|
93
|
+
return {}
|
94
|
+
end
|
95
|
+
|
96
|
+
body = response.parsed_response
|
97
|
+
if body.is_a? String
|
98
|
+
parsed_body = JSON.parse(body)
|
99
|
+
if parsed_body.is_a?(Hash)
|
100
|
+
body = parsed_body.with_indifferent_access
|
101
|
+
elsif parsed_body.is_a?(Array)
|
102
|
+
body = parsed_body.map(&:with_indifferent_access)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
body
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.headers
|
110
|
+
{
|
111
|
+
'Authorization': "Bearer #{NoyoApi::Api.config.access_token}",
|
112
|
+
'Accept': 'application/json',
|
113
|
+
'Content-Type': 'application/json',
|
114
|
+
'User-Agent' => user_agent,
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.authenticate
|
119
|
+
client_id = NoyoApi::Api.config.client_id
|
120
|
+
client_secret = NoyoApi::Api.config.client_secret
|
121
|
+
service = NoyoAccounts::Client::Services::AuthService.new
|
122
|
+
access_token = service.authorize(client_id, client_secret)
|
123
|
+
NoyoApi::Api.config.access_token = access_token
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.all(**args)
|
127
|
+
page_number = args[:page_number] || 1
|
128
|
+
base_uri(NoyoApi::Api.config.fulfillment_base_uri)
|
129
|
+
|
130
|
+
offset = (page_number - 1) * @@PAGE_SIZE
|
131
|
+
query = { offset: offset, page_size: @@PAGE_SIZE }
|
132
|
+
response = perform_request_authenticated(:get, resource_url(args), query: query)
|
133
|
+
api_response = ApiResponse.new(response)
|
134
|
+
response_body = api_response.response
|
135
|
+
if response_body.empty?
|
136
|
+
nil
|
137
|
+
else
|
138
|
+
if response_body.is_a?(Array)
|
139
|
+
results = response_body.map{ |response_item| new(response_item) }
|
140
|
+
NoyoFulfillment::ApiResourceCollection.new(results, page_number)
|
141
|
+
else
|
142
|
+
new(response_body)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.find(id, **args)
|
148
|
+
self.base_uri(NoyoApi::Api.config.fulfillment_base_uri)
|
149
|
+
|
150
|
+
response = perform_request_authenticated(:get, single_resource_url(id, args))
|
151
|
+
body = get_body(response)
|
152
|
+
|
153
|
+
if body.nil? || body.empty?
|
154
|
+
nil
|
155
|
+
else
|
156
|
+
new(body)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.handle_unauthenticated(body)
|
161
|
+
if body&.[]('code') == 16
|
162
|
+
puts 'Need to reauth' if NoyoApi::Api.config.verbose
|
163
|
+
# returns the access token
|
164
|
+
return authenticate
|
165
|
+
end
|
166
|
+
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_hash
|
171
|
+
attributes
|
172
|
+
end
|
173
|
+
|
174
|
+
def create
|
175
|
+
options = { body: create_hash.to_json }
|
176
|
+
response = self.class.perform_request_authenticated(:post, create_url, options)
|
177
|
+
body = self.class.get_body(response)
|
178
|
+
update_attrs(self.class.new(body))
|
179
|
+
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
def resource_url
|
184
|
+
self.class.resource_url
|
185
|
+
end
|
186
|
+
|
187
|
+
def create_url
|
188
|
+
resource_url
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|