-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.rb
196 lines (172 loc) · 5.42 KB
/
app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
require 'sinatra'
require 'json'
require 'base64'
require_relative 'underwriting'
require_relative 'models'
require_relative 'lib/store'
## configuration
configure do
set :environments, %w(development test staging production)
set :allow_test_responses, false
set :organization, 'Example Capital'
set :return_offer_urls, true
end
configure :development do
set :api_username, 'development'
set :api_password, 'abc123'
set :allow_test_responses, true
end
configure :test do
set :api_username, 'test'
set :api_password, 'abc123'
set :allow_test_responses, true
end
configure :staging do
set :api_username, 'staging'
set :api_password, 'staging_password_CHANGEME'
set :allow_test_responses, true
end
configure :production do
set :api_username, 'production'
set :api_password, 'production_password_CHANGEME'
end
## API handlers
# require authorization for the API.
before '/api/v1/*' do
unless request.env['HTTP_AUTHORIZATION'] == "Basic #{Base64.strict_encode64("#{settings.api_username}:#{settings.api_password}")}"
halt 401, 'Not authorized'
end
end
# handle JSON post bodies for the API.
before '/api/v1/*' do
next if !request.post? || request.path =~ /\/documents$/
begin
request.body.rewind
@request_body = JSON.parse(request.body.read)
rescue JSON::ParserError => e
halt 400, e.to_s
end
end
# ensure successful API responses have a JSON content-type, if not content-type is set.
after '/api/v1/*' do
response.headers['Content-Type'] = 'application/json; charset=utf-8' if response.status == 200 && !response.headers.key?('Content-Type')
end
# handle prequalification API requests.
post '/api/v1/prequalify' do
Store.for(Application, settings.environment) do |store|
application = store.get(@request_body['company']['uuid']) if @request_body['company'] && @request_body['company']['uuid']
if application
application.attributes = @request_body
else
application = Application.new(@request_body)
end
if application.valid?
begin
application.decision = Underwriting.new(test_mode: settings.allow_test_responses).preapprove(application)
rescue => e
return [500, e.to_s]
end
if application.decision.decision == 'preapproved' && settings.return_offer_urls
application.decision.offers.each_with_index do |offer, i|
offer.url = url("/apply-for-loan/#{application.company.uuid}?offer=#{i + 1}")
end
end
store.put(application.company.uuid, application)
response_hash = application.decision.to_hash
response_hash[:updated] = application.created_at != application.updated_at
[200, JSON.pretty_generate(response_hash)]
else
[422, application.errors.join("\n")]
end
end
end
# handle document uploads.
post '/api/v1/documents' do
return [400, 'POST body must be multipart/form-data'] unless (request.env['CONTENT_TYPE'] || '') =~ /^multipart\/form-data/
file = params[:file]
filename = file && file[:filename]
file_handle = file && file[:tempfile]
return [422, 'Missing parameter: file'] unless file && filename && file_handle
uuid = params[:company_uuid]
return [422, 'Missing parameter: company_uuid'] unless uuid
Store.for(Application, settings.environment) do |store|
application = store.get(uuid)
return [404, "No application for ID #{uuid}"] unless application
document = Document.new
document.attributes = {
uuid: SecureRandom.uuid,
filename: filename,
data: file_handle.read,
document_type: params[:document_type],
document_periods: params[:document_periods]
}
return [422, document.errors.join("\n")] unless document.valid?
application.documents << document
store.put(uuid, application)
end
[200]
end
## "admin" pages, for viewing API submissions.
before '/admin/*' do
@organization = settings.organization
end
get '/admin' do
redirect to('/admin/applications')
end
get '/admin/applications' do
Store.for(Application, settings.environment) do |store|
@applications = store.all
end
erb 'admin/applications'.to_sym
end
get '/admin/application/:uuid' do
Store.for(Application, settings.environment) do |store|
@application = store.get(params[:uuid])
end
if @application
erb 'admin/application'.to_sym
else
[404]
end
end
get '/admin/application/:application_uuid/document/:document_uuid' do
Store.for(Application, settings.environment) do |store|
application = store.get(params[:application_uuid])
document = (application.documents || []).find { |d| d.uuid == params[:document_uuid] } if application
if document
content_type(
case document.filename.downcase
when /\.pdf$/
'application/pdf'
when /\.jpg$/
'image/jpeg'
when /\.png$/
'image/png'
when /\.txt$/
'text/plain'
else
'application/octet-stream'
end
)
[200, document.data]
else
[404]
end
end
end
# landing page for URLs returned on offers from the API.
get '/apply-for-loan/:uuid' do
@organization = settings.organization
Store.for(Application, settings.environment) do |store|
@application = store.get(params[:uuid])
end
if @application && @application.decision.decision == 'preapproved'
@offer =
if params[:offer] && params[:offer].to_i > 0
@application.decision.offers[params[:offer].to_i - 1]
end
erb :apply
else
erb :application_not_found
end
end